diff --git a/changelog.d/fixed-523-npm-audit-gate-package-scope.md b/changelog.d/fixed-523-npm-audit-gate-package-scope.md new file mode 100644 index 00000000..c50c1f08 --- /dev/null +++ b/changelog.d/fixed-523-npm-audit-gate-package-scope.md @@ -0,0 +1,3 @@ +- The npm audit gate documents the optional `package` exception field, treats an + empty `package` as unscoped, and names the package in unused-exception + warnings (hivecommons/docs#523, #524, #525). diff --git a/scripts/npm-audit-gate.test.ts b/scripts/npm-audit-gate.test.ts index 6871937e..61d96a37 100644 --- a/scripts/npm-audit-gate.test.ts +++ b/scripts/npm-audit-gate.test.ts @@ -184,6 +184,12 @@ describe("evaluateAudit", () => { expect(r.blocking).toEqual([]); }); + it("treats an empty package as an unscoped exception", () => { + const r = evaluateAudit(REPORT, [exception({ package: "" })], "high", NOW); + expect(r.blocking).toEqual([]); + expect(r.unused).toEqual([]); + }); + it("does not apply an exception scoped to a different package", () => { const r = evaluateAudit( REPORT, @@ -416,7 +422,9 @@ describe("main", () => { expect(exitSpy).not.toHaveBeenCalled(); const out = logSpy.mock.calls.flat().join("\n"); expect(out).toMatch(/Acknowledged 1 advisory/); - expect(out).toMatch(/::warning::Unused npm audit exception GHSA-zzzz/); + expect(out).toMatch( + /::warning::Unused npm audit exception GHSA-zzzz-zzzz-zzzz \(nope\)/ + ); expect(out).toMatch(/Ignoring 1 advisory/); expect(out).toMatch(/passed at level "high"/); }); diff --git a/scripts/npm-audit-gate.ts b/scripts/npm-audit-gate.ts index 93e5740a..4ba071db 100644 --- a/scripts/npm-audit-gate.ts +++ b/scripts/npm-audit-gate.ts @@ -12,6 +12,11 @@ * expired exception fails the gate so acknowledged findings are re-reviewed * rather than silently forgotten. * + * An exception may also carry an optional `package` that scopes it to one + * affected package, so one GHSA can be listed once per package. Without + * `package` (or with an empty one) the exception covers that GHSA for every + * package. + * * A file that is not an audit report (e.g. the error document npm writes when * the registry is unreachable) fails the gate rather than passing it. * @@ -124,7 +129,7 @@ export interface GateResult { } function exceptionKey(id: string, pkg?: string): string { - return `${id.toUpperCase()}:${pkg ?? "*"}`; + return `${id.toUpperCase()}:${pkg || "*"}`; } export function evaluateAudit( @@ -240,7 +245,9 @@ export function main(argv: string[] = process.argv.slice(2)): void { ); } for (const ex of result.unused) { - console.log(`::warning::Unused npm audit exception ${ex.id} — remove it.`); + console.log( + `::warning::Unused npm audit exception ${ex.id} (${ex.package || "*"}) — remove it.` + ); } let failed = false;