Skip to content

Fix npm/Bun VEX attesting a patch a same-lock copy skips (#588) - #589

Open
Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
mainfrom
agent/fix-npm-same-lock-unwired-copy
Open

Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
mainfrom
agent/fix-npm-same-lock-unwired-copy

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #588

Summary

A package-lock.json can wire one copy of name@version to the Socket artifact while a second entry for the same name@version in 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 running npm install. npm ci then installs that copy unpatched, yet:

  • vendored vex (with or without --no-verify) and hosted vex --no-verify attested not_affected;
  • vendor --check passed with "committed artifact and wiring verified".

Now that copy contests the reference. vex omits the purl with a patched_ref_unattributable diagnostic that names the entry and says to re-run vendor / scan. vendor --check fails with vendor_check_failed, naming the unwired entry.

Root cause

What changed

  • vendor/lock_inventory/npm.rs: new npm_lock_located_nodes, the shared lock walk with each entry's location (the packages key or the v1 chain). The bundled walk already produced locations.
  • vex/discover/npm.rs: unwired maps each purl to its first location. push_uncontested contests a ref when its own lock has an unwired entry for the same purl, before the existing cross-lock check.
  • vex/discover/bun.rs: classify returns the purl of a registry entry, and a new Unwired set contests same-lock refs after the bundled contest, for both bun.lock and bun.lockb.
  • vendor/npm_lock.rs check_wiring and vendor/npm_flavor.rs check_npm_wiring: for a package-lock ledger entry, every entry vendor would rewire (scan_lock_matches, the same set) in each present npm lock must resolve to file:<artifact>. commands/vendor.rs run_check calls it for npm entries. Other npm flavors are unchanged (Ok).
  • CLI_CONTRACT.md (contested locks, vendor --check) and CHANGELOG.md are updated.

No wrapper (npm/, pypi/, gem/) changes are needed: they only dispatch to the binary.

Test evidence

Issue facet Regression test On main With fix
npm lock, hosted + vendored ref contested by a same-lock registry copy; a different version doesn't contest vex::discover::npm::tests::issue_588_unwired_registry_copy_in_the_same_lock_contests_the_ref FAILED (ref attested) ok
Bun twin (bun.lock), hosted + vendored vex::discover::bun::tests::issue_588_registry_copy_in_the_same_lock_contests_the_ref FAILED (contest disabled) ok
CLI vex (vendored, no node_modules) does not attest e2e_vex_vendor::vendored_npm_patch_with_an_unwired_registry_copy_in_the_same_lock FAILED: exit 0, not_affected ok
CLI vendor --check fails naming packages/b/node_modules/lodash same test, run with only the VEX commits applied FAILED: exit 0, vendor_check_ok ok

The red runs were made in a separate worktree on origin/main with only the new tests copied in. The vendor --check row 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's clippy flagged on 5ce219e / 8d1a570; it's removed in c7479de.
  • rustfmt --check: every hunk this PR adds is clean. main already 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-features on e2e_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_build and e2e_vendor_bun_build with --include-ignored: 17 + 14 passed.
  • A full cargo test --workspace doesn'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)

Per-issue checklist

🤖 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@version to 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.

vex now treats that registry entry as a same-lock contest (like bundled copies already did). Discovery drops the patch ref with patched_ref_unattributable, names the unwired lock path, and tells users to re-run vendor / scan. npm uses npm_lock_located_nodes and maps unwired purls to locations; Bun adds an Unwired pass after classification for bun.lock / bun.lockb.

vendor --check for package-lock entries runs new wiring audit check_wiring: every rewritable packages instance must point at file:<vendored artifact>; drift fails with vendor_check_failed and names the offending entries.

CHANGELOG and CLI_CONTRACT document contested-lock and --check behavior. Regression tests cover npm/Bun discovery and CLI vex + vendor --check.

Reviewed by Cursor Bugbot for commit edc7c8b. Configure here.


Generated by Claude Code

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
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] CI: native (ubuntu-latest, 1.4.2) (Bun) failed on a0a210c. 51 of 52 cells passed. The only failing cell is 1.4.2 hoisted hosted, which ran out of its 3 transport retries.

I don't think this PR caused it:

  • Attempt 1 failed with Network error … patches-api.socket.dev/patch/batch: Connection reset by peer. Dozens of other cells in the same job hit the same resets and passed on retry.
  • The failing step moved on every attempt: cliSuccess, then vexApplyEmbedded, then repeatClean. Attempt 3 got past the vex steps, which are the only Bun code this PR changes (the same-lock contest in vex/discover/bun.rs), so the hosted ref was still attested there.

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

@mikolalysenko Mikola Lysenko (mikolalysenko) changed the title Fix npm VEX attesting a patch a same-lock copy skips (#588) Fix npm/Bun VEX attesting a patch a same-lock copy skips (#588) Oct 2, 2026
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 2, 2026 18:57
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] CI: scan performance failed on a0a210c. Every scenario's timing is unchanged. The job fails only because both yarn-berry scenarios are invalid on the base binary as well as the head: redirect.rewrittenFiles: got ["package.json", "yarn.lock"], want ["yarn.lock"].

This isn't caused by this PR, which doesn't touch yarn berry or the benchmark harness. The benchmark fixture (crates/socket-patch-bench/src/fixtures/npm.rs, build_yarn_berry) still expects only yarn.lock, but since #465 hosted Berry redirects also pin package.json. So the check is red against main itself.

A fixture-only fix already exists in #587 (329b146, "test: align Berry benchmarks with descriptor resolutions"), and scan performance passes on that commit. I haven't ported it into this PR: my session's safety policy blocks running another branch's code here. A maintainer can either let #587 land first and merge main in, or cherry-pick 329b146 onto this branch.


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ 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.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 2, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[burn-down agent] Labeled Ready for review at edc7c8b8ef2bea094f04c88928c342ee133e6639.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Reviewed edc7c8b8ef2bea094f04c88928c342ee133e6639. Ready to merge as-is; I found no actionable correctness or security issues.

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 045d7ec7 is clean.

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

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

2 participants