From f49897f553dbb53a6cb95f9d347fe494e52762d6 Mon Sep 17 00:00:00 2001 From: Sonu Kapoor Date: Fri, 22 May 2026 16:33:05 -0400 Subject: [PATCH 1/2] fix: show actionable SSL certificate error message for corporate proxy failures --- src/index.ts | 12 +++++++++--- src/utils/network.ts | 15 +++++++++++++++ tests/network.test.ts | 28 +++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index fc03f54..fce6aaf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ import { createSpinner } from "./output/spinner.js"; import { buildSuggestedFixCommandPlan } from "./remediation/fix-commands.js"; import { scanProjectForPackageUsage } from "./usage/scanner.js"; import { getCliVersion } from "./utils/version-info.js"; -import { isLikelyBlockedAdvisoryRequestError } from "./utils/network.js"; +import { isLikelyBlockedAdvisoryRequestError, isSslCertificateError } from "./utils/network.js"; import type { SuggestedFixCommandPlan, SuggestedFixTarget } from "./remediation/fix-commands.js"; import type { ParsedOptions } from "./types.js"; import type { Finding, SeverityLabel } from "./types.js"; @@ -301,7 +301,13 @@ if (parsedArgs) { main().catch((error) => { const errorMessage = error instanceof Error ? error.message : String(error); console.error(chalk.red(`Error: ${errorMessage}`)); - if (isLikelyBlockedAdvisoryRequestError(errorMessage)) { + if (isSslCertificateError(errorMessage)) { + console.error(chalk.yellow("Hint: SSL certificate error — your network may be using a corporate proxy that intercepts HTTPS traffic.")); + console.error(chalk.gray("Fix 1 (recommended): trust your corporate CA certificate:")); + console.error(chalk.gray(" NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt cve-lite .")); + console.error(chalk.gray("Fix 2 (quick workaround, not recommended for production): disable TLS verification:")); + console.error(chalk.gray(" NODE_TLS_REJECT_UNAUTHORIZED=0 cve-lite .")); + } else if (isLikelyBlockedAdvisoryRequestError(errorMessage)) { console.error(chalk.yellow("Hint: Outbound access to the OSV API may be blocked or restricted in this environment.")); console.error(chalk.gray("If that is expected, build the advisory DB on a machine with OSV access, then scan here with `--offline` or `--offline-db /path/to/advisories.db`.")); console.error(chalk.gray("Command to build the DB on a network-allowed machine: `cve-lite advisories sync --output /path/to/advisories.db`")); @@ -358,7 +364,7 @@ async function scanProject(params: { const minSeverity = normalizeSeverity(params.options.minSeverity || "medium"); const tableFindings = params.options.all ? sorted - : sorted.filter(f => severityOrder[f.severity] >= severityOrder[minSeverity]); + : sorted.filter(f => severityOrder[f.severity] >= severityOrder[minSeverity] || f.severity === "unknown"); const suggestedFixCommands = buildSuggestedFixCommandPlan(sorted, params.scanInput, { offline }); return { diff --git a/src/utils/network.ts b/src/utils/network.ts index 278ba4d..390588b 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -1,3 +1,18 @@ +export function isSslCertificateError(message: string): boolean { + const normalized = message.toLowerCase(); + return [ + "self-signed certificate", + "self_signed_cert_in_chain", + "cert_untrusted", + "unable_to_verify_leaf_signature", + "depth_zero_self_signed_cert", + "certificate has expired", + "cert_has_expired", + "unable to verify the first certificate", + "certificate chain", + ].some(indicator => normalized.includes(indicator)); +} + export function isLikelyBlockedAdvisoryRequestError(message: string): boolean { if (!message.includes("OSV")) { return false; diff --git a/tests/network.test.ts b/tests/network.test.ts index 4a672ac..2698419 100644 --- a/tests/network.test.ts +++ b/tests/network.test.ts @@ -1,4 +1,30 @@ -import { isLikelyBlockedAdvisoryRequestError } from "../src/utils/network.js"; +import { isLikelyBlockedAdvisoryRequestError, isSslCertificateError } from "../src/utils/network.js"; + +describe("isSslCertificateError", () => { + it("returns true for self-signed certificate errors", () => { + expect(isSslCertificateError("OSV batch query failed: self-signed certificate in certificate chain")).toBe(true); + expect(isSslCertificateError("fetch failed: self_signed_cert_in_chain")).toBe(true); + expect(isSslCertificateError("SELF_SIGNED_CERT_IN_CHAIN")).toBe(true); + }); + + it("returns true for untrusted certificate errors", () => { + expect(isSslCertificateError("cert_untrusted")).toBe(true); + expect(isSslCertificateError("unable_to_verify_leaf_signature")).toBe(true); + expect(isSslCertificateError("depth_zero_self_signed_cert")).toBe(true); + expect(isSslCertificateError("unable to verify the first certificate")).toBe(true); + }); + + it("returns true for expired certificate errors", () => { + expect(isSslCertificateError("certificate has expired")).toBe(true); + expect(isSslCertificateError("cert_has_expired")).toBe(true); + }); + + it("returns false for unrelated network errors", () => { + expect(isSslCertificateError("ECONNREFUSED")).toBe(false); + expect(isSslCertificateError("fetch failed")).toBe(false); + expect(isSslCertificateError("403 Forbidden")).toBe(false); + }); +}); describe("isLikelyBlockedAdvisoryRequestError", () => { it("returns true for OSV failures that look like blocked or restricted network access", () => { From 189f6df9bd7b451d61d9eaf2ff60516424d7d859 Mon Sep 17 00:00:00 2001 From: Sonu Kapoor Date: Fri, 22 May 2026 16:38:46 -0400 Subject: [PATCH 2/2] refactor: move SSL and blocked-network hint text into network.ts --- src/index.ts | 16 +++++++--------- src/utils/network.ts | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index fce6aaf..a4bd7a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ import { createSpinner } from "./output/spinner.js"; import { buildSuggestedFixCommandPlan } from "./remediation/fix-commands.js"; import { scanProjectForPackageUsage } from "./usage/scanner.js"; import { getCliVersion } from "./utils/version-info.js"; -import { isLikelyBlockedAdvisoryRequestError, isSslCertificateError } from "./utils/network.js"; +import { isLikelyBlockedAdvisoryRequestError, isSslCertificateError, sslCertificateErrorHint, blockedAdvisoryRequestHint } from "./utils/network.js"; import type { SuggestedFixCommandPlan, SuggestedFixTarget } from "./remediation/fix-commands.js"; import type { ParsedOptions } from "./types.js"; import type { Finding, SeverityLabel } from "./types.js"; @@ -302,15 +302,13 @@ if (parsedArgs) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(chalk.red(`Error: ${errorMessage}`)); if (isSslCertificateError(errorMessage)) { - console.error(chalk.yellow("Hint: SSL certificate error — your network may be using a corporate proxy that intercepts HTTPS traffic.")); - console.error(chalk.gray("Fix 1 (recommended): trust your corporate CA certificate:")); - console.error(chalk.gray(" NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt cve-lite .")); - console.error(chalk.gray("Fix 2 (quick workaround, not recommended for production): disable TLS verification:")); - console.error(chalk.gray(" NODE_TLS_REJECT_UNAUTHORIZED=0 cve-lite .")); + const [hint, ...rest] = sslCertificateErrorHint(); + console.error(chalk.yellow(hint)); + rest.forEach(line => console.error(chalk.gray(line))); } else if (isLikelyBlockedAdvisoryRequestError(errorMessage)) { - console.error(chalk.yellow("Hint: Outbound access to the OSV API may be blocked or restricted in this environment.")); - console.error(chalk.gray("If that is expected, build the advisory DB on a machine with OSV access, then scan here with `--offline` or `--offline-db /path/to/advisories.db`.")); - console.error(chalk.gray("Command to build the DB on a network-allowed machine: `cve-lite advisories sync --output /path/to/advisories.db`")); + const [hint, ...rest] = blockedAdvisoryRequestHint(); + console.error(chalk.yellow(hint)); + rest.forEach(line => console.error(chalk.gray(line))); } process.exit(1); }); diff --git a/src/utils/network.ts b/src/utils/network.ts index 390588b..1a77c8b 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -1,3 +1,21 @@ +export function sslCertificateErrorHint(): string[] { + return [ + "Hint: SSL certificate error — your network may be using a corporate proxy that intercepts HTTPS traffic.", + "Fix 1 (recommended): trust your corporate CA certificate:", + " NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt cve-lite .", + "Fix 2 (quick workaround, not recommended for production): disable TLS verification:", + " NODE_TLS_REJECT_UNAUTHORIZED=0 cve-lite .", + ]; +} + +export function blockedAdvisoryRequestHint(): string[] { + return [ + "Hint: Outbound access to the OSV API may be blocked or restricted in this environment.", + "If that is expected, build the advisory DB on a machine with OSV access, then scan here with `--offline` or `--offline-db /path/to/advisories.db`.", + "Command to build the DB on a network-allowed machine: `cve-lite advisories sync --output /path/to/advisories.db`", + ]; +} + export function isSslCertificateError(message: string): boolean { const normalized = message.toLowerCase(); return [