106 lines
4.3 KiB
JavaScript
106 lines
4.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Regression test for #3588 — production dependency tree must not carry
|
|
* high or moderate npm-audit advisories.
|
|
*
|
|
* Strategy: run `npm audit --omit=dev --json` against both the root
|
|
* workspace and the embedded SDK package and assert that the metadata
|
|
* vulnerability counts are zero across info/low/moderate/high/critical.
|
|
*
|
|
* The test is intentionally strict — any advisory of any severity (other
|
|
* than 'low' if the maintainer accepts it; that branch is left explicit
|
|
* here) blocks CI. If a future advisory lands without an upstream patch,
|
|
* either bump the patched transitive (preferred), or annotate the
|
|
* acceptance below with a justification AND a link to the upstream tracker.
|
|
*
|
|
* Skips automatically when `node_modules/` is absent (a fresh checkout
|
|
* before `npm install`) so the test does not falsely report on developer
|
|
* machines mid-setup.
|
|
*/
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
const fs = require('node:fs');
|
|
const { execFileSync } = require('node:child_process');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const SDK = path.join(ROOT, 'sdk');
|
|
|
|
function auditProductionVulns(cwd) {
|
|
if (!fs.existsSync(path.join(cwd, 'node_modules'))) {
|
|
return null; // signal "skip" to caller
|
|
}
|
|
const isWindows = process.platform === 'win32';
|
|
const npmCandidates = isWindows ? ['npm.cmd', 'npm'] : ['npm'];
|
|
const args = ['audit', '--omit=dev', '--json'];
|
|
let out;
|
|
let lastErr = null;
|
|
for (const npmCmd of npmCandidates) {
|
|
try {
|
|
out = execFileSync(
|
|
npmCmd,
|
|
args,
|
|
{
|
|
cwd,
|
|
encoding: 'utf-8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
timeout: 60_000,
|
|
shell: isWindows,
|
|
}
|
|
);
|
|
lastErr = null;
|
|
break;
|
|
} catch (e) {
|
|
// `npm audit` exits non-zero when advisories are present; the JSON is
|
|
// still on stdout in that case. Recover and let the assertion classify.
|
|
if (e && typeof e.stdout !== 'undefined' && e.stdout !== undefined && e.stdout !== null) {
|
|
out = Buffer.isBuffer(e.stdout) ? e.stdout.toString('utf-8') : String(e.stdout);
|
|
lastErr = null;
|
|
break;
|
|
}
|
|
lastErr = e;
|
|
}
|
|
}
|
|
if (lastErr) throw lastErr;
|
|
const parsed = JSON.parse(out);
|
|
// `null` is reserved for the "node_modules missing → skip" signal above.
|
|
// Any other unexpected JSON shape is a real failure of the audit harness
|
|
// (npm changed its output format, audit aborted before metadata, etc.) —
|
|
// throw so the test fails loudly instead of skipping silently.
|
|
if (parsed && parsed.metadata && parsed.metadata.vulnerabilities) {
|
|
return parsed.metadata.vulnerabilities;
|
|
}
|
|
throw new Error(`Unexpected npm audit JSON shape in ${cwd}: missing metadata.vulnerabilities`);
|
|
}
|
|
|
|
describe('#3588: npm audit --omit=dev reports zero advisories', () => {
|
|
test('root workspace production tree has no advisories', { timeout: 90_000 }, (t) => {
|
|
const vulns = auditProductionVulns(ROOT);
|
|
if (vulns === null) {
|
|
t.skip('node_modules/ not present — run `npm install` before this test');
|
|
return;
|
|
}
|
|
assert.strictEqual(vulns.critical, 0, `expected 0 critical; got ${vulns.critical}`);
|
|
assert.strictEqual(vulns.high, 0, `expected 0 high; got ${vulns.high}`);
|
|
assert.strictEqual(vulns.moderate, 0, `expected 0 moderate; got ${vulns.moderate}`);
|
|
// Low advisories are not explicitly forbidden by the #3588 acceptance
|
|
// criterion but the issue listed only high/moderate as actual findings —
|
|
// tighten if any future low advisory is introduced.
|
|
assert.strictEqual(vulns.low, 0, `expected 0 low; got ${vulns.low}`);
|
|
});
|
|
|
|
test('sdk/ production tree has no advisories', { timeout: 90_000 }, (t) => {
|
|
const vulns = auditProductionVulns(SDK);
|
|
if (vulns === null) {
|
|
t.skip('sdk/node_modules/ not present — run `npm ci` inside sdk/ before this test');
|
|
return;
|
|
}
|
|
assert.strictEqual(vulns.critical, 0, `expected 0 critical; got ${vulns.critical}`);
|
|
assert.strictEqual(vulns.high, 0, `expected 0 high; got ${vulns.high}`);
|
|
assert.strictEqual(vulns.moderate, 0, `expected 0 moderate; got ${vulns.moderate}`);
|
|
assert.strictEqual(vulns.low, 0, `expected 0 low; got ${vulns.low}`);
|
|
});
|
|
});
|