diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b3b1f367d..715c53935 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -51,7 +51,7 @@ "semver": "^7.8.5", "ws": "^8.21.0", "yaml": "^2.8.3", - "zod": "^4.3.5" + "zod": "^4.6.6" }, "bin": { "agentcore": "dist/cli/index.mjs" @@ -115,15 +115,6 @@ "zod": "^3.22.4" } }, - "node_modules/@ag-ui/core/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", @@ -9227,9 +9218,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "funding": [ { "type": "github", @@ -16618,9 +16609,9 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 4427d38a9..07eb35af9 100644 --- a/package.json +++ b/package.json @@ -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" @@ -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", @@ -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" }, diff --git a/scripts/security-audit.lib.mjs b/scripts/security-audit.lib.mjs index 7866c8f5d..6b25c3de0 100644 --- a/scripts/security-audit.lib.mjs +++ b/scripts/security-audit.lib.mjs @@ -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]; @@ -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 = []; @@ -96,7 +90,7 @@ 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.` @@ -104,7 +98,7 @@ export function evaluateAuditReport(report, { status, now = new Date() }) { } 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.` diff --git a/scripts/security-audit.test.mjs b/scripts/security-audit.test.mjs index c43c98cf4..04af35da6 100644 --- a/scripts/security-audit.test.mjs +++ b/scripts/security-audit.test.mjs @@ -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', @@ -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); @@ -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); @@ -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); @@ -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); @@ -101,6 +112,7 @@ 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); @@ -108,7 +120,7 @@ test('expires the braces exception only when the audit still relies on it', () = }); 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/); @@ -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);