Skip to content

npm hosted and vendored modes refuse a registry-installed package when its dependent's git spec is replaced by an overrides entry (regression from #345) #490

Description

[agent] Found by the scheduled npm bug-hunt routine (ledger #302).

Summary

The #326 fix (#345, 6e7ef74) decides that an npm lock entry is "not installed from the registry" when any dependent's raw spec for that name is a git, URL or file: spec (npm_non_registry_entries, crates/socket-patch-core/src/vendor/npm_origin.rs:71-99). It doesn't take root overrides into account. When a transitive dependency declares left-pad: "github:…" and the project overrides it with "overrides": {"left-pad": "1.3.0"}, npm installs left-pad@1.3.0 from the registry. The lock records that: resolved is https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz, with registry integrity. The dependent's dependencies entry still shows the git spec, because npm copies it from the dependent's package.json.

socket-patch on main then:

  • hosted (scan, the v5 default): skips the entry with redirect_npm_non_registry_entry_skipped ("npm installs it from that spec, so that copy stays UNPATCHED; depend on the registry release to patch it"), redirects 0 packages and exits 0 with status: success;
  • vendored (scan --mode vendored): fails with vendor_lock_entry_not_rewritable, exit 1;
  • vex: refuses to attest the package even when the lock was correctly redirected (by v4.0.0) and the patched bytes are installed (no_applicable_patches, exit 1).

Overriding a git dependency with the registry release is exactly what the warning tells users to do, yet the warning still fires afterwards.

Impact

A project that pins a git-sourced transitive dependency back to the registry through overrides can't get hosted or vendored patches for that package at all. v4.0.0 redirected it correctly, and npm ci installed the patched bytes. The hosted path exits 0, so a CI job that runs socket-patch scan reports success while the vulnerable package stays unpatched. The only signal is a warning whose diagnosis is wrong.

Repro (Linux, npm 10.9.4 / Node 22; same on npm 8, 11, 12)

mkdir -p pkga app
echo '{"name":"pkga","version":"1.0.0","dependencies":{"left-pad":"github:stevemao/left-pad#v1.3.0"}}' > pkga/package.json
echo 'module.exports=require("left-pad")' > pkga/index.js
(cd pkga && npm pack) && mv pkga/pkga-1.0.0.tgz app/ && cd app
echo '{"name":"app","version":"0.0.0","private":true,"dependencies":{"pkga":"file:pkga-1.0.0.tgz"},"overrides":{"left-pad":"1.3.0"}}' > package.json
npm install
# lock: node_modules/left-pad resolved=https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz
#       node_modules/pkga dependencies.left-pad = "github:stevemao/left-pad#v1.3.0"
socket-patch scan --json --yes   # with a free patch for pkg:npm/left-pad@1.3.0

The patch source was a local mock of the patch API (--api-url / --patch-server-url pointed at it) serving one free patch for pkg:npm/left-pad@1.3.0.

Actual on main 61cfb9b:

"status": "success", exit 0
"redirect": {"mode":"hosted","redirected":0,"rewrittenFiles":[],
  "warnings":[{"code":"redirect_npm_non_registry_entry_skipped",
   "detail":"lock entry `node_modules/left-pad` is not installed from the registry (`node_modules/pkga` depends on it as \"github:stevemao/left-pad#v1.3.0\", which npm installs from that spec) and CANNOT be redirected — npm installs it from that spec, so that copy stays UNPATCHED; ..."}]}

package-lock.json is unchanged, and after rm -rf node_modules && npm ci, node_modules/left-pad/index.js is the original. scan --mode vendored: partialFailure, exit 1, vendor_lock_entry_not_rewritable with the same reason.

v4.0.0 (scan --mode hosted) on the same project: redirected: 1, the lock pin is rewritten, and a fresh npm ci installs the patched index.js. v4.0.0's vex attests not_affected. Main's vex on that v4-wired, correctly patched tree returns no_applicable_patches (exit 1).

Expected vs actual

Matrix (Linux sandbox, real npm installs)

npm lockfileVersion main 61cfb9b hosted main vendored v4.0.0 hosted
8.19.4 2 fail (skipped, exit 0) — —
10.9.4 3 fail (skipped, exit 0) — pass (npm ci installs patched bytes)
11.21.0 3 fail (skipped, exit 0) fail (vendor_lock_entry_not_rewritable, exit 1) —
12.2.0 (Node 24.21) 3 fail (skipped, exit 0) — —

macOS and Windows weren't probed; the classification is pure lock-JSON logic with no OS-specific paths.

First bad

#345 (6e7ef74, "Fix npm rewiring git/URL/file lock entries (#326)"). v4.0.0 predates it and works.

Suspect code

crates/socket-patch-core/src/vendor/npm_origin.rs:71-99: the edge loop marks the resolved target non-registry whenever !npm_spec_is_registry(spec), without checking the target entry's own resolved (a registry tarball URL here) or the root overrides. One option: when the edge's target entry has a registry resolved + integrity (not git/file:) and the root package.json overrides cover the name, treat it as a registry copy. Or more simply, trust a registry tarball resolved over a non-registry edge spec only when an override applies, since #326 showed that resolved alone isn't authoritative without one.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions