Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
"semver": "^7.8.5",
"ws": "^8.21.0",
"yaml": "^2.8.3",
"zod": "^4.3.5"
"zod": "^4.6.6"
},
"optionalDependencies": {
"@napi-rs/keyring": "^1.3.0"
Expand Down Expand Up @@ -174,7 +174,9 @@
"@aws-sdk/xml-builder": "aws/aws-sdk-js-v3#7867: @aws-sdk/xml-builder <3.972.14 does not configure maxTotalExpansions on fast-xml-parser, causing 'Entity expansion limit exceeded' on large CloudFormation responses. Remove once @aws-sdk/client-* deps are bumped past 3.972.14.",
"@opentelemetry/core": "GHSA-8988-4f7v-96qf: @opentelemetry/core <2.8.0 has unbounded memory allocation in W3C Baggage propagation. Transitive via @opentelemetry/otlp-transformer. Remove once @opentelemetry/otlp-transformer is upgraded past 0.219.0.",
"@opentelemetry/otlp-transformer": "Pinned to 0.213.0 — the last version that ships generated protobuf types for trace, logs, and metrics (used by the dev-mode OTLP collector). Versions 0.214–0.217 drop logs/trace protos; 0.218+ removes the generated root entirely. Remove this pin once the collector is refactored to not depend on the internal generated/root path.",
"protobufjs": "Multiple high-severity CVEs in protobufjs <8.7.0 (code injection, DoS, prototype pollution). Transitive via @opentelemetry/otlp-transformer@0.213.0. Remove once @opentelemetry/otlp-transformer is upgraded past 0.217.0."
"protobufjs": "Multiple high-severity CVEs in protobufjs <8.7.0 (code injection, DoS, prototype pollution). Transitive via @opentelemetry/otlp-transformer@0.213.0. Remove once @opentelemetry/otlp-transformer is upgraded past 0.217.0.",
"fast-uri": "GHSA-w74x-v6f9-j3xg, GHSA-xhq7-xmqj-769h, GHSA-wjr9-j795-jxcr, GHSA-333x-w4r6-6crp: fast-uri <3.1.9 has URI scheme spoofing, host confusion, and path normalization vulnerabilities. Transitive via ajv. Remove once ajv requires fast-uri >=3.1.9.",
"zod": "GHSA-mrhx-c7xc-m3w5: zod <4.6.6 can exhaust memory validating oversized constrained collections, and zod 3 has no patched release. @aws/agent-inspector depends on @ag-ui/core@^0.0.52, which pins zod@^3, but agent-inspector only ships a prebuilt browser bundle, so that copy is never loaded at runtime. Remove once @aws/agent-inspector depends on an @ag-ui/core release that accepts zod 4."
},
"overrides": {
"minimatch": "10.2.4",
Expand All @@ -183,7 +185,8 @@
"@aws-sdk/xml-builder": "3.972.15",
"@opentelemetry/core": ">=2.8.0",
"protobufjs": ">=8.7.0",
"fast-uri": "^3.1.8",
"fast-uri": "^3.1.9",
"zod": "$zod",
"brace-expansion": "^5.0.12",
"@humanfs/node": "^0.16.8"
},
Expand Down
18 changes: 6 additions & 12 deletions scripts/security-audit.lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@ const SEVERITY_RANK = {
critical: 4,
};

export const TEMPORARY_AUDIT_EXCEPTIONS = {
'GHSA-vfj7-8cjw-p6xm': {
expiresAt: '2026-11-05T00:00:00.000Z',
reason:
'braces@3.0.3 is the latest release and has no patched version; it is pulled in through @aws-cdk/toolkit-lib.',
},
};
export const TEMPORARY_AUDIT_EXCEPTIONS = {};

function advisoryId(url) {
return url?.match(/GHSA-[\w-]+$/)?.[0];
Expand Down Expand Up @@ -67,14 +61,14 @@ export function auditAdvisories(report) {
);
}

export function evaluateAuditReport(report, { status, now = new Date() }) {
export function evaluateAuditReport(report, { status, now = new Date(), exceptions = TEMPORARY_AUDIT_EXCEPTIONS }) {
const advisories = auditAdvisories(report);
const blockingSeverityAdvisories = advisories.filter(advisory => isBlockingSeverity(advisory.severity));
const allowed = uniqueAdvisories(
blockingSeverityAdvisories.filter(advisory => advisory.id && advisory.id in TEMPORARY_AUDIT_EXCEPTIONS)
blockingSeverityAdvisories.filter(advisory => advisory.id && advisory.id in exceptions)
);
const blocked = uniqueAdvisories(
blockingSeverityAdvisories.filter(advisory => !advisory.id || !(advisory.id in TEMPORARY_AUDIT_EXCEPTIONS))
blockingSeverityAdvisories.filter(advisory => !advisory.id || !(advisory.id in exceptions))
);
const errors = [];

Expand All @@ -96,15 +90,15 @@ export function evaluateAuditReport(report, { status, now = new Date() }) {
}

const allowedIds = new Set(allowed.map(advisory => advisory.id));
const unusedExceptions = Object.keys(TEMPORARY_AUDIT_EXCEPTIONS).filter(id => !allowedIds.has(id));
const unusedExceptions = Object.keys(exceptions).filter(id => !allowedIds.has(id));
if (unusedExceptions.length > 0) {
errors.push(
`Temporary npm audit exception(s) are no longer required: ${unusedExceptions.join(', ')}. Remove them.`
);
}

for (const advisory of allowed) {
const exception = TEMPORARY_AUDIT_EXCEPTIONS[advisory.id];
const exception = exceptions[advisory.id];
if (now >= new Date(exception.expiresAt)) {
errors.push(
`Temporary npm audit exception expired: ${advisory.id}. Recheck for patched releases and remove the exception.`
Expand Down
15 changes: 14 additions & 1 deletion scripts/security-audit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ function auditReport(advisories, fixAvailable = false) {
};
}

const exceptions = {
'GHSA-vfj7-8cjw-p6xm': {
expiresAt: '2026-11-05T00:00:00.000Z',
reason: 'braces@3.0.3 is the latest release and has no patched version.',
},
};

const bracesAdvisory = {
name: 'braces',
severity: 'high',
Expand All @@ -29,6 +36,7 @@ test('allows the time-boxed braces advisory', () => {
const decision = evaluateAuditReport(report, {
status: 1,
now: new Date('2026-11-04T23:59:59.999Z'),
exceptions,
});

assert.equal(decision.ok, true);
Expand All @@ -52,6 +60,7 @@ test('does not allow other high-severity advisories', () => {
const decision = evaluateAuditReport(report, {
status: 1,
now: new Date('2026-11-04T23:59:59.999Z'),
exceptions,
});

assert.equal(decision.ok, false);
Expand All @@ -75,6 +84,7 @@ for (const severity of [undefined, 'severe']) {
const decision = evaluateAuditReport(report, {
status: 1,
now: new Date('2026-11-04T23:59:59.999Z'),
exceptions,
});

assert.equal(decision.ok, false);
Expand All @@ -90,6 +100,7 @@ test('rejects the braces exception when a non-breaking fix is available', () =>
const decision = evaluateAuditReport(auditReport([bracesAdvisory], fixAvailable), {
status: 1,
now: new Date('2026-11-04T23:59:59.999Z'),
exceptions,
});

assert.equal(decision.ok, false);
Expand All @@ -101,14 +112,15 @@ test('expires the braces exception only when the audit still relies on it', () =
const decision = evaluateAuditReport(auditReport([bracesAdvisory]), {
status: 1,
now: new Date('2026-11-05T00:00:00.000Z'),
exceptions,
});

assert.equal(decision.ok, false);
assert.match(decision.errors.join('\n'), /exception expired/);
});

test('requires stale exceptions to be removed', () => {
const decision = evaluateAuditReport({ vulnerabilities: {} }, { status: 0 });
const decision = evaluateAuditReport({ vulnerabilities: {} }, { status: 0, exceptions });

assert.equal(decision.ok, false);
assert.match(decision.errors.join('\n'), /no longer required/);
Expand All @@ -118,6 +130,7 @@ test('fails closed when npm audit returns an unexpected status', () => {
const decision = evaluateAuditReport(auditReport([bracesAdvisory]), {
status: 2,
now: new Date('2026-11-04T23:59:59.999Z'),
exceptions,
});

assert.equal(decision.ok, false);
Expand Down
Loading