Repository navigation
Fix npm VEX attesting a patch the twin lock lacks (#798) - #799
Conversation
Assisted-by: Claude Code:claude-opus-5-5
With both npm-shrinkwrap.json and package-lock.json committed, lockfile-only `vex` attested a patch that only one lock wired when the other lock had no entry for the package at all. npm 12 installs from package-lock.json and re-resolves a missing entry from the registry, so the checkout installed unpatched bytes while the VEX document said `not_affected`. A twin lock with no entry for the package now contests the wiring the same way a registry entry does (`patched_ref_unattributable`), in both directions and for hosted and vendored wiring. A twin that holds the package only at another version still contests nothing: npm installs that version, not unpatched bytes of the patched one. Fixes #798 Assisted-by: Claude Code:claude-opus-5-5
The test that proves both npm locks are read wired each package in only one lock. After #798 such a pair is contested (npm re-resolves the package missing from the other lock), so the fixture now has each lock wire both packages. It still proves both locks are read (4 refs) and that the v2 legacy mirror adds nothing. Refs #798 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
CodeQL flagged the new dual-lock test for printing the wired patch reference (which holds the patch uuid) in an assertion message. The message now names the wiring mode instead. Refs #798 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] CI status for the native compatibility suites:
Generated by Claude Code |
|
Ready for review (burn-down agent).
Generated by Claude Code |
Conflict only in the NpmLockRefs doc comment (vex/discover/npm.rs): kept main's bundled-location wording plus this PR's package-name set; main's unwired→BTreeMap change and #588 same-lock check combine with the missing-entry contest unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
#605 taught the name-keyed npm resolver to probe bundled store trees, so it now finds aliased copies (node_modules/lp) and a nested host's store peers itself. Two vex_consumed tests from #738 assumed that set never held aliases, so main's CI went red after both merged. The tests now feed the alias-free set explicitly to keep covering alias expansion, and also check the resolver's own set reaches the same copies with no duplicates. No production code changes. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 40dac07)
|
[agent] This PR didn't cause it. Current I pushed Generated by Claude Code |
A twin lock that held the package only at another version still let the wired ref through. npm keeps that entry only while it satisfies package.json, and otherwise fetches the wired version unpatched from the registry, so the lock alone can't vouch for it. The twin now contests unless it has an entry for the same name@version at any path. Lock pairs the rewriters keep in sync share that set, so they are unaffected. Refs #798 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TtZrsd52E6vxhvF9hpLVSw
|
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 375f576. Configure here.
|
Burn-down agent: labeled Ready for review.
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #798
Summary
Lockfile-only
vexno longer attests a patch when only one ofnpm-shrinkwrap.json/package-lock.jsonwires it and the other lock has no entry for the package. It now refuses withpatched_ref_unattributable, the same way it already handled a twin lock with a registry entry.Root cause
vex::discover::npm::push_uncontestedcounted the twin lock as contesting a wired package only when the twin had a non-Socket entry for the samename@version. Its doc comment said "The lock that does not mention the package at all contests nothing." That's wrong for npm. npm 12 installs frompackage-lock.jsonbeside a committed shrinkwrap and re-resolves a missing entry from the registry, so the checkout installs unpatched bytes while VEX saysnot_affected. npm <= 11 does the same with a stale shrinkwrap.Change
NpmLockRefsnow records every package name each lock has any entry for: regular, bundled and non-registry entries, with aliases resolved to the real name.push_uncontested: a ref is contested when another npm lock has no entry for the package's name. The diagnostic names the twin and says it has no entry for the package.docs/testing/npm-compatibility.mddocuments the rule.vendor_npm_sibling_lock_unwired, hosted "No package-lock.json entry") are unchanged. The issue's expected behaviour ("vexshould refuse here as well") is met at the attestation boundary.Why this issue: it's a p1 false VEX attestation (a correctness/security bug) with a small, self-contained fix in VEX discovery. It doesn't touch the files the many open gem/pypi agent PRs edit.
Test evidence
vex::discover::npm::tests::a_sibling_npm_lock_without_the_package_contests_itsocket-patch vexbinary, hosted + vendored × stale package-lock / stale shrinkwrap)e2e_vex_lockfile npm::dual_lock_with_one_lock_missing_the_package_attests_nothingvex::discover::npm::tests::a_sibling_npm_lock_with_another_version_contests_nothingboth_locks_are_read_and_the_agreeing_mirror_adds_nothingused disjoint twins, which is exactly the #798 shape. It now uses agreeing twins and still proves both locks are read (4 refs) and that the v2 legacy mirror adds nothing.Commands run locally (Linux, toolchain 1.93.1):
cargo clippy --workspace --all-features -- -D warnings: clean.cargo fmt --all -- --check: the touched files are clean.mainalready has unrelated rustfmt diffs in other files, and CI has no fmt gate.cargo test -p socket-patch-core --all-features: 5393 passed, 5 failed. One was the fixture above, now fixed:vex::is 607/607. The other four (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_…) depend on read-only directories, which don't hold when the sandbox runs as root. They don't touch this code.cargo test -p socket-patch-cli --all-features --test e2e_vex_lockfile: 287/287.--test e2e_vex_redirect28/28,--test hosted_memory_engine28/28,--test scan104/104,--test vendor81/81.cargo test --workspacecould not finish locally because the sandbox disk ran out linking ~240 test binaries. CI runs the full set.npm/,pypi/,gem/) aren't affected: this is Rust core VEX discovery only.🤖 Generated with Claude Code
https://claude.ai/code/session_01TtZrsd52E6vxhvF9hpLVSw
Note
Medium Risk
Changes npm dual-lock VEX discovery at the attestation boundary; wrong logic would still allow false
not_affectedclaims, though coverage is extensive.Overview
Lockfile-only vex no longer attests when one of
npm-shrinkwrap.json/package-lock.jsonwires a Socket patch and the twin lock has no entry for thatname@version(or only a different version). Those cases emitpatched_ref_unattributablewith messaging that npm may re-resolve from the registry depending on major, matching the existing “sibling resolves elsewhere” rule.Discovery now tracks every
name@versionmentioned in each lock (NpmLockRefs.mentioned, populated from normal, bundled, and non-registry entries).push_uncontestedtreats a sibling that does notmentionsthe wired purl as contesting the ref. The samename@versionat another path in the twin still agrees and attests.Tests/docs: new unit cases for missing sibling entries, wrong-version siblings, and agreeing paths; e2e
dual_lock_with_one_lock_missing_the_package_attests_nothing;npm-compatibility.mdupdated.vex_consumedhosted npm tests were adjusted so alias expansion is still exercised after the name-keyed resolver (#605) finds aliases on its own.Reviewed by Cursor Bugbot for commit 375f576. Configure here.
Generated by Claude Code