Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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";
Expand Down Expand Up @@ -301,10 +301,14 @@ if (parsedArgs) {
main().catch((error) => {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(chalk.red(`Error: ${errorMessage}`));
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`"));
if (isSslCertificateError(errorMessage)) {
const [hint, ...rest] = sslCertificateErrorHint();
console.error(chalk.yellow(hint));
rest.forEach(line => console.error(chalk.gray(line)));
} else if (isLikelyBlockedAdvisoryRequestError(errorMessage)) {
const [hint, ...rest] = blockedAdvisoryRequestHint();
console.error(chalk.yellow(hint));
rest.forEach(line => console.error(chalk.gray(line)));
}
process.exit(1);
});
Expand Down Expand Up @@ -358,7 +362,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 {
Expand Down
33 changes: 33 additions & 0 deletions src/utils/network.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
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 [
"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;
Expand Down
28 changes: 27 additions & 1 deletion tests/network.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down