import { describe, it, expect } from "vitest"; import { execSync } from "node:child_process"; /** * Regression guard for npm audit vulnerabilities. * * Runs `npm audit --omit=dev --json` and asserts that no high or critical * severity vulnerabilities exist in the production dependency tree. * * Rationale: after fixing CVEs (nodemailer CRLF GHSA-vvjj-xcjg-gr5g, * path-to-regexp ReDoS GHSA-j3q9-mxjg-w52f / GHSA-27v5-c462-wpq7, and * related transitive issues in basic-ftp, brace-expansion, picomatch), * we want CI to fail fast if a new high/critical vuln is introduced into * the production dependency graph rather than discovering it later. * * Network access is required (audit hits the npm registry). If the * registry is unreachable in a given environment, the test is skipped * rather than reported as a failure — we don't want flaky network to * break builds. All other errors (including the audit finding vulns) * must fail loudly. */ describe("npm audit regression guard", () => { it( "has zero high or critical vulnerabilities in production dependencies", () => { let stdout: string; try { // `npm audit` exits non-zero when vulnerabilities are found, which // makes execSync throw. We still want to parse stdout in that case, // so we catch and inspect the error object. stdout = execSync("npm audit --omit=dev --json", { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 60_000, }); } catch (err: unknown) { const e = err as { stdout?: Buffer | string; stderr?: Buffer | string; message?: string }; const out = typeof e.stdout === "string" ? e.stdout : e.stdout?.toString() ?? ""; const errOut = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString() ?? ""; // No JSON at all? Likely a network/registry failure — skip, don't fail CI. if (!out || !out.trim().startsWith("{")) { const msg = (errOut || e.message || "").toLowerCase(); if ( msg.includes("enotfound") || msg.includes("etimedout") || msg.includes("econnrefused") || msg.includes("network") || msg.includes("registry") ) { console.warn("npm audit: registry unreachable, skipping regression guard"); return; } throw new Error( `npm audit did not return parseable JSON. stderr=${errOut} message=${e.message}`, ); } stdout = out; } const report = JSON.parse(stdout) as { metadata?: { vulnerabilities?: { info?: number; low?: number; moderate?: number; high?: number; critical?: number; total?: number; }; }; }; const vulns = report.metadata?.vulnerabilities; expect(vulns, "npm audit JSON missing metadata.vulnerabilities").toBeDefined(); const high = vulns?.high ?? 0; const critical = vulns?.critical ?? 0; if (high > 0 || critical > 0) { // Surface the full report so failure output is actionable. console.error("npm audit found high/critical vulnerabilities:"); console.error(JSON.stringify(vulns, null, 2)); } expect(critical).toBe(0); expect(high).toBe(0); }, 90_000, ); });