Skip to content

fix(security): merge per-range registry advisories and patch brace-expansion and undici - #1229

Merged
aviggiano merged 2 commits into
mainfrom
claude/advisory-gate-per-range-entries
Sep 30, 2026
Merged

aviggiano merged 2 commits into
mainfrom
claude/advisory-gate-per-range-entries

Conversation

@aviggiano

@aviggiano aviggiano commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

CI on main (a46a496) and on every open PR fails Build gates and Package, dependency, and policy gates at pnpm -w security:dependency-advisories:

dependency advisory policy: approved registry advisory brace-expansion[2] duplicates GHSA-6j4f-fj2g-mc7p

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:

  1. The parser rejects valid registry responses. The bulk endpoint returns one entry per affected version range, so one GHSA shows up several times for one package, each with its own registry id and vulnerable_versions. For our request (brace-expansion 2.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). strictProductionAuditFromBulkResponse keyed on (GHSA, package) and threw on the second entry.
  2. Real High advisories are in the production closure. With the parser fixed, the gate reports:

Change

fix(ci): merge per-range registry entries in the advisory gate

  • Entries that share a GHSA for one package merge into one audited advisory at the highest severity among their ranges. An exception therefore still has to match the worst installed range.
  • A repeated registry id is rejected anywhere in the response: same GHSA, different GHSA, or another package. All other malformed-input checks are unchanged.
  • Tests in scripts/ci/dependency-advisories.test.ts cover 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 on main.

fix(security): move brace-expansion and undici past the September advisories

  • pnpm-installed copies: pnpm update brace-expansion -r moves 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.
    • I did not add a pnpm override: workspace-engine-overrides.test.ts requires the overrides block to equal the Effect pin set exactly.
  • Copies bundled inside the operator npm: overrides can't reach inside the npm tarball, and 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). So I extended the existing lockfile-bound npm@11.19.0 patch, which is how the repo already handles this, to carry brace-expansion 5.0.12 and undici 6.28.1:
    • The new files come from the registry tarballs (sha512 checked against registry metadata), filtered by npm/cli's own node_modules/.gitignore. Applied to 5.0.9 / 6.28.0, that filter reproduces npm's current bundle byte for byte.
    • The patch was regenerated with pnpm patch-commit and 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 npm package.json hunks are byte-identical to main, so reviewing with git diff -w hides nothing.
    • The pinned operator npm closure digest is now 4852a49e… (12,230,966 bytes; still 460 dirs / 1,943 files). operator-npm.test.ts pins the new bundled versions and checks that the comma-part input that overflows 5.0.9's stack now expands.
  • docs/security.md and CHANGELOG (Unreleased > Other changes) are updated.
  • No advisory exception was needed.

Verification

  • bun test scripts/ci/dependency-advisories.test.ts: 21 pass. Swapping in main'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 after pnpm install --frozen-lockfile.
  • pnpm install --frozen-lockfile passes, both in place and in a fresh worktree.
  • git apply of the committed patch onto the pristine npm-11.19.0.tgz gives the same tree and digest. The installed brace-expansion/undici dirs match the filtered registry tarballs file for file.
  • Passing: pnpm -w build, pnpm -w lint, strict lint on the changed files, pnpm -w knip, pnpm -w format:check, pnpm -w docs:check, and pnpm -w test:ci-scripts (84 pass).
  • @ultrafuzz/runtime operator-npm.test.ts: 3 pass.
  • The patched operator npm runs npm pack --dry-run with brace globs in files and includes and excludes the right files.
  • The built CLI's --help and command help load under brace-expansion 5.0.12.

Known risks / follow-ups

  • Still in the production closure, below the High/Critical threshold, so the policy doesn't block them: Moderate fast-uri 3.1.7 (GHSA-hrr3-gc8f-f4qj) and four Moderate ip-address 10.5.0 advisories inside the npm bundle.
  • The npm patch now goes beyond what upstream npm ships. Drop the brace-expansion/undici hunks once an npm release bundles brace-expansion >= 5.0.12 and undici >= 6.28.1.

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR appears safe to merge, with a non-blocking gap in regression coverage for bundled undici.

Fix All in Claude CodeFindings

  1. P2 Undici behavior lacks regression coverage ▶
Fix with agent prompt
### Issue 1
packages/runtime/test/operator-npm.test.ts:34
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.

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!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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.

  • The advisory gate retains strict duplicate registry-ID rejection.
  • The operator-npm closure digest and version assertions are updated; the bundled undici behavior lacks a focused regression test.

Reviews (1) · Last reviewed commit: "fix(security): move brace-expansion and ..."

aviggiano and others added 2 commits September 30, 2026 10:14
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>
@aviggiano
aviggiano requested a review from a team as a code owner September 30, 2026 10:17
"ip-address": "10.5.0",
tar: "7.5.22",
undici: "6.28.0"
undici: "6.28.1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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!

Fix in Claude Code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this as a version check. Two reasons:

  • A regression can't pass silently. bundledOperatorNpmClosureForValidation hashes the path and bytes of every file in npm's tree except node_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.js requires undici, for Agent, EnvHttpProxyAgent, RetryAgent and fetch. 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant