diff --git a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json index 5c2278190e..7b64ea54a8 100644 --- a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json +++ b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json @@ -7757,15 +7757,16 @@ } }, "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", + "destroy": "1.2.0", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", @@ -8892,6 +8893,17 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -14856,9 +14868,9 @@ } }, "node_modules/markdown-it": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", - "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "version": "14.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.2.tgz", + "integrity": "sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q==", "dev": true, "funding": [ { @@ -16427,9 +16439,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -17877,9 +17889,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" diff --git a/package-lock.json b/package-lock.json index 5c2278190e..7b64ea54a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7757,15 +7757,16 @@ } }, "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", + "destroy": "1.2.0", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", @@ -8892,6 +8893,17 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -14856,9 +14868,9 @@ } }, "node_modules/markdown-it": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", - "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "version": "14.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.2.tgz", + "integrity": "sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q==", "dev": true, "funding": [ { @@ -16427,9 +16439,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -17877,9 +17889,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" diff --git a/scripts/check-ui-npm-audit.js b/scripts/check-ui-npm-audit.js index 4d29313e07..93c21fe2ae 100644 --- a/scripts/check-ui-npm-audit.js +++ b/scripts/check-ui-npm-audit.js @@ -180,18 +180,28 @@ function evaluateAudit({ audit, lock, pending, now = new Date(), npmExitCode }) visiting.add(name); const v = vulnerabilities[name]; need(v.severity === 'high' && v.nodes.every(n => pinned.includes(n)), 'UNREVIEWED_HIGH_NODE'); + let reviewedHighCause = false; for (const via of v.via) { if (typeof via === 'string') { need(v.nodes.every(from => { const spec = { ...packages[from].dependencies, ...packages[from].optionalDependencies }[via]; return typeof spec === 'string' && vulnerabilities[via].nodes.includes(resolveDependency(packages, from, via)); }), 'METAVULNERABILITY_LOCK_EDGE_MISMATCH'); - knownClosure(via); + // npm meta packages can have separate lower-severity branches. + // Their shape, severity and actual lock edge are still checked, + // but they are neither a High exception nor evidence for one. + if (vulnerabilities[via].severity === 'high') { + knownClosure(via); + reviewedHighCause = true; + } } else { + if (LEVELS.indexOf(via.severity) < LEVELS.indexOf('high')) continue; need(name === 'braces' && via.name === 'braces' && via.dependency === 'braces' && via.severity === 'high' && via.url === ADVISORY && via.range === '<=3.0.3', 'UNREVIEWED_DIRECT_ADVISORY'); + reviewedHighCause = true; } } + need(reviewedHighCause, 'HIGH_WITHOUT_REVIEWED_CAUSE'); visiting.delete(name); verified.add(name); } diff --git a/scripts/test-ui-npm-audit.js b/scripts/test-ui-npm-audit.js index 868ace7302..ad6b900a1d 100644 --- a/scripts/test-ui-npm-audit.js +++ b/scripts/test-ui-npm-audit.js @@ -69,6 +69,52 @@ test('exact actual seven-node npm12 closure passes and keeps raw High plus separ assert.equal(result.knownPending.metavulnerabilityCount, 6); assert.equal(result.knownPending.upstreamPatchedVersion, null); }); +function mixedSeverityInput() { + const value = input(); + value.audit.vulnerabilities['underscore.string'] = { + name: 'underscore.string', severity: 'moderate', isDirect: false, + via: [{ source: 99, name: 'underscore.string', dependency: 'underscore.string', title: 'Separate Moderate fixture', + url: 'https://github.com/advisories/GHSA-aaaa-bbbb-cccc', severity: 'moderate', cwe: ['CWE-400'], + cvss: { score: 5, vectorString: null }, range: '*' }], + effects: ['broccoli'], range: '*', nodes: ['node_modules/underscore.string'], fixAvailable: false + }; + value.audit.vulnerabilities.broccoli.via.push('underscore.string'); + value.audit.metadata.vulnerabilities.moderate++; + value.audit.metadata.vulnerabilities.total++; + return value; +} +test('reviewed High meta retains independent Moderate branches without expanding the High exception', () => { + const result = evaluateAudit(mixedSeverityInput()); + assert.equal(result.ok, true); + assert.equal(result.outcome, 'PASS_BUILD_VENDOR_PENDING'); + assert.equal(result.totals.high, 7); + assert.equal(result.totals.moderate, 1); + assert.equal(result.knownPending.metavulnerabilityCount, 6); +}); +test('mixed-severity branches still require actual lock edges and reject a newly promoted High', () => { + const edge = mixedSeverityInput(); + edge.audit.vulnerabilities.sane.via.push('underscore.string'); + fail(edge, 'METAVULNERABILITY_LOCK_EDGE_MISMATCH'); + const promoted = mixedSeverityInput(); + promoted.audit.vulnerabilities['underscore.string'].severity = 'high'; + promoted.audit.metadata.vulnerabilities.moderate--; + promoted.audit.metadata.vulnerabilities.high++; + fail(promoted, 'UNREVIEWED_HIGH_NODE'); +}); +test('lower-severity paths alone cannot manufacture a reviewed High cause', () => { + const value = mixedSeverityInput(); + value.audit.vulnerabilities.broccoli.via = ['underscore.string']; + fail(value, 'HIGH_WITHOUT_REVIEWED_CAUSE'); +}); +test('a direct Moderate on a reviewed node stays raw Moderate, while a new direct High is refused', () => { + const value = input(); + const moderate = { ...value.audit.vulnerabilities.braces.via[0], source: 99, + url: 'https://github.com/advisories/GHSA-aaaa-bbbb-cccc', severity: 'moderate' }; + value.audit.vulnerabilities.braces.via.push(moderate); + assert.equal(evaluateAudit(value).ok, true); + moderate.severity = 'high'; + fail(value, 'UNREVIEWED_DIRECT_ADVISORY'); +}); test('unknown High or extra direct advisory cannot borrow the known package name', () => { const value = input(); value.audit.vulnerabilities.braces.via[0].url = 'https://github.com/advisories/GHSA-aaaa-bbbb-cccc';