Skip to content

audit fix: report and rewrite the overrides rule that holds a vulnerable version - #38785

Open
robobun wants to merge 2 commits into
mainfrom
farm/48b5b457/audit-fix-overrides
Open

robobun wants to merge 2 commits into
mainfrom
farm/48b5b457/audit-fix-overrides

Conversation

@robobun

@robobun robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • bun audit fix on a project whose overrides entry pins the vulnerable version blames the third-party dependents: the plan prints a@1.0.0 depends on b@1.0.0 and c@1.0.0 depends on b@1.0.0 although a and c declare b@^1.0.0; 1.0.0 is the override's value.
  • No remedy is printed for that class, and bun audit fix --latest also ends in Fixed 0 of 1, so the one file the user controls is never named or touched. docs/pm/cli/audit.mdx even suggests adding an override as the way out.
  • Cause: src/install/audit_fix.rs builds each Blocker from the edge's post-override range (effective_npm_range) and labels it with the dependent, and pin_for returned None for every edge matched by lockfile.overrides, so neither mode had an edit to offer.
  • Overrides are how people pinned transitive dependencies for earlier advisories, so this is a common shape for audit fix to meet.

Fix

  • OverrideMap::lookup returns the rule that applies to an edge (get is now a wrapper over it); dedupe::applied_override adds the existing "aliases and workspace: edges are never overridden" condition, and effective_version is built on it, so the planner and the range computation agree on which rule governs an edge.
  • Blocked section: an edge held by a rule reports package.json overrides b@<value>, followed by the rule's key when it is scoped ((a>b), (b@<1.1.0)); all edges held by one rule collapse into one line. Bundled edges stay attributed to the package that bundles them.
  • pin_for plans an edit for the rule's value under the same policy as dependency pins and catalog entries: an exact value is rewritten whenever the fix stays within ^current, any range is rewritten under --latest, and the bun audit fix --latest hint appears in the blocked section when that would help. A rule whose value is catalog: produces the usual catalog edit.
  • PackageJsonEdit.catalog becomes EditSite { Dependencies, Catalog, Override(OverrideSelector) }. Applying an override edit walks overrides (else resolutions) and matches each entry by its normalized selector, so the entry is found in any accepted spelling: "b", "a>b", "a/b", "**/b", {"a": {"b": ..}}, {"b": {".": ..}}, "a@1>b", "b@<1.0.1". The edit is only planned when the entry's current text equals the value bun.lock recorded; an entry written as "$dep" is therefore reported as the blocker and left alone instead of being claimed as fixed.
  • Text output: package.json (overrides): 1.0.0 -> 1.0.1, or (overrides a>b) for a scoped rule. --json: blockers and packageJson edits gain "override" (the rule key, null elsewhere); the other keys are unchanged.
  • Verified with test/cli/install/bun-audit.test.ts: the new and rewritten override cases (direct and transitive dependents, the eight key spellings, $ref, scoped rule blocked and rewritten, --json) fail on the unfixed build (25 failures, 14 of them these cases, the rest the override JSON key) and the whole file passes with it (189 tests).
  • test/cli/install/overrides.test.ts, nested-overrides.test.ts, bun-dedupe.test.ts and bun-update-transitive.test.ts pass (395 tests), covering the OverrideMap/effective_version refactor; the byte-search and pub-exports source lints pass.
  • docs/pm/cli/audit.mdx describes the new lines.

Background

  • overrides (npm) / resolutions (yarn) in the root package.json replace the range a dependent declares for a package. bun stores each rule in the lockfile's OverrideMap in a normalized form: either a flat rule keyed by name, or a ScopedOverride carrying an optional parent (name plus optional range) and an optional target range; the key text the user wrote is not kept, which is why the edit matches entries by re-parsing their keys with the same selector parser OverrideMap uses.
  • A $name value copies the range declared for name in the project's own dependencies; bun.lock records the resolved range, so it is the one case where the file and the lockfile literal differ.
  • bun audit fix plans per installed vulnerable version ("instance") over the edges that resolve to it. An edge either accepts a safe candidate through its effective range (fixed in bun.lock only), or through a PackageJsonEdit ("pin") that will be rewritten before the install runs; edges with neither become Blockers. After a package.json edit, the regular install differ notices the changed override and re-resolves every edge of that name, which is what moves the dependents onto the fixed version.

[review] gate passed · iteration 0 · 7 files touched

fails on main (without fix)
ASAN without fix: 27 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-audit.test.ts
bun test v1.4.0 (8934192ec)

test/cli/install/bun-audit.test.ts:
(pass) `bun audit` > should fail with no package.json [290.47ms]
(pass) `bun audit` > should fail with package.json but no lockfile [240.12ms]
(pass) `bun audit` > should exit 0 when there are no dependencies in package.json [207.44ms]
(pass) `bun audit` > should exit 0 when there are no vulnerabilities [319.83ms]
(pass) `bun audit` > should exit code 1 when there are vulnerabilities [348.53ms]
(pass) `bun audit` > --audit-level that drops every advisory says how many it dropped [199.22ms]
(pass) `bun audit` > --ignore that drops every advisory says how many it ignored [205.06ms]
(pass) `bun audit` > --audit-level and --ignore drops are listed separately [169.16ms]
(pass) `bun audit` > should print valid JSON and exit 0 when --json is passed and there are no vulnerabilities [200.49ms]
(pass) `bun audit` > should print valid JSON and exit 1 when --json is passed and there are vulnerabilities [369.87ms]
(pass) `bun audit` > --json exits 0 
... (truncated)

release without fix: 2 FAILED
bun test v1.4.0-canary.1 (e8fb0578b)

test/cli/install/bun-audit.test.ts:
(pass) `bun audit` > should fail with no package.json [12.67ms]
(pass) `bun audit` > should fail with package.json but no lockfile [8.83ms]
(pass) `bun audit` > should exit 0 when there are no dependencies in package.json [19.58ms]
(pass) `bun audit` > should exit 0 when there are no vulnerabilities [18.72ms]
(pass) `bun audit` > should exit code 1 when there are vulnerabilities [14.65ms]
(pass) `bun audit` > --audit-level that drops every advisory says how many it dropped [6.79ms]
(pass) `bun audit` > --ignore that drops every advisory says how many it ignored [11.37ms]
(pass) `bun audit` > --audit-level and --ignore drops are listed separately [5.95ms]
(pass) `bun audit` > should print valid JSON and exit 0 when --json is passed and there are no vulnerabilities [4.89ms]
(pass) `bun audit` > should print valid JSON and exit 1 when --json is passed and there are vulnerabilities [11.84ms]
(pass) `bun audit` > --json exits 0 when --audit-level filters out every advisory, but still prints them all [8.23ms]
(pass) `bun audit` > --json exits 0 when --ignore covers every advisory, but still prints t
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-audit.test.ts
bun test v1.4.0 (8934192ec)

test/cli/install/bun-audit.test.ts:
(pass) `bun audit` > should fail with no package.json [230.13ms]
(pass) `bun audit` > should fail with package.json but no lockfile [204.59ms]
(pass) `bun audit` > should exit 0 when there are no dependencies in package.json [211.18ms]
(pass) `bun audit` > should exit 0 when there are no vulnerabilities [297.93ms]
(pass) `bun audit` > should exit code 1 when there are vulnerabilities [407.47ms]
(pass) `bun audit` > --audit-level that drops every advisory says how many it dropped [265.44ms]
(pass) `bun audit` > --ignore that drops every advisory says how many it ignored [228.83ms]
(pass) `bun audit` > --audit-level and --ignore drops are listed separately [235.19ms]
(pass) `bun audit` > should print valid JSON and exit 0 when --json is passed and there are no vulnerabilities [218.27ms]
(pass) `bun audit` > should print valid JSON and exit 1 when --json is passed and there are vulnerabilities [374.12ms]
(pass) `bun audit` > --json exits 0 
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 1004ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/28] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited
[2/28] gen cpp.rs (cppbind)
[3/28] gen JS modules (bundle-modules)
Preprocess modules (12729ms)
Bundle modules (58ms)
Postprocesss modules (217ms)
Bundle Functions (1009ms)
Generate Code (34ms)

[14.07s] Bundled "src/js" for production
  2632 kb
  197 internal modules
  13 native modules
  91 internal functions across 17 files
[3/23] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m
... (truncated)
diff hotspot
docs/pm/cli/audit.mdx                       |  13 +-
 src/install/audit_fix.rs                    | 246 +++++++++++++--------
 src/install/audit_fix/json.rs               |  22 +-
 src/install/audit_fix/package_json_edits.rs | 222 +++++++++++++++++--
 src/install/dedupe.rs                       |  27 ++-
 src/install/lockfile/OverrideMap.rs         |  33 ++-
 test/cli/install/bun-audit.test.ts          | 320 +++++++++++++++++++++++++---
 7 files changed, 717 insertions(+), 166 deletions(-)

gate history · 2 passed · 0 rejected · iteration 0

evidence per changed file
file                                         reads  edits  tests
docs/pm/cli/audit.mdx                            1      4      0
src/install/audit_fix.rs                         6     13      0
src/install/audit_fix/json.rs                    2      6      0
src/install/audit_fix/package_json_edits.rs      3      5      0
src/install/dedupe.rs                            2      2      0
src/install/lockfile/OverrideMap.rs              4      2      0
test/cli/install/bun-audit.test.ts              11     12      0

…ble version

When the installed version of a vulnerable package comes from an
overrides/resolutions rule in the root package.json, the plan used to
print the rule's range as if each dependent had declared it
("a@1.0.0 depends on b@1.0.0"), offered no remedy, and --latest could
not get past it either, since pin_for refused every overridden edge.

Edges held by a rule are now attributed to the rule: the blocked
section prints one "package.json overrides b@<range>" line per rule
(with the rule's key when it is scoped), and the rule's value is
rewritten in place the way dependency pins and catalog entries are:
an exact value whenever the fix stays within its caret range, any
range under --latest. The entry is located by the normalized rule
rather than by key text, so every accepted spelling is handled
(plain, "a>b", "a/b", "**/b", nested objects, "." and "name@range"
keys); a value written as a $reference is reported as the blocker and
left alone. --json carries the rule key as "override" on blockers and
package.json edits.
@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3e7a097-c454-4a99-a132-91a969e69a04

📥 Commits

Reviewing files that changed from the base of the PR and between 4bf3f36 and 8934192.

📒 Files selected for processing (7)
  • docs/pm/cli/audit.mdx
  • src/install/audit_fix.rs
  • src/install/audit_fix/json.rs
  • src/install/audit_fix/package_json_edits.rs
  • src/install/dedupe.rs
  • src/install/lockfile/OverrideMap.rs
  • test/cli/install/bun-audit.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:05 PM PT - Aug 14th, 2026

@robobun, your commit 8934192 is building: #97070

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review; needs a maintainer's eye on the override JSON key name and the (overrides ...) / package.json overrides ... text format.

Reproduced on main with the cases now in test/cli/install/bun-audit.test.ts (a root overrides entry holding a vulnerable transitive version: the plan printed the dependents with the override's range, no remedy, and --latest changed nothing). With src/ stashed, 25 cases in that file fail; with this branch the file passes (191), as do overrides.test.ts, nested-overrides.test.ts, bun-dedupe.test.ts and bun-update-transitive.test.ts (395).

8934192 addresses the two review notes (every entry spelling the same rule is rewritten, planning reads the last one; override_literal doc comment corrected); both threads are resolved and the follow-up review pass found nothing further.

CI (build 97070, final): 178 of 179 jobs passed. The one non-passing job is a darwin 14 aarch64 test shard that expired without ever running: that queue has ~300 jobs waiting on six agents and PR jobs expire before they are served. The other shard of the same lane ran and passed, and the only test failures in the build are flaky entries that passed on retry, on lanes this diff does not touch. Not retriggering, since a new build would join the same queue; the diff is green on everything that ran.

Comment thread src/install/audit_fix/package_json_edits.rs Outdated
Comment thread src/install/audit_fix/package_json_edits.rs Outdated
OverrideMap keeps the last of several entries that normalize to one
rule, so the planner now reads the last matching entry and the edit
rewrites each matching entry that holds the version, instead of both
stopping at the first one.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both earlier inline findings are addressed in 8934192 — for_each_override_value now visits every matching entry (planning reads the last, the edit rewrites each one holding the old value), and the override_literal doc comment is accurate. No further issues found this pass.

Given the scope — ~700 lines across the audit-fix planner, package.json rewriting, OverrideMap, and a new override key in the --json output — a human look is still worthwhile.

What was reviewed

  • pin_for refactor: override edges now produce an EditSite::Override edit; bundled edges stay attributed to the dependent; $ref values compared against the lockfile literal and left alone.
  • for_each_override_value matches entries by re-parsing keys with the same parse_selector/parse_package_segment the OverrideMap parser uses, so the eight accepted spellings agree; nested-object form gated on overrides only.
  • OverrideMap::lookup/get and dedupe::effective_version are behavior-preserving refactors — same alias/workspace exclusions, same clone semantics.
  • --json schema: override added alongside existing keys; existing test assertions updated to include override: null.
Extended reasoning...

Overview

Teaches bun audit fix to recognize when a vulnerable version is held by an overrides/resolutions rule in the root package.json, report it as such, and rewrite the rule under the same policy as dependency/catalog pins. Touches the audit-fix planner (audit_fix.rs), the package.json edit applier (package_json_edits.rs), the JSON output writer, dedupe.rs (extracts applied_override), and OverrideMap.rs (adds lookup returning a borrowed OverrideRule so callers can read the rule's shape, with get now a thin wrapper). ~300 lines of new test coverage in bun-audit.test.ts, plus docs.

Prior findings

Two inline comments from the previous run were both addressed in 8934192:

  • The single-slot override_rule_value was replaced with a visitor (for_each_override_value) so duplicate spellings of one rule are all rewritten; two test.each rows cover both-rewritten and shadowed-first cases.
  • The misleading override_literal doc comment was reworded; the $ref handling is now documented at the equality check in pin_for where it actually happens.

Security risks

None identified. The code reads and rewrites the user's own root package.json based on lockfile contents and registry advisories; no untrusted-input parsing beyond what bun install already does. The written == literal guard ensures an edit is only planned when the file text matches what bun.lock recorded, so $ref values and hand-edited entries aren't silently overwritten with something else.

Level of scrutiny

Medium-high. This is package-manager code that rewrites user files (package.json) and changes the shape of bun audit fix --json output (adds an override key to blockers and packageJson edits). The refactor of pin_for changes how every edge is classified, and effective_version in dedupe.rs now goes through applied_override — I traced that both preserve the prior alias/workspace: exclusion and clone semantics. The OverrideMap::get → lookup split is mechanical.

Other factors

  • Test coverage is thorough: direct and transitive dependents, eight key spellings across overrides and resolutions, $ref left alone, scoped rule blocked and rewritten, --json shape, duplicate-spelling rows. The PR body confirms 25 targeted failures on main and a clean 189-test pass on the branch, plus overrides/nested-overrides/dedupe/update-transitive suites (395 tests) covering the refactor.
  • Not approving automatically because of size and because it changes user-facing --json output and file-rewriting behavior; a maintainer should confirm the override JSON key naming and the (overrides ...) text-output format are what they want.

This branch has not been deployed

No deployments
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.

2 participants