Skip to content

Commit ed42010

Browse files
authored
wrangler: clearer user-facing error for self-signed cert issues (#11615)
* wrangler: clearer user-facing error for self-signed cert issues * wrangler: add changeset for this
1 parent 4201472 commit ed42010

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

.changeset/cute-cat-club.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Add helpful warning when SSL certificate errors occur due to corporate proxies or VPNs intercepting HTTPS traffic. When errors like "self-signed certificate in certificate chain" are detected, wrangler now displays guidance about installing missing system roots from your corporate proxy vendor.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
import { handleError } from "../../core/handle-errors";
3+
import { mockConsoleMethods } from "../helpers/mock-console";
4+
5+
describe("handleError", () => {
6+
const std = mockConsoleMethods();
7+
8+
beforeEach(() => {
9+
vi.clearAllMocks();
10+
});
11+
12+
describe("SSL/TLS certificate errors", () => {
13+
it("should log a warning for self-signed certificate errors", async () => {
14+
const error = new Error("self-signed certificate in certificate chain");
15+
16+
await handleError(error, {}, []);
17+
18+
expect(std.warn).toContain(
19+
"Wrangler detected that a corporate proxy or VPN might be enabled"
20+
);
21+
expect(std.warn).toContain("certificate mismatch");
22+
expect(std.warn).toContain("install the missing system roots");
23+
});
24+
25+
it("should log a warning for unable to verify first certificate errors", async () => {
26+
const error = new Error("unable to verify the first certificate");
27+
28+
await handleError(error, {}, []);
29+
30+
expect(std.warn).toContain(
31+
"Wrangler detected that a corporate proxy or VPN might be enabled"
32+
);
33+
});
34+
35+
it("should log a warning for unable to get local issuer certificate errors", async () => {
36+
const error = new Error("unable to get local issuer certificate");
37+
38+
await handleError(error, {}, []);
39+
40+
expect(std.warn).toContain(
41+
"Wrangler detected that a corporate proxy or VPN might be enabled"
42+
);
43+
});
44+
45+
it("should log a warning when certificate error is in error cause", async () => {
46+
const cause = new Error("self-signed certificate in certificate chain");
47+
const error = new Error("fetch failed", { cause });
48+
49+
await handleError(error, {}, []);
50+
51+
expect(std.warn).toContain(
52+
"Wrangler detected that a corporate proxy or VPN might be enabled"
53+
);
54+
});
55+
56+
it("should log a warning when certificate error is part of a longer message", async () => {
57+
const error = new Error(
58+
"Request failed: self-signed certificate in certificate chain (details: some info)"
59+
);
60+
61+
await handleError(error, {}, []);
62+
63+
expect(std.warn).toContain(
64+
"Wrangler detected that a corporate proxy or VPN might be enabled"
65+
);
66+
});
67+
68+
it("should not log a warning for unrelated errors", async () => {
69+
const error = new Error("Connection refused");
70+
71+
await handleError(error, {}, []);
72+
73+
expect(std.warn).not.toContain(
74+
"Wrangler detected that a corporate proxy or VPN might be enabled"
75+
);
76+
});
77+
78+
it("should still log the original error after the warning", async () => {
79+
const error = new Error("self-signed certificate in certificate chain");
80+
81+
await handleError(error, {}, []);
82+
83+
// The warning should appear
84+
expect(std.warn).toContain(
85+
"Wrangler detected that a corporate proxy or VPN might be enabled"
86+
);
87+
// The original error should also be logged
88+
expect(std.err).toContain("self-signed certificate in certificate chain");
89+
});
90+
});
91+
});

packages/wrangler/src/core/handle-errors.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,36 @@ import { logPossibleBugMessage } from "../utils/logPossibleBugMessage";
2727
import type { ReadConfigCommandArgs } from "../config";
2828
import type { ComplianceConfig } from "@cloudflare/workers-utils";
2929

30+
/**
31+
* SSL/TLS certificate error messages that indicate a corporate proxy or VPN
32+
* may be intercepting HTTPS traffic with a custom root certificate.
33+
*/
34+
const SSL_ERROR_SELF_SIGNED_CERT =
35+
"self-signed certificate in certificate chain";
36+
const SSL_ERROR_UNABLE_TO_VERIFY = "unable to verify the first certificate";
37+
const SSL_ERROR_UNABLE_TO_GET_ISSUER = "unable to get local issuer certificate";
38+
39+
const SSL_CERT_ERROR_MESSAGES = [
40+
SSL_ERROR_SELF_SIGNED_CERT,
41+
SSL_ERROR_UNABLE_TO_VERIFY,
42+
SSL_ERROR_UNABLE_TO_GET_ISSUER,
43+
];
44+
45+
/**
46+
* Check if an error (or its cause) is related to SSL/TLS certificate validation,
47+
* which commonly occurs when a corporate proxy or VPN intercepts HTTPS traffic.
48+
*/
49+
function isCertificateError(e: unknown): boolean {
50+
const message = e instanceof Error ? e.message : String(e);
51+
const causeMessage =
52+
e instanceof Error && e.cause instanceof Error ? e.cause.message : "";
53+
54+
return SSL_CERT_ERROR_MESSAGES.some(
55+
(errorText) =>
56+
message.includes(errorText) || causeMessage.includes(errorText)
57+
);
58+
}
59+
3060
/**
3161
* Handles an error thrown during command execution.
3262
*
@@ -42,6 +72,16 @@ export async function handleError(
4272
let loggableException = e;
4373

4474
logger.log(""); // Just adds a bit of space
75+
76+
// Log a helpful warning for SSL certificate errors caused by corporate proxies/VPNs
77+
if (isCertificateError(e)) {
78+
logger.warn(
79+
"Wrangler detected that a corporate proxy or VPN might be enabled on your machine, " +
80+
"resulting in API calls failing due to a certificate mismatch. " +
81+
"It is likely that you need to install the missing system roots provided by your corporate proxy vendor."
82+
);
83+
}
84+
4585
if (e instanceof CommandLineArgsError) {
4686
logger.error(e.message);
4787
// We are not able to ask the `wrangler` CLI parser to show help for a subcommand programmatically.

0 commit comments

Comments
 (0)