Fix npm VEX attesting packages with an unpatched bundled copy (#325) - #337
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A project can install the same name@version twice: the lock entry the hosted or vendored rewriter points at Socket's patched tarball, and a copy bundled inside another package (inBundle / bundled). npm unpacks the bundled copy from its parent's tarball, so it stays unpatched, and the rewriters already warn about that. VEX still attested the package not_affected, because lockfile discovery skipped bundled entries entirely. Discovery now records each bundled entry's name@version. A Socket reference with a bundled copy of the same version in either npm lock is reported as patched_ref_unattributable, naming the bundled location, and is not attested. The bundled copy also contests the same package in other lockfiles. This covers the in-run scan --vex, lock-basis and post-install vex runs in hosted and vendored mode. Fixes #325 Assisted-by: Claude Code:claude-opus-5-5
Adds the CHANGELOG entry and the CLI contract rule: a bundled npm copy of a patched name@version keeps that patch from being attested. Refs #325 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Two jobs failed on 2aa1f21, both in suites this PR doesn't touch (the diff is limited to
I've re-run the failed jobs once. If either fails again, I'll treat it as real and dig in. Generated by Claude Code |
Main's v5 consolidation (#277) rewrote the npm discover imports, the lock_inventory re-exports, the CHANGELOG's Unreleased section and the CLI contract, so the #325 fix no longer applied cleanly. Keep main's structure and re-apply the fix on top: the bundled-node walk export and import, the bundled-copy contest in push_uncontested, a condensed Unreleased Fixed entry, and the bundled sentence on the Contested locks rule. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQoii5oP1pwcJh5mzzo1HU
|
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 00495f0. Configure here.
|
[burn-down agent] Ready for review on Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #325
Summary
If a lock rewires
name@versionto a Socket patch and another package also bundles that samename@version,vexandscan --vexno longer attest itnot_affected. The bundled copy isinBundle: truein v2/v3 locks orbundled: truein v1. npm unpacks it from the parent's tarball, so no rewire reaches it and it stays unpatched. The reference is now reported aspatched_ref_unattributable, the warning names the bundled copy's lock location, and the reference isn't attested. That matches how the contract already treats "another lock resolves the samename@versionfrom a non-Socket source".Changes:
vendor/lock_inventory/npm.rs: the shared npm lock walk is split on the bundled flag.npm_lock_nodesis unchanged. The newnpm_lock_bundled_nodesreturns the bundled entries with their location: thepackageskey, or the>-joined v1 dependency chain.vex/discover/npm.rs: each npm lock records its bundled purls, andpush_uncontesteddrops any ref whose purl has a bundled copy in either lock of the npm pair. Bundled copies also count asresolved_elsewhere, so they contest the same version in other lock types too. The dropped uuid stays recognized, so a matching ledger record is dead as well (redirect_unwired/vendor_unwired), under--no-verifytoo.Fixedentry, and the CLI_CONTRACT "Contested locks" rule.Root cause
npm_lock_nodesskips bundled entries, which is correct for the rewriters. That meant VEX discovery never saw the unpatched bundled copy.contest_across_locksonly contests refs from a different file, so the hoisted rewired entry was attested even though the same run warned*_bundled_instance_skipped("that copy stays UNPATCHED").Test evidence
mainvex::discover::npm::tests::bundled_copy_of_the_same_version_contests_the_wired_entry(also checks that a bundled copy of a different version contests nothing)bundled: truev1_bundled_copy_contests_the_wired_entrybundled_copy_in_the_sibling_npm_lock_contests_the_refvex, lock basis and after install (hoisted patched, bundled unpatched)e2e_vex_vendor::vendored_npm_patch_with_an_unpatched_bundled_copy_is_not_attested(plus a no-bundle control that still attests)status: success,not_affected)vex, lock basis / ledger (--no-verify)e2e_vex_redirect::hosted_npm_patch_with_an_unpatched_bundled_copy_is_not_attested(plus a control)The in-run
scan --vexgoes through the same generator (generate_vex_from_manifest_path, which callsdiscover_wiring), so it shares the gate. The hosted post-install cell already omitted the purl onmain, and it still does.Red was shown by stashing the two core source files and re-running the new tests against
main's implementation.cargo test -p socket-patch-core --all-features --lib -- vex::discover lock_inventory: 435 passed.SOCKET_PATCH_NPM_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_redirect_npm_build -- --ignored(real npm 10, Node 22): 5 passed.cargo test -p socket-patch-cli --all-features --test e2e_vendor_npm_build -- --include-ignored: 14 passed.cargo clippy --workspace --all-features -- -D warnings: clean.cargo fmt --all -- --check: the new code is rustfmt-clean. The remaining diffs are already onmain, in files and hunks this PR doesn't touch.cargo test --workspace --all-features --no-fail-fast: 10,397 passed and 22 failed. The 22 are the same sandbox-only set reported on Fix #258: state the real Maven Trusted Checksums floor #322 and Fix Poetry venv discovery to match Poetry (#327, #329) #330: write-failure and permission tests that fail because the sandbox runs as root, plus the peak-RSSstage_local_artifact_caps_oversized_artifact_before_buffering. None of them are in VEX or npm code.test-releaseandtest (windows-latest)failed in suites this PR doesn't touch (vendor_gem_lockfile_only_e2eandupdate_notifier_e2e; see the comment above). Both passed on their single rerun.Follow-up (not needed for #325)
The vendored out-of-sync probe in
vex/verify.rsstill checks only the onepackage_pathscopy for each purl. With this change, a bundled copy can no longer produce an attestation. The other duplicates of the same version are ordinary nested entries, and the vendor backend rewires all of those. So the single-copy probe only limits how complete thevendored_tree_out_of_syncwarning is, not whether the attestation is correct.Separately,
vendor_gem_lockfile_only_e2e'sdead_endpoint()releases its port before use, so tests running in parallel can race for it. The comment above proposes a fix for its own PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01BAB2TPeepsb616tuH3Nd3S
Note
Medium Risk
Changes npm VEX discovery and attestation outcomes (fail-closed when bundled copies exist), which affects security attestations but aligns them with actual install bytes.
Overview
VEX and
scan --vexno longer attest an npmname@versionwhen the lock also installs an unpatched bundled copy of that same version (inBundle: truein v2/v3 locks, or v1bundled: true). npm unpacks bundled deps from the parent tarball, so Socket rewires never reach that copy even when the hoisted entry is patched.Discovery now indexes bundled lock entries via
npm_lock_bundled_nodesand treats them like other contested installs: the Socket ref is dropped withpatched_ref_unattributable, naming the bundled path, across hosted and vendored wiring and either npm lock in a shrinkwrap/package-lock pair. Bundled purls also register as resolved elsewhere, so they contest the same version in non-npm locks. Matching ledger records stay unwired (redirect_unwired/ vendor equivalent) under--no-verify.CHANGELOG and CLI_CONTRACT document the new contested-lock rule; unit and e2e tests cover v1/v3 locks, sibling locks, hosted redirect ledgers, and vendored lock-basis vs installed trees.
Reviewed by Cursor Bugbot for commit 00495f0. Configure here.
Generated by Claude Code