Repository navigation
install: treat an out-of-range package id in bun.lockb as corruption - #31008
Conversation
|
Updated 6:18 PM PT - May 18th, 2026
✅ @robobun, your commit 94894b08c80ed2283dc0db039c1f1e6b4bd3ace6 passed in 🧪 To try this PR locally: bunx bun-pr 31008That installs a local version of the PR into your bun-31008 --bun |
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
WalkthroughThis PR adds a bounds check for ChangesLockfile Corruption Handling
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/cli/install/bun-lockb.test.ts`:
- Around line 123-137: The test currently discards child process output by
passing stdout/stderr: "ignore" to spawn, which hides valuable diagnostics;
change the spawn call used in this test (the spawn invocation that returns
exited and the code variable) to capture stdout/stderr (e.g., pipe or collect
streams) and surface them in assertions before or alongside the exit-code checks
so failures include stdout/stderr context; preserve the existing expectations
(expect(code).not.toBe(133), expect(code).toBe(0), and the node_modules exists
check) but reorder or augment the test to first assert or log captured
stdout/stderr when code !== 0 to make CI flakes debuggable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ce939314-b71a-447e-96ba-1ebde475acc9
⛔ Files ignored due to path filters (1)
test/cli/install/fixtures/corrupt-lockb/bun.lockbis excluded by!**/bun.lockb
📒 Files selected for processing (3)
src/install/lockfile/Package.rstest/cli/install/bun-lockb.test.tstest/cli/install/fixtures/corrupt-lockb/package.json
db374b3 to
dcd8ffc
Compare
|
Found 2 issues this PR may fix:
🤖 Generated with Claude Code |
|
@robobun fix review comments |
|
✅ Review comments addressed in 4a8e403:
Gate verified: without the src fix the test panics with CI: |
… in test Move the out-of-range meta.id check into Serializer::load where InvalidLockfile flows into LoadResult::Err and triggers the existing 'Ignoring lockfile' re-resolve fallback, instead of propagating out of clean_with_logger as an unrecoverable abort. Rewrite the regression test to generate a bun.lockb against the local Verdaccio registry (no-deps / a-dep) and surgically corrupt meta[1].id, so re-resolution never touches npmjs.org. Pipe stdout/stderr and assert on the 'Ignoring lockfile' warning + installed packages before the exit code. Drop the unused fixtures/corrupt-lockb/ (bun.lockb was never committed).
There was a problem hiding this comment.
LGTM — thanks for moving the check into Serializer::load and reworking the test to generate/corrupt the lockfile against local Verdaccio; all prior feedback addressed.
Extended reasoning...
Overview
This PR adds load-time validation that every meta.id in a deserialized bun.lockb is within [0, packages.len()), returning InvalidLockfile if not so the existing LoadResult::Err → report_lockfile_load_error → re-resolve path kicks in instead of a later panic_bounds_check. A second, defense-in-depth check is kept in Package::clone with an updated comment that correctly notes errors at that point abort rather than recover. The regression test now generates a valid lockfile against the local Verdaccio registry (no-deps/a-dep), surgically overwrites meta[1].id with 0x7fffffff, and asserts the install warns "Ignoring lockfile", re-resolves, and exits 0.
Security risks
None. Both source changes are pure additive bounds checks on data already read from disk; they only fire on out-of-range values and return an error that the caller already handles. No new parsing surface, no new trust boundaries, no change to happy-path behaviour.
Level of scrutiny
Lockfile deserialization is a critical install path, but the change here is the most conservative possible: two if id >= len { return Err } guards. The happy path is untouched. The earlier revision had real problems (error returned from a non-recoverable call site, network-dependent test, missing fixture) — all of which I flagged and all of which were fixed in 4a8e403. The author's reply confirms gate testing: without the fix the test panics with index out of bounds: the len is 3 but the index is 2147483647; with it, exit 0.
Other factors
- All three of my prior inline comments are resolved and the responses match the current diff.
- The test's hardcoded binary offsets (header at 42/86/110, 88-byte
Meta, column ordering) are brittle to future format changes, but the test self-validates withexpect(meta[0].id).toBe(0)/expect(meta[1].id).toBe(1)before corrupting, so format drift will fail loudly at the sanity check rather than silently pass. - The three CI failures on 4a8e403 (macOS x64 build-cpp,
v8-heap-snapshot.test.tsSIGKILL,spawn-stdout-iterate-leak.test.ts) are unrelated to this change. - No CODEOWNERS entry covers
src/install/lockfile/. - No bugs found by the bug-hunting system on this revision.
…#41366) ### Problem - `bun add` can panic in `Lockfile::clean_with_logger`: `panic: range end index 391 out of range for slice of length 383`. The frames are `lockfile::StringBuilder::append_with_hash`, `Dependency::clone_with_different_buffers` and `Package::clone`. - `StringBuilder::count` (`src/install/lockfile.rs`) skips a string whose hash is already pooled. `append_with_hash` looked up a hash the caller passed. `Package::clone` passed the `name_hash` stored in the lockfile, and `Package::from_npm` passed hashes stored in the manifest cache. If a stored hash does not match its bytes, the append writes bytes that were never reserved. ### Fix - Remove `append_with_hash`. `append` hashes the bytes it writes, so the count and the append always use the same key. The six callers move to `append`. - Recompute each package's `name_hash` from its name when a `bun.lockb` loads, as loading `bun.lock` already does. A valid file does not change. - Verified: two new tests. `bun-lockb.test.ts` changes a stored hash in `bun.lockb`. `bun-install-registry.test.ts` changes the name hash in a cached manifest. Bun 1.4.1 panics on both. The notes list the other suites. ### Background - The lockfile keeps its strings in one buffer. `StringBuilder` counts the bytes it needs, reserves that much, and then appends. - The string pool maps the hash of a string to its place in the buffer. A pooled string is not counted or written again. - `bun.lockb` is the binary lockfile, and each `.npm` file in the cache is a serialized manifest. Both loaders copy their hashes from disk. <details><summary>Notes</summary> - Crash report: one event, Windows x64, Bun 1.4.0. It has no `text_lockfile` feature, so that run did not load a `bun.lock`. The origin of its bad hash is not proven. The stored hashes in `bun.lockb` and in the manifest cache are the two sources found, and the tests construct both. - A build that logs each mismatched hash found none in the install suites. `bun.lockb` files written by Bun 1.0.36, 1.1.38, 1.2.0, 1.3.0 and 1.3.14 have none either. - #31008 rejects a `bun.lockb` with an out-of-range package id. This change repairs `name_hash` instead, because the name fully determines it. `package_index` is keyed by it, and the debug build's `verify_data` asserts it. - Excluded: the `name_hash` of dependency rows in `bun.lockb`. Dependency strings are counted and appended by their bytes, so those rows cannot reach this panic. - #32753 also edits the end of `bun.lockb.rs` `load`. The two changes touch different lines. - Self-review: it asked for the key to be fixed in `StringBuilder`, which covers `from_npm`, and for the manifest cache test. Both are in this PR. - Suites run with the debug build: `bun-lockb`, `bun-install-registry`, `bun-lock`, `migrate-bun-lockb-v2`, `bun-add`, `bun-add-catalog`, `bun-install`, `bun-workspaces`, `overrides`, `nested-overrides`, `catalogs`, `bun-update`, `migration/`. </details> <!-- robobun:evidence:begin --> --- **no test proof** · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/install/bun-lockb.test.ts, test/cli/install/bun-install-registry.test.ts <!-- robobun:evidence:end -->
A
bun.lockbdamaged by a bad binary-file merge / interrupted write / disk-full / bit-rot madebun install(and--frozen-lockfile) hard-panic (exit 133). Released Bun tolerates the damaged lockfile, re-resolves, and completes the install + rewrites a good lockfile — a deliberate robustness guarantee the port regressed into a crash.Package::cloneindexedpackage_id_mapping[self.meta.id as usize]withself.meta.iddeserialized verbatim from lockfile bytes and never range-checked; on corruption it's garbage and Rust's slice bounds check panics. Zig's ReleaseFast does the same unchecked write (silent UB) and recovers downstream via re-resolution.Bounds-check
self.meta.idagainstpackage_id_mapping.len(); out-of-range → return anInvalidLockfileerror, which the installer already handles by falling back to a fresh resolution. Verified end-to-end against the saved corrupt lockfile:bun install(and--frozen-lockfile) now completes (81 packages installed, exit 0) exactly like released Bun instead of panicking. Regression test + corrupt-lockfile fixture added.