Fix npm/Bun VEX attesting a patch a same-lock copy skips (#588) - #589
Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A package-lock.json can wire one copy of name@version to the Socket artifact while another entry for the same name@version (a workspace member added after vendoring, then npm install) still resolves from the registry. npm ci installs that copy unpatched, but vex attested the patch as not_affected because only the sibling npm lock could contest a ref. An unwired entry in the ref's own lock now contests it too, with a diagnostic naming the entry and asking to re-run vendor / scan. Refs #588 Assisted-by: Claude Code:claude-opus-5-5
The bun.lock / bun.lockb twin of the npm fix: a second entry resolving a wired name@version from the registry installs unpatched beside the rewired one, so it now contests the ref instead of letting vex attest it as not_affected. Refs #588 Assisted-by: Claude Code:claude-opus-5-5
vendor --check only verified the committed artifact for npm entries, so it passed while package-lock.json still had a second entry for the patched name@version resolving from the registry. That copy installs unpatched and a reinstall cannot fix it. The check now re-scans every present npm lock for the entries vendor would rewire and fails with vendor_check_failed when one of them does not resolve to the vendored artifact, naming the entry. Refs #588 Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
|
[agent] CI: I don't think this PR caused it:
No fix is needed in this PR. I'll re-run the failed job once when the workflow finishes; the re-run API returns 403 "already running" until then. If it fails again, I'll treat it as real. Generated by Claude Code |
|
BugBot review Generated by Claude Code |
|
[agent] CI: This isn't caused by this PR, which doesn't touch yarn berry or the benchmark harness. The benchmark fixture ( A fixture-only fix already exists in #587 (329b146, "test: align Berry benchmarks with descriptor resolutions"), and Generated by Claude Code |
|
bugbot run 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 edc7c8b. Configure here.
|
[burn-down agent] Labeled Ready for review at
Generated by Claude Code |
|
Reviewed The same-lock contest preserves UUID recognition, preventing an old ledger from reviving the dropped reference. I also verified the mixed sibling-lock case: when one npm lock is clean and the other contains both patched and registry copies, the full discovery pass drops every reference. The new npm wiring audit checks both locks and matches aliases by their real package identity. Local validation passed: 41 npm discovery tests, 25 Bun discovery tests, the new CLI VEX/vendor-check regression, and 4 additional reviewer probes covering sibling locks, legacy nesting versus v2 mirrors, scoped aliases/prereleases, and both-lock audits (71 unique tests). Exact-head CI is terminal: 455 checks passed, 7 skipped; all 13 workflows completed (12 successful, 1 skipped). Bugbot found no new issues on this head, there are no unresolved review threads, and the merge with current main |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #588
Summary
A
package-lock.jsoncan wire one copy ofname@versionto the Socket artifact while a second entry for the samename@versionin the same lock still resolves from the registry. That's the normal state after vendoring, adding a workspace member that needs the same version, and runningnpm install.npm cithen installs that copy unpatched, yet:vex(with or without--no-verify) and hostedvex --no-verifyattestednot_affected;vendor --checkpassed with "committed artifact and wiring verified".Now that copy contests the reference.
vexomits the purl with apatched_ref_unattributablediagnostic that names the entry and says to re-runvendor/scan.vendor --checkfails withvendor_check_failed, naming the unwired entry.Root cause
vex/discover/npm.rspush_uncontestedonly let an unwired registry entry contest a ref when it was in the other npm lock (*j != i). Bundled copies (npm VEX attests not_affected while a bundled (inBundle) copy of the same package@version stays unpatched #325) and non-registry copies (npm hosted and vendored modes rewire git-sourced lock entries, so npm ci silently installs the unpatched git bytes while scan and VEX report success #326) were already contested inside the same lock; a registry copy was not. The Bun extractor (vex/discover/bun.rs) had the same gap. The issue reports that the Bun routine reproduced it withbun.lock.vendor --check:run_checkverified only the committed artifact for npm entries and never looked at the lock.What changed
vendor/lock_inventory/npm.rs: newnpm_lock_located_nodes, the shared lock walk with each entry's location (thepackageskey or the v1 chain). The bundled walk already produced locations.vex/discover/npm.rs:unwiredmaps each purl to its first location.push_uncontestedcontests a ref when its own lock has an unwired entry for the same purl, before the existing cross-lock check.vex/discover/bun.rs:classifyreturns the purl of a registry entry, and a newUnwiredset contests same-lock refs after the bundled contest, for bothbun.lockandbun.lockb.vendor/npm_lock.rscheck_wiringandvendor/npm_flavor.rscheck_npm_wiring: for a package-lock ledger entry, every entryvendorwould rewire (scan_lock_matches, the same set) in each present npm lock must resolve tofile:<artifact>.commands/vendor.rsrun_checkcalls it for npm entries. Other npm flavors are unchanged (Ok).CLI_CONTRACT.md(contested locks,vendor --check) andCHANGELOG.mdare updated.No wrapper (
npm/,pypi/,gem/) changes are needed: they only dispatch to the binary.Test evidence
mainvex::discover::npm::tests::issue_588_unwired_registry_copy_in_the_same_lock_contests_the_refbun.lock), hosted + vendoredvex::discover::bun::tests::issue_588_registry_copy_in_the_same_lock_contests_the_refvex(vendored, nonode_modules) does not atteste2e_vex_vendor::vendored_npm_patch_with_an_unwired_registry_copy_in_the_same_locknot_affectedvendor --checkfails namingpackages/b/node_modules/lodashvendor_check_okThe red runs were made in a separate worktree on
origin/mainwith only the new tests copied in. Thevendor --checkrow was checked separately by applying only the VEX commits.Commands run locally:
cargo clippy --workspace --all-features -- -D warnings: clean on the head. An intermediate commit carried an unused re-export, which is what CI'sclippyflagged on5ce219e/8d1a570; it's removed inc7479de.rustfmt --check: every hunk this PR adds is clean.mainalready has unformatted code elsewhere, and CI doesn't gate on fmt.cargo test -p socket-patch-core --lib: 4833 passed, 4 failed. The 4 are the known root-sandbox permission tests (relax_loop_must_not_traverse_symlinked_root,an_unremovable_hidden_lock_keeps_every_store_entry,wire_write_failure_maps_error_and_leaves_lock_untouched,wire_failure_rolls_back_already_written_files).cargo test -p socket-patch-cli --all-featuresone2e_vex,e2e_vex_lockfile,e2e_vex_redirect,e2e_vex_vendor,covgap_commands_vex,in_process_vendor,in_process_vendor_bun_takeover,mode_migration_npm,mode_migration_bun,vendor_eject*: 532 passed, 0 failed.covgap_commands_vendor: 41 passed, 3 failed. The 3 are*_state_write_failure_*tests, which can't fail as root in this sandbox.e2e_vendor_npm_buildande2e_vendor_bun_buildwith--include-ignored: 17 + 14 passed.cargo test --workspacedoesn't fit in this sandbox's disk allowance, so CI runs the rest.CI status (a0a210c: 453 passed, 6 skipped, 1 failed, 1 re-running)
native (ubuntu-latest, 1.4.2)(Bun): one cell failed after its 3 transport retries, with patches-apiConnection reset by peer. It's being re-run once (19:28 UTC).scan performance: red onmainas well. Bothyarn-berryscenarios are invalid on the base binary too, because of a stale benchmark expectation after Fix yarn berry hosted pin leaking npm auth (#404) #465. The fixture-only fix is Stream ZIP member hashes during vendored verification #587's 329b146. It isn't ported here (see the PR comment): either Stream ZIP member hashes during vendored verification #587 lands first, or a maintainer cherry-picks it.Per-issue checklist
vex/vex --no-verifydon't attest: npm discover test + CLIe2e_vex_vendortestvex --no-verifydoesn't attest: hosted case of the npm discover test (the same discovery path)vendor --checkreports the unwired copy as drift: CLIe2e_vex_vendortest🤖 Generated with Claude Code
https://claude.ai/code/session_01FN6qdGNobQZG6C41AQMb5V
Note
Medium Risk
Changes VEX attestation and vendor audit logic for npm/Bun lock discovery; incorrect contest detection could wrongly suppress attestations or fail checks, but scope is limited to duplicate same-version lock entries.
Overview
Fixes #588: when one lock entry wires
name@versionto Socket but another entry in the same npm or Bun lock still resolves that version from the registry (typical after vendoring, then adding a workspace member and running install), the package manager installs an unpatched copy alongside the patched one.vexnow treats that registry entry as a same-lock contest (like bundled copies already did). Discovery drops the patch ref withpatched_ref_unattributable, names the unwired lock path, and tells users to re-runvendor/scan. npm usesnpm_lock_located_nodesand maps unwired purls to locations; Bun adds anUnwiredpass after classification forbun.lock/bun.lockb.vendor --checkfor package-lock entries runs new wiring auditcheck_wiring: every rewritablepackagesinstance must point atfile:<vendored artifact>; drift fails withvendor_check_failedand names the offending entries.CHANGELOG and CLI_CONTRACT document contested-lock and
--checkbehavior. Regression tests cover npm/Bun discovery and CLIvex+vendor --check.Reviewed by Cursor Bugbot for commit edc7c8b. Configure here.
Generated by Claude Code