Repository navigation
Fix npm overrides to a URL treated as registry (#1965) - #1966
Mikola Lysenko (mikolalysenko) wants to merge 8 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A registry range that the root package.json overrides with a remote tarball URL, git or path spec is installed by npm from that spec, not from the lock's resolved URL. These tests show the copy is not yet treated as a non-registry install, so hosted and vendored scans rewire it and VEX attests a patch that npm ci never installs (#1965). Assisted-by: Claude Code:claude-opus-5-5
When the root package.json overrides a registry range with a remote tarball URL, git or path spec, npm installs that spec and ignores the lock's resolved URL. Hosted and vendored scans now treat such a copy like any other non-registry install: hosted skips it with redirect_npm_non_registry_entry_skipped, vendored refuses it, and VEX no longer attests a patch that npm ci never installs. When the winning override can't be decided exactly, any rule for the package with a non-registry value keeps the copy unpatched and reported, matching the module's cautious rule for unclear cases. Fixes #1965 Assisted-by: Claude Code:claude-opus-5-5
Test the hosted rewriter, the vendored lock writer and VEX lockfile discovery against a registry range that the root overrides send to a remote tarball URL: hosted skips it loudly, vendored refuses it, and VEX attests nothing (#1965). A pin an older release wrote at such a copy is still the rewriters' own output, so VEX discovery keeps it shadowed and rollback and remove can still unwind it. Assisted-by: Claude Code:claude-opus-5-5
Real npm installs a local foo that depends on left-pad ^1.3.0 while the root overrides send left-pad to a remote tarball URL. The hosted scan now skips that copy with redirect_npm_non_registry_entry_skipped, leaves the lock byte-identical and attests nothing (#1965). Assisted-by: Claude Code:claude-opus-5-5
Settle registry edges whose package has no non-registry override rule before walking the dependent's chain, so large locks with an unrelated overrides block don't pay a per-edge search. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
npm reaches a workspace member through the root's link edge, so an override keyed by the member's name applies to its dependencies. The override lookup now starts a dependent's ancestor chain with its workspace member, so a URL override scoped to a member skips that member's copy instead of rewiring and attesting it. Found in review. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
A member without a name field keeps its @scope/ folder parent, and only the root-level link entry names a member: a nested link to the same folder may be an alias. Member-scoped overrides now match the member's real name. Found in review. Assisted-by: Claude Code:claude-opus-5-5
|
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 fa843ed. Configure here.
|
Labeled Ready for review (burn-down agent).
Generated by Claude Code |
Final review: one gap to settle before this merges (
|
Wenxin Jiang (Wenxin-Jiang)
left a comment
There was a problem hiding this comment.
[agent] Reviewed fa843ede4f3ab84c19f72176979069deafc5e563. Checked URL override detection, workspace-dependent names, conservative ambiguous rules and unwind ownership. No major correctness issues found. Validation: source and regression-test review plus the available exact-head CI checks; no local test pass is claimed.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1965
Summary
When the root
package.jsonoverrides a registry range with a remote tarball URL, git or path spec, npm installs that spec and ignores the lock'sresolved. Hosted and vendored scans now treat that copy like any other non-registry install. Hosted skips it withredirect_npm_non_registry_entry_skipped, vendored refuses it (vendor_lock_entry_not_rewritable), and VEX lockfile discovery attests nothing for it. Before this change, both modes rewired the copy and exited 0,npm cithen installed the unpatched upstream bytes, and vendoredvex/vendor --checkand the hosted in-run--vexstill attested the patch.Root cause
npm_non_registry_entries(crates/socket-patch-core/src/vendor/npm_origin.rs) skipped every edge whose declared spec is a registry spec before looking atoverrides. It only handled the opposite direction (#490: a git/URL spec that an override turns into a registry one). A remote-tarball override also gets past theresolvedcheck, because itsresolvedis an ordinaryhttps://…tgz. The hosted rewriter, the vendored lock writer and VEX discovery all read this one function, so every mode was affected.Fix
NpmOverrides::non_registry_replacement: for a registry-spec edge, returns the non-registry spec an override makes npm install. When the winning rule is decided exactly, its value counts. When it isn't decided (a target selector other than the spec, a range selector on an ancestor, or equally close rules that disagree), any rule for the package with a non-registry value counts. That follows the module's existing rule for unclear cases: leave the copy unpatched and reported, never attest it.dependent_chainnow starts a dependent inside a workspace member with that member (named by itsnamefield, else its root-level link entry, else its folder with any@scope/parent), because npm reaches members through the root's link edge and applies member-scoped rules to them (this also makes the npm hosted and vendored modes refuse a registry-installed package when its dependent's git spec is replaced by anoverridesentry (regression from #345) #490 direction recognize member-scoped registry overrides). A fast path returns early when no rule for the package names a non-registry spec, so locks with unrelatedoverridesdon't pay a per-edge chain walk.npm_override_only_entries: lists the entries flagged only because of such an override. VEX discovery keeps a Socket pin at one of those entries shadowed, not foreign, sorollback/removecan still unwind a pin that a release before this fix wrote there. Without this, those projects would be left with a hosted pin nothing unwinds.npm/,pypi/,gem/only dispatch to the binary).Tests (red → green)
Every new test fails with the fix neutralized (
non_registry_replacementforced toNone) and passes with it:vendor::npm_origin::tests::issue_1965_an_override_of_a_registry_edge_to_a_url_is_non_registryfoo,foo@1.0.0),"."object,$name,dep@specselector; URL/git/file:/path/npm:alias-to-URL; fresh, hosted-pinned and vendored-wiredresolved…issue_1965_an_unclear_override_that_may_swap_in_a_url_keeps_the_edge$refbeside a URL rule…issue_1965_a_url_override_scoped_to_a_workspace_member_is_non_registryapp,app@2.0.0) reaches the member's deps; a non-matching version or another member doesn't (Bugbot finding on 70d9aff)…issue_1965_a_workspace_member_is_named_as_npm_names_itnamekeeps its@scope/folder; only the root-level link names a member, not a nested alias link (second Bugbot finding, on 2df1c47)…issue_1965_a_url_override_of_a_nested_registry_edge_names_its_copy…issue_1965_registry_and_no_op_overrides_of_a_registry_edge_stay_registry*, empty, non-applying and shadowed rulespatch::redirect::tests::issue_1965_a_registry_edge_overridden_to_a_url_is_unredirectedvendor::npm_lock::tests::issue_1965_a_registry_edge_overridden_to_a_url_is_refusedvex::discover::npm::tests::issue_1965_a_registry_edge_overridden_to_a_url_is_not_attestede2e_redirect_npm_build::npm_redirect_skips_a_registry_edge_overridden_to_a_url(ignored e2e)foo+ URL-override fixture: hosted scan skips loudly, lock byte-identical, nothing attestednot_affected. Red before the fix (scan reportedapplied)Per-issue checklist
overridesforces to a remote tarball URL;npm ciinstalls it unpatched, and vendoredvex/vendor --checkstill pass #1965 hosted:patch::redirect::tests::issue_1965_…,e2e_redirect_npm_build::npm_redirect_skips_a_registry_edge_overridden_to_a_urloverridesforces to a remote tarball URL;npm ciinstalls it unpatched, and vendoredvex/vendor --checkstill pass #1965 vendored:vendor::npm_lock::tests::issue_1965_…overridesforces to a remote tarball URL;npm ciinstalls it unpatched, and vendoredvex/vendor --checkstill pass #1965vex/vendor --checkattestation:vex::discover::npm::tests::issue_1965_…allow-remote=all): the logic readspackagesonly (lockfileVersion ≥ 2) and doesn't depend on the npm version. The e2e runs on whatever npm the CI matrix leg pins.Local verification
cargo fmt --all -- --check: okcargo clippy --workspace --all-features -- -D warnings: okcargo test --workspace --all-features --no-fail-fast: everything passes except 11 tests that fail for environmental reasons, all in code this PR doesn't touch. The sandbox runs as root, so 9 chmod/unremovable write-failure tests can't make writes fail (in_process_redirect×3,repair×2, corecopy_tree/vlt_heal/pypi_poetry/pypi_requirements). The other 2 (e2e_bun_lockb) fetch blobs from the livepatches-api.socket.dev, which the sandbox can't reach (see Fix bun.lockb nightly e2e reaching the live blob API #1905). CI runs as non-root.SOCKET_PATCH_NPM_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_redirect_npm_build -- --ignored npm_redirect_skips_a_registry_edge_overridden_to_a_url: ok (npm 10.9.9)Generated by Claude Code