Repository navigation
fix(security): merge per-range registry advisories and patch brace-expansion and undici - #1229
Conversation
The npm bulk advisory endpoint returns one entry per affected version range, so a single GHSA appears several times for one package under different registry ids and vulnerable_versions. For brace-expansion, GHSA-6j4f-fj2g-mc7p comes back as ids 1240108 (<1.1.19), 1240109 (>=2.0.0 <2.1.5) and 1240111 (>=4.0.0 <5.0.10), and the registry sends every entry whose range matches a requested version. strictProductionAuditFromBulkResponse keyed advisories by (GHSA, package) and threw on the second entry, so every CI run failed with "approved registry advisory brace-expansion[2] duplicates GHSA-6j4f-fj2g-mc7p" before the policy ever looked at severities. Entries for one (GHSA, package) now merge into a single audited advisory that carries the highest severity among its ranges, so an exception still has to match the worst installed range. The parser now tracks registry ids instead, and a repeated id still fails closed anywhere in the response: same GHSA, different GHSA, or another package. Every other malformed-input check is unchanged, and the audited list keeps its first-seen order, so the parsed audit document still passes parseProductionAudit's duplicate check. The new tests fail on main: per-range entries throw there, and a repeated id with a different GHSA or package was silently accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…isories With the parser fixed, the advisory gate reports three unapproved High production advisories published on 2026-09-29: GHSA-6j4f-fj2g-mc7p and GHSA-qhr7-859c-m2p7 in brace-expansion 2.1.4 and 5.0.9, and GHSA-rfgv-xxqx-mfg5 in undici 6.28.0. 2.1.7 and 5.0.12 also fix the Moderate GHSA-q2hr-2g5m-vwhr, and undici 6.28.1 also fixes the Moderate GHSA-3wwx-pv8p-q78v and the Low GHSA-r53p-7pc4-xj5r. The pnpm-installed brace-expansion copies come from @oclif/core through minimatch 10.2.5 (^5.0.5) and minimatch 5.1.9 (^2.0.1), whose ranges already admit the fixed versions. "pnpm update brace-expansion -r" moves the lockfile to 2.1.7 and 5.0.12 and changes nothing else. A pnpm override would also work, but workspace-engine-overrides.test.ts requires the overrides block to equal the Effect pin set. The operator npm's brace-expansion 5.0.9 and undici 6.28.0 live inside the npm 11.19.0 tarball, where overrides do not reach. No npm release bundles a fix yet: 11.19.1, 11.20.0, 12.1.0 and the open 11.20.1 release PR all ship brace-expansion 5.0.9 and undici 6.28.0. The lockfile-bound npm patch therefore now also carries the registry tarballs of brace-expansion 5.0.12 and undici 6.28.1 (integrity checked against registry metadata), limited to the files npm/cli's node_modules/.gitignore keeps. Applied to 5.0.9 and 6.28.0, that same filter reproduces npm's current bundle byte for byte. The installed tree matches those tarballs exactly. The ip-address, tar and npm package.json hunks are byte-identical to before. The patch was regenerated with "pnpm patch-commit" and normalized as before, with blank context lines stripped of their trailing space, and the lockfile hash was refreshed with a real install. The pinned operator npm closure moves to digest 4852a49e... (12,230,966 bytes; still 460 directories and 1,943 files). The runtime test pins the new bundled versions and checks that the comma-part input that exhausts 5.0.9's stack now expands. The pinned closure is only compared against a fresh per-process controller snapshot, so in-flight runs are unaffected. pnpm -w security:dependency-advisories now passes with 0 High/Critical production advisories and 0 exceptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| "ip-address": "10.5.0", | ||
| tar: "7.5.22", | ||
| undici: "6.28.0" | ||
| undici: "6.28.1" |
There was a problem hiding this comment.
Undici behavior lacks regression coverage The test checks that bundled undici is version 6.28.1, but it does not exercise undici’s changed behavior. The patch changes HTTP socket validation and retries, among other behavior, so the version check and closure digest would still pass if one of those paths regressed. A focused test of the advisory fix would help catch that before release.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/test/operator-npm.test.ts
Line: 34
Comment:
**Undici behavior lacks regression coverage** The test checks that bundled undici is version 6.28.1, but it does not exercise undici’s changed behavior. The patch changes HTTP socket validation and retries, among other behavior, so the version check and closure digest would still pass if one of those paths regressed. A focused test of the advisory fix would help catch that before release.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Keeping this as a version check. Two reasons:
- A regression can't pass silently.
bundledOperatorNpmClosureForValidationhashes the path and bytes of every file in npm's tree exceptnode_modules/.bin, and the first test in this file pins that digest (4852a49e…). Any change to a bundled undici file changes the digest and fails that test, not only a version bump. The bundled files are also byte-identical to the registry tarball of undici 6.28.1 (sha512 matches registry metadata) after npm's own bundle filter. - The fixed code isn't reachable from the operator npm. Inside npm, only
node-gyp/lib/download.jsrequires undici, forAgent,EnvHttpProxyAgent,RetryAgentandfetch. The High advisory (GHSA-rfgv-xxqx-mfg5) and GHSA-3wwx-pv8p-q78v are in undici's WebSocket client, which npm never opens. A behavioral test would exercise upstream code this package never runs.
brace-expansion got a behavioral check because minimatch reaches it on every glob npm expands, and a stack-exhausting input makes a cheap check.
There was a problem hiding this comment.
That’s a fair justification. Given the closure digest covers every bundled file’s path and contents, a bundled-undici change cannot pass silently just because the version assertion remains unchanged. Since npm’s bundled code does not exercise the advisory’s affected WebSocket path, a behavioral test here would not add meaningful coverage; the existing version, byte-level closure, and registry-integrity checks are appropriate. I’m withdrawing this finding—no additional test is needed.
Problem
CI on
main(a46a496) and on every open PR failsBuild gatesandPackage, dependency, and policy gatesatpnpm -w security:dependency-advisories:Nothing in the repo changed. The npm registry published new brace-expansion and undici advisories on 2026-09-28/29.
Root cause
Two separate problems:
idandvulnerable_versions. For our request (brace-expansion2.1.4 and 5.0.9), GHSA-6j4f-fj2g-mc7p comes back as ids 1240109 (>=2.0.0 <2.1.5) and 1240111 (>=4.0.0 <5.0.10).strictProductionAuditFromBulkResponsekeyed on (GHSA, package) and threw on the second entry.brace-expansion2.1.4 and 5.0.9. These come from@oclif/corethrough minimatch 5.1.9 / 10.2.5, and 5.0.9 is also bundled inside the operator npm.undici6.28.0, bundled inside the operator npm only.Change
fix(ci): merge per-range registry entries in the advisory gateidis rejected anywhere in the response: same GHSA, different GHSA, or another package. All other malformed-input checks are unchanged.scripts/ci/dependency-advisories.test.tscover per-range merging in both orders, no merging across packages, policy evaluation of a merged advisory with and without an exception, and three repeated-id cases. Both new tests fail onmain.fix(security): move brace-expansion and undici past the September advisoriespnpm update brace-expansion -rmoves 2.1.4 to 2.1.7 and 5.0.9 to 5.0.12, within the dependents' existing ranges (^2.0.1,^5.0.5). The lockfile diff is just those two bumps plus the patch hash.workspace-engine-overrides.test.tsrequires the overrides block to equal the Effect pin set exactly.npm@11.19.0patch, which is how the repo already handles this, to carrybrace-expansion5.0.12 andundici6.28.1:node_modules/.gitignore. Applied to 5.0.9 / 6.28.0, that filter reproduces npm's current bundle byte for byte.pnpm patch-commitand normalized the way the committed patch already was (blank context lines without a trailing space; see cf2926c in fix(security): close final AppSec review gaps #644). The ip-address, tar and npmpackage.jsonhunks are byte-identical tomain, so reviewing withgit diff -whides nothing.4852a49e…(12,230,966 bytes; still 460 dirs / 1,943 files).operator-npm.test.tspins the new bundled versions and checks that the comma-part input that overflows 5.0.9's stack now expands.docs/security.mdand CHANGELOG (Unreleased > Other changes) are updated.Verification
bun test scripts/ci/dependency-advisories.test.ts: 21 pass. Swapping inmain's script makes exactly the two new tests fail.pnpm -w security:dependency-advisories: exit 0,0 High/Critical production advisories; 0 active exceptions. Also exit 0 from a fresh worktree afterpnpm install --frozen-lockfile.pnpm install --frozen-lockfilepasses, both in place and in a fresh worktree.git applyof the committed patch onto the pristinenpm-11.19.0.tgzgives the same tree and digest. The installedbrace-expansion/undicidirs match the filtered registry tarballs file for file.pnpm -w build,pnpm -w lint, strict lint on the changed files,pnpm -w knip,pnpm -w format:check,pnpm -w docs:check, andpnpm -w test:ci-scripts(84 pass).@ultrafuzz/runtimeoperator-npm.test.ts: 3 pass.npm pack --dry-runwith brace globs infilesand includes and excludes the right files.--helpand command help load under brace-expansion 5.0.12.Known risks / follow-ups
fast-uri3.1.7 (GHSA-hrr3-gc8f-f4qj) and four Moderateip-address10.5.0 advisories inside the npm bundle.🤖 Generated with Claude Code
The PR appears safe to merge, with a non-blocking gap in regression coverage for bundled undici.
Fix with agent prompt
Summary
The PR merges per-range registry advisories by GHSA and package at their highest severity, updates installed brace-expansion versions, and extends the pinned operator-npm patch with newer bundled brace-expansion and undici versions.
Reviews (1) · Last reviewed commit: "fix(security): move brace-expansion and ..."