Repository navigation
Fix npm root-bundled deps refused (#1618) - #1661
Conversation
Assisted-by: Claude Code:claude-opus-5-5
npm 7+ marks every package covered by the root project's own bundleDependencies (and their dependencies) with inBundle: true. socket-patch read that flag as "ships inside a dependency's tarball", so hosted scans skipped those packages, vendored scans failed with exit 1, and VEX left them out, all while npm ci installs them from the lock like any other entry. Only a copy a dependency bundles is now treated as bundled: the lock inventory walks up the entry's owners and stops at the first plain dependency (or one that declares its own bundle). An owner chain that reaches the root or a workspace member is the project's own bundle. The legacy dependencies tree follows the same rule. Fixes #1618 Assisted-by: Claude Code:claude-opus-5-5
Covers the three package.json spellings npm accepts for a root bundle and checks the in-run VEX attests the rewired package. Refs #1618 Assisted-by: Claude Code:claude-opus-5-5
npm writes no bundled flag in the legacy dependencies tree for a root bundle, so that tree keeps reading bundled: true as before; only the packages map needs the owner walk. Update the inventory fixture that used a top-level inBundle entry as a stand-in for a dependency's bundle, and re-bless the npm lock rewrite golden: its generator puts inBundle on top-level entries, which are now rewired as root bundles. Refs #1618 Assisted-by: Claude Code:claude-opus-5-5
Real npm records the root's bundle list on the lock's "" entry (as a list, whatever spelling package.json used) and writes no inBundle for a workspace member's bundle. So a top-level inBundle copy now counts as the project's own bundle only when that list exists; a workspace owner, a missing owner or a root without a bundle keeps the copy bundled. The npm lock rewrite golden is back to main's version. Also fix the docs this left stale, and move a test that had split the #325 regression test from its doc comment. Refs #1618 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 89b80fd. Configure here.
|
Ready for review at
Slack announcement not sent: this run had no Slack send tool. Generated by Claude Code |
Final review briefWhat it does. npm writes Risk: low–medium. The flag feeds hosted, vendored and VEX, but it has a single reader (I grepped for Look here
Verified
Changes I made: none. Open questions: none blocking. One gap is documented: a copy nested under a root-bundled package that declares its own bundle stays skipped, which is the same as before this PR. Auto-merge is armed: approving sends this straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1618
Summary
npm 7+ writes
inBundle: trueon every package the root project's ownbundleDependenciescovers, and on their dependencies. socket-patch read that flag as "ships inside a dependency's tarball", so for those packages:scanskipped them (redirect_npm_bundled_instance_skipped, exit 0) and pointed at a "bundling parent" that does not exist. On a v2 lock only the legacy mirror was rewired, leaving the lock half-wired;scan/vendorrefused them (vendor_lock_entry_not_rewritable, exit 1 on every run);vexleft them out.Root bundles have no parent tarball.
npm install/npm cifetch them fromresolved+integritylike any other entry (the issue showsnpm ciinstalling a hand-rewired root-bundled entry on npm 8, 10 and 12).Root cause and fix
Every hosted, vendored and VEX reader keys off one flag,
NpmLockEntry::bundled, whichwalk_npm_packages(vendor/lock_inventory/npm.rs) took straight frominBundle. The fix is at that shared boundary:npm_dependency_bundledwalks up the entry's owners (the key before its lastnode_modules/):bundleDependencies, is a dependency whose tarball carries the copy, so the copy stays bundled.""is the project's own bundle when the lock's""entry records a bundle. The copy then becomes rewirable.I checked the inputs against real npm 10.9.4 here. npm records the root bundle on
packages[""].bundleDependenciesas a list for all threepackage.jsonspellings (a list,true, and thebundledDependenciesalias). It writes noinBundlefor a workspace member's bundle, and no legacybundledflag for a root bundle. So the legacydependenciestree keeps its existing reading.Known conservative gap: take a copy nested beneath a root-bundled package that itself declares
bundleDependencies(a version-conflict nest). The lock can't tell whether that package's own tarball carries the copy, so it stays bundled, as it was before this PR. CLI_CONTRACT.md documents this.Tests (red → green)
With the fix reverted to the raw
inBundleflag, all four new core tests fail; with the fix they pass.vendor::lock_inventory::npm::tests::only_dependency_bundles_are_bundledpatch::redirect::tests::npm_root_bundled_entries_are_rewiredvendor::npm_lock::tests::root_bundled_instance_is_vendoredvex::discover::npm::tests::root_bundled_wired_entry_is_a_ref--vexin_process_redirect::scan_redirect_npm_root_bundled_entry_is_rewired_and_attestedLocal runs (Linux, as root):
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --all-features --lib: 6595 passed, 4 failed. The four are permission-dependent tests that can't make a write fail when run as root (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_*,pypi_requirements::wire_failure_rolls_back_*). None of them touches npm code.in_process_redirect: 145 passed, 3 failed, the same root-permission write-failure cases.in_process_vendor: 134 passed.hosted_inventory: 21 passed.e2e_redirect_npm_build --include-ignoredwith real npm 10.9.4: 36 passed.cargo test --workspacerun hit the session's disk limit while linking the e2e test binaries, so I ran the suites above instead.I didn't add a real-npm
npm cie2e for the root bundle. The issue already verifies that npm honours the rewired entry, and the in-process test covers the CLI path. The npm, pypi and gem wrappers only dispatch to the binary, so they need no change.https://claude.ai/code/session_01RYRA3DFNMzVU2tcjyH1HVf
Note
Medium Risk
Touches shared npm lock classification used by redirect, vendor, and VEX; misclassification could skip rewires or attest wrong installs, though conservative fallbacks remain for ambiguous cases.
Overview
Fixes #1618: npm lock handling no longer treats every
inBundle: trueentry as “inside a dependency tarball.” Packages covered by the root project’sbundleDependencies(and their transitive deps) are installed fromresolved/integritylike normal lock entries, so hosted redirect, vendoring, and VEX discovery can wire and attest them again.The behavior change is centralized in
npm_dependency_bundled: when walkingpackage-lock.jsonpackages, it walks up owner keys and only marks copies as bundled when a dependency (not the root bundle) carries them. Hosted scan, vendor, and VEX all shareNpmLockEntry::bundled, so they pick up the same classification.CLI_CONTRACT.mddocuments the split between root bundles vs dependencyinBundle/bundledskips.Regression coverage adds unit tests for classification, redirect rewrite, vendoring, and VEX refs, plus an in-process test that hosted scan + in-run
--vexattests a root-bundled package.Reviewed by Cursor Bugbot for commit 89b80fd. Configure here.
Generated by Claude Code