All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 18m58s
Resolves 7 npm audit findings (3 moderate, 4 high) via `npm audit fix`
— no --force needed, all bumps satisfied by existing semver ranges:
basic-ftp 5.2.0 -> 5.2.2 (high: FTP command injection via CRLF)
brace-expansion 1.1.12 -> 1.1.13 (moderate: ReDoS / mem exhaustion)
nodemailer 8.0.3 -> 8.0.5 (high: SMTP command injection via
CRLF in EHLO/HELO transport name,
GHSA-vvjj-xcjg-gr5g, and envelope.size
injection GHSA-c7w3-x93f-qmm8)
path-to-regexp 8.3.0 -> 8.4.2 (high: ReDoS, GHSA-j3q9-mxjg-w52f and
GHSA-27v5-c462-wpq7)
picomatch 4.0.3 -> 4.0.4 (high: method injection + ReDoS)
vite 0.115.0 -> 0.124.0 (high: path traversal / FS bypass,
dev-only, transitive via vitest)
yaml 2.x -> patched (moderate: stack overflow, dev-only)
Only package-lock.json changed — no source changes required, no API
breaks. nodemailer 8.0.5 is fully backwards-compatible with our usage
in src/services/email.ts.
Adds src/__tests__/no-vulnerable-deps.test.ts as a TDD regression guard:
runs `npm audit --omit=dev --json` and asserts
metadata.vulnerabilities.high === 0 && critical === 0. Network failures
are skipped rather than failing CI. Red→Green verified locally (stashed
lockfile -> 2 high failures; restored -> 0).
Test count: 901 -> 902 (new regression guard). npm audit: 4 high -> 0.
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
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,
|
|
);
|
|
});
|