install: preserve empty trustedDependencies across install + lockfile round-trip#31197
install: preserve empty trustedDependencies across install + lockfile round-trip#31197robobun wants to merge 8 commits into
Conversation
Setting "trustedDependencies": [] in package.json blocked lifecycle scripts on the first install — the lockfile's trusted_dependencies was Some(empty_set), so has_trusted_dependency fell through to the empty membership check and correctly returned false for everything. But the text lockfile writer only emitted "trustedDependencies" when at least one installed dep matched the set (bun.lock.rs:401). With an empty set nothing matched, the key was dropped, and reloading yielded trusted_dependencies = None. has_trusted_dependency then fell through to the default allow list and silently re-trusted packages like bcrypt, electron, and esbuild. bun pm untrusted shows the same regression: it only reads the lockfile (not package.json), so after the round-trip it reports 'Found 0 untrusted dependencies with scripts' while bun install was still saying 'Blocked 1 postinstall'. Fix: - bun.lock.rs: emit trustedDependencies whenever the user defined it, even as []. trusted_dependencies.is_some() iff the key was present; None preserves the 'use Bun's defaults' semantics. - install_with_manager.rs: in the !had_any_diffs branch, also clone trusted_dependencies from the freshly-parsed package.json onto the manager's lockfile. The diff only compares dependency lists, so editing trustedDependencies alone (no dep changes) would otherwise get ignored at install time until a dep also changed. Regression test in bun-install-lifecycle-scripts.test.ts uses a file: dep so the lockfile round-trip can be verified without a registry. Reported in #31026 (comment)
|
Updated 7:15 AM PT - May 28th, 2026
❌ @alii, your commit bb0b233 has some failures in 🧪 To try this PR locally: bunx bun-pr 31197That installs a local version of the PR into your bun-31197 --bun |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughChanges enable explicit empty ChangesTrusted Dependencies Empty Set Persistence
Possibly related PRs:
Suggested reviewers:
🚥 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-install-lifecycle-scripts.test.ts`:
- Around line 3227-3229: The test currently consumes stderr and exitCode
together (const [stderr, exitCode] = await Promise.all([proc.stderr.text(),
proc.exited])) which checks exitCode before reading stdout; update the spawn
checks to also read proc.stdout.text() and assert stdout contents before
asserting exitCode. Specifically, change to await
Promise.all([proc.stderr.text(), proc.stdout.text(), proc.exited]) (assigning to
stderr, stdout, exitCode), add the appropriate expect on stdout (e.g.,
expect(stdout).not.toContain("error:") or other existing stdout assertions) and
then assert expect(exitCode).toBe(0); apply the same change to both blocks that
use proc, stderr, and exitCode in this test file.
🪄 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: 6998c84f-2f99-4a42-bc01-fe1cfed3b827
📒 Files selected for processing (3)
src/install/PackageManager/install_with_manager.rssrc/install/lockfile/bun.lock.rstest/cli/install/bun-install-lifecycle-scripts.test.ts
Follow-up on the review in #31197: the original fix refreshed `lockfile.trusted_dependencies` from package.json unconditionally in the `!had_any_diffs` branch, which made install behaviour correct but left `bun pm untrusted` stale on disk — the lockfile save was still gated on `has_diffs()`, and `None → Some({})` (adding `trustedDependencies: []` to an existing project with no dep changes) was the one transition that produced zero added/removed entries and therefore no diff. Move the signal into `Diff::generate` case 4: when transitioning from "use defaults" (None) to an explicit empty set, flag the summary as changed so `has_diffs()` reports it. The existing had-diffs branch in `install_with_manager.rs` already clones `trusted_dependencies` onto the manager's lockfile and triggers a save, so the `!had_any_diffs`-branch refresh from the prior commit is no longer needed and is removed. Net effect: - Install still blocks postinstalls correctly on every run. - `bun.lock` is rewritten with `"trustedDependencies": []` even when only the key was added (no dep edits). - `bun pm untrusted`, which reads the lockfile directly, now sees the empty set on reload and matches `bun install`'s blocked count. Adds a second regression test covering the edit-on-existing-project scenario and updates the original round-trip test to read stdout before asserting the exit code.
The debian-13-x64-asan-test-bun failure on the previous CI run hit test/js/bun/http/serve-body-leak.test.ts, which is a well-documented ASAN-flaky memory-leak test (see PR #28301 and test/no-validate-leaksan.txt:274). This PR only touches src/install/lockfile/ and test/cli/install/ — nothing in the HTTP/Bun.serve path. Re-rolling once.
|
Added the end-to-end test from the review: install
Also covered the |
alii
left a comment
There was a problem hiding this comment.
Did a thorough pass on this — walked the writer, parser, the full Diff transition matrix, the binary/migration paths, workspaces, every trust consumer, --frozen-lockfile, and parity with #31027. The fix is correct and sufficient — behavior now matches the documented contract (omitted → defaults, ["a"] → only a, [] → trust nothing).
Both halves are load-bearing:
- Writer (
bun.lock.rs): the first install now persists[], so it reloads asSome(empty)instead of the key vanishing. - Diff flag (
Package.rstrusted_dependencies_changed): handles theNone → Some({})transition — adding[]to an existing project, or upgrading from a lockfile written by the buggy version. Without ithad_any_diffsstays false, so package.json'sSome({})is never copied into the in-memory lockfile and the lockfile is never rewritten. It's genuinely non-redundant sincegenerate_meta_hashexcludestrusted_dependencies.
Also nice that this brings the text lockfile in line with what bun.lockb already did via HAS_EMPTY_TRUSTED_DEPENDENCIES_TAG. Parser is already symmetric ([] ⟷ Some(empty)), has_trusted_dependency already treats Some(empty) as "trust nothing" (never falls through to the default list), and it merges clean with main despite the #31218 TrustedDependenciesSet change — no rebase needed.
One real ask (non-blocking): the tests don't prove the security property
Both added tests use a file: dep and assert only that bun.lock contains "trustedDependencies": []. But a file: dep is a non-Npm tag, and the default-allow-list fallback only applies to Npm-tag packages — so under both Some(empty) and None a file: dep is "not trusted." These tests guard serialization, but they can't actually exercise the regression (a default-trusted npm package's postinstall re-running after reload).
Could you add one end-to-end test — essentially automating the repro in the description? electron is already on the default-trusted list and has a registry fixture with a preinstall script, and this file already uses the exists(node_modules/electron/preinstall.txt) pattern — so: install electron with trustedDependencies: [], reload/re-install, assert preinstall.txt does not exist. That fails on main (defaults re-apply → preinstall runs) and passes here, which is the property that actually matters. A Some → None (delete-the-key) case dropping the key from bun.lock would be a nice bonus.
Two pre-existing things I noticed while in here (out of scope, just flagging): Some({the full default set}) → None isn't detected as a diff, and --frozen-lockfile won't flag a stale trustedDependencies key because eql/meta-hash both exclude it.
|
@robobun ^ |
|
@robobun wake up and read the comments. |
…stall across reload Add end-to-end coverage using electron (on Bun's built-in default allow list, with a preinstall script) to exercise the actual security property: with trustedDependencies: [], the postinstall stays blocked on the first install AND on reinstall from the persisted lockfile. On main the text lockfile dropped the empty array, so the reload fell back to the default allow list and electron's preinstall ran again. The existing file: round-trip tests guard the lockfile serialization but can't catch this regression: file: deps carry a non-Npm tag, and the default-list fallback only applies to Npm-tag packages, so they are 'not trusted' under both Some(empty) and None. Also adds the Some -> None case: removing the trustedDependencies key drops it from bun.lock and returns the package to the default allow list.
|
Added the end-to-end test you asked for in f2f8927 — it automates the repro with
That second install is where Also added the Review nits addressed (343104e); reinstall step asserts the deterministic ground truth — electron's Status (after the latest The two pre-existing gaps you flagged ( |
|
@robobun two small review nits still open on
Can you tidy those up and push? Otherwise this looks ready. |
|
Both tidied up in 343104e:
Resolved both review threads. |
…e on reinstall The text lockfile doesn't persist per-package script metadata, so the "Blocked N postinstall" summary is not emitted on a reinstall from bun.lock even though the package stays correctly blocked. Assert the deterministic ground truth (electron's preinstall.txt is absent) instead of the cosmetic summary line, which was failing CI on the reinstall step.
…-lockfile-roundtrip
What
Reported in #31026 (comment) — companion fix for the docs PR #31027.
Setting
"trustedDependencies": []inpackage.jsonblocked lifecycle scripts on the first install but silently stopped working on every subsequent one. With the default allow list re-applying under the user's nose, packages likebcrypt,electron, andesbuildhad their postinstalls run even though the user had asked to trust nothing.Repro (before this PR)
$ cat package.json { "trustedDependencies": [], "dependencies": { "bcrypt": "5.1.1" } } $ bun install Blocked 1 postinstall. Run `bun pm untrusted` for details. ← correct $ bun pm untrusted Found 0 untrusted dependencies with scripts. ← disagrees with install! $ rm -rf node_modules && bun install 58 packages installed ← no "Blocked" message $ find node_modules/bcrypt -name '*.node' node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node ← postinstall RANRoot cause
Two separate layers dropped the signal:
Text lockfile writer (
src/install/lockfile/bun.lock.rs) only emitted"trustedDependencies"when at least one installed dep matched the set. With[](or any list whose names aren't yet installed), nothing matched, the key was dropped, and reloading yieldedtrusted_dependencies = None.has_trusted_dependency(src/install/lockfile.rs):The two code paths depend on
SomevsNone, so losingSome(empty)quietly flips the contract.Install-time diff (
src/install/lockfile/Package.rs,Diff::generate): aNone → Some(empty)transition ontrusted_dependencies— adding"trustedDependencies": []to an existing project with no dependency changes — produced zero added/removed entries, sohas_diffs()returned false and the lockfile was never rewritten, leaving the staleNoneon disk.Fix
bun.lock.rs: emittrustedDependencieswhenever the user defined it, even as[].trusted_dependencies.is_some()iff the key was present;Nonestill means "use Bun's defaults".Package.rs: inDiff::generatecase 4, flag theNone → Some({})transition via a newtrusted_dependencies_changedsummary field sohas_diffs()reports it and the lockfile is rewritten to persist the[].Verification
New regression tests in
test/cli/install/bun-install-lifecycle-scripts.test.ts, covering both a fresh-install round-trip and editingtrustedDependencies: []onto an existing project with no dep changes. Uses afile:dep so the lockfile round-trip can be verified without a registry.main: lockfile is missing the"trustedDependencies": []line after install.The full repro from the issue comment (bcrypt +
rm -rf node_modules && bun install+bun pm untrusted) also works end-to-end with this build.Related
Companion to #31027 (docs clarification). Together these land the behavior the docs describe.