Fix npm rewiring git/URL/file lock entries (#326) - #345
Mikola Lysenko (mikolalysenko) merged 8 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
npm installs a git, remote-tarball or file: dependency from the dependent's spec and ignores the lock entry's resolved. Hosted and vendored mode rewired those entries anyway, so the scan reported the package patched and vex attested it while npm ci installed the original bytes. Both rewriters now skip such entries with a loud stays-UNPATCHED warning (vendored refuses when no registry copy is left), and vex no longer attests a name@version while a non-registry copy of it is in the lock. Fixes #326 Assisted-by: Claude Code:claude-opus-5-5
4a797e2 to
ad605fe
Compare
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
npm 12 refuses remote-tarball specs unless allow-remote permits them, so the new vendored e2e now passes --allow-remote=all there. The VEX test messages and the new diagnostic no longer print patch URLs or uuids. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent]
This PR only changes npm lock handling and npm VEX discovery; no PyPI or Poetry code is touched. I've re-run the failed jobs of run 36755620097 once. If it fails again, I'll treat it as a real failure and dig in. Generated by Claude Code |
|
[agent]
This PR doesn't change any vlt code. I can't re-run the job yet because workflow run 36755620093 still has jobs in progress (the API returns 403). I'll re-run it once when the run finishes, and treat a second failure as real. Generated by Claude Code |
The hosted rewriter and the vendored backend skipped a git, URL or file: packages entry, but still rewired its lockfileVersion 2 legacy dependencies mirror when that mirror stored the plain version. Each legacy node now maps to the packages key it mirrors, and a node whose twin is non-registry is left alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LycgFGuki2BqZ25VNhwxJ5
|
[agent]
This PR only changes npm lock handling and npm VEX; no Composer code is touched. I can't re-run it yet because run 36758922826 still has jobs in progress (403). I'll re-run it once when the run finishes, and treat a second failure as real. Generated by Claude Code |
|
BugBot review Generated by Claude Code |
Bring #326's fix onto the v5 workflow from #277 so the PR merges cleanly. Main dropped the pre-index npm/yarn oracles from the lock index equivalence tests, so the PR's oracle tweak goes with them; the vex npm discover imports follow main's move to formats::pnpm and keep the npm_origin import; the changelog entry moves into main's condensed Unreleased "Fixed" section. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQoii5oP1pwcJh5mzzo1HU
|
bugbot run Generated by Claude Code |
|
[burn-down agent] Ready for review on Generated by Claude Code |
Union the CHANGELOG Fixed entries from both sides. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
Co-Authored-By: Claude <noreply@anthropic.com>
|
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 f1e0108. Configure here.
|
[agent] Ready for review on
Slack announcement not sent this run: the Slack send tool isn't available in the agent session. The next run will retry. Generated by Claude Code |
Keeps both CHANGELOG entries (gem manifest fix and the npm non-registry entry fix from #345). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6i4cQ51yarBFnFs2b8HRx
LLM Description written by Claude Code:claude-opus-5-5
Fixes #326
Summary
scan --mode hostedandvendorno longer rewire an npm lock entry that npm installs from a git, remote-tarball (URL) orfile:spec, andvexno longer attests one. Before this change both modes rewrote that entry'sresolved/integrityand reported the package patched, andvexattestednot_affected. Meanwhilenpm cifetched the git checkout or URL again and installed the original bytes.Changes:
vendor/npm_origin.rs(new):npm_non_registry_entries(lock)returns thepackageskeys npm installs from a non-registry source, each with a reason. An entry counts as non-registry in two cases:npm_spec_is_registrymirrors npm-package-arg: versions, ranges, dist-tags andnpm:aliases count as registry; git, GitHub shorthand, URLs, paths and tarball names don't. The edges come from every entry'sdependencies/optionalDependencies/devDependencies/peerDependencies, including the root and workspace members, resolved in node's lookup order.resolvedisgit+…/github:…/… or afile:outside.socket/vendor/.The spec check is what catches a URL spec whose
resolvedlooks exactly like a registry tarball. It also catches an entry that has already been rewired: our own wiring only rewritesresolved, never the dependent's spec.Hosted (
patch/redirect/mod.rs): such an entry is skipped withredirect_npm_non_registry_entry_skipped, a stays-UNPATCHED warning that names the entry and the spec. It still counts as a match, soredirect_npm_entry_not_foundstays quiet.Vendored (
vendor/npm_lock.rs): such an entry is skipped withvendor_non_registry_entry_skipped. When no rewritable copy is left, the vendor refuses withvendor_lock_entry_not_rewritable(the existing bundled/link refusal, with its wording widened) and writes nothing.v2 legacy mirror (both modes): in a lockfileVersion 2 lock, each legacy
dependenciesnode maps to thepackageskey it mirrors (legacy_packages_key). A node whose twin is non-registry is left alone, even when it stores the plain version.VEX (
vex/discover/npm.rs): each lock drops every ref whosename@versionhas a non-registry copy in that lock, diagnosed aspatched_ref_unattributable. That copy also counts as resolved elsewhere, so it contests the same version in the sibling npm lock and in other lock types. This covers locks that were rewired before this fix, and a registry copy that is wired while a git copy of the same version stays unpatched next to it.Docs: a CHANGELOG
Fixedentry, and the CLI_CONTRACT npm row.Root cause
The npm lock rewriters pick entries by name and version only:
rewrite_one_npm_lockandrewrite_npm_v2_depsfor hosted,scan_lock_matchesandrewrite_legacy_treefor vendored. Onlylink/inBundleentries are excluded. npm installs a git, URL orfile:dependency from the dependent's spec and ignores the lock'sresolved, so the rewrite had no effect at install time. VEX discovery trusted the rewiredresolvedin the same way.Not affected: lockfileVersion 1. There, a git/URL/
file:entry'sversionis the spec itself, so it never matches a patch'sname@version.Test evidence
mainfile:tarball (direct)patch::redirect::tests::npm_non_registry_entries_are_skipped_with_loud_warningnpm_nested_git_copy_is_skipped_and_registry_copy_rewirednpm_v2_legacy_mirror_of_a_non_registry_entry_is_not_rewiredfile:(refuses, writes nothing)vendor::npm_lock::tests::non_registry_only_instances_refuse_and_write_nothingnested_git_instance_is_skipped_with_warningv2_legacy_mirror_of_a_git_instance_is_not_rewrittenfile:spec, nested git copy contests the wired copy, plus a registry controlvex::discover::npm::tests::entries_npm_installs_from_a_non_registry_spec_are_not_attestedvendorthenvexe2e_vendor_npm_build::npm_vendor_refuses_a_remote_tarball_dependencyapplied: 1)vendor::npm_origin::tests::*(9 tests: spec classification, node lookup, workspaces, aliases)Red was shown by stashing the three core source files and re-running the new tests against
main's implementation. For the two v2-mirror tests, it was shown by disabling the new guard.cargo test -p socket-patch-core --all-features --lib -- npm vex::discover redirect: 1494 passed and 1 failed. The failure isvlt_heal::tests::an_unremovable_hidden_lock_keeps_every_store_entry, which fails on the unchanged branch too (the sandbox runs as root).SOCKET_PATCH_NPM_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_vendor_npm_build --test e2e_redirect_npm_build -- --include-ignored(real npm 10.9.7, Node 22): 14 + 15 passed. The new e2e also passes against npm 12.1.0.EALLOWREMOTE). That made the new e2e fail its fixture install in CI'sinstall-proof (12.x)legs, so it now passes--allow-remote=allon npm ≥ 12.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(on d53ef13): 10,407 passed and 22 failed. The 22 are the same sandbox-only set reported on Fix #258: state the real Maven Trusted Checksums floor #322, Fix Poetry venv discovery to match Poetry (#327, #329) #330 and Fix npm VEX attesting packages with an unpatched bundled copy (#325) #337: 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 npm or VEX code.install-proof(npm registry) and macOS Poetry 1.8.5 (PyPI).scan --vexgoes through the same discovery gate.Notes
vex/discover/npm.rsnext to Fix npm VEX attesting packages with an unpatched bundled copy (#325) #337, which adds a same-shaped gate for bundled copies. The two edit different functions (extract_package_lockhere,push_uncontestedthere), so whichever lands second should need at most a trivial merge.overridesentry forces a URL spec onto a registry-range edge, the lock alone doesn't show it. Theresolvedcheck still catches forced git andfile:sources.🤖 Generated with Claude Code
https://claude.ai/code/session_01LycgFGuki2BqZ25VNhwxJ5
Note
Medium Risk
Changes npm lock rewrite, vendoring, and VEX attestation gates for a common dependency pattern; incorrect classification could skip legitimate registry patches or still over-attest, but behavior is covered by extensive tests and fail-closed warnings.
Overview
Fixes false “patched” reporting for npm deps installed from git, URLs, or
file:specs (#326). npm honors the dependent’s spec, not the lock entry’sresolved, so rewiring those entries did not change whatnpm ciinstalled while hosted/vendored flows and VEX still claimed success.A shared
npm_non_registry_entriesclassifier (newvendor/npm_origin.rs) drives consistent behavior: hosted scan skips withredirect_npm_non_registry_entry_skipped, vendor skips or refuses withvendor_non_registry_entry_skipped/vendor_lock_entry_not_rewritable, and VEX discovery drops attestations for affectedname@versionpairs (patched_ref_unattributable). Lockfile v2 legacydependenciesmirrors of skipped entries are left untouched. Docs and e2e/unit tests cover git, URL tarball,file:, and mixed registry/non-registry trees.Reviewed by Cursor Bugbot for commit f1e0108. Configure here.
Generated by Claude Code