Skip to content

Fix npm 6 installing unpatched aliases (#432) - #813

Merged
Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-npm-legacy-alias-mirror
Oct 5, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-npm-legacy-alias-mirror

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #432

Summary

A lockfileVersion 2 package-lock.json (written by npm 7 and 8) carries
two copies of every install: the packages map that npm 7+ reads, and
the legacy dependencies mirror that npm 6 reads. npm 6 spells an npm
alias install in that mirror as "lp": {"version": "npm:left-pad@1.3.0"}.
Before this change:

  • Hosted scan/get rewired only packages["node_modules/lp"]. The
    alias mirror node silently stayed on the registry.
  • Vendored did the same, with only a vendor_legacy_alias_skipped
    warning.
  • Lockfile-only vex attested not_affected from the packages
    half while npm 6 npm ci installed the unpatched registry tarball
    (exit 0).
  • With a lockfileVersion 1 alias lock, hosted scan pinned nothing
    (redirect_npm_entry_not_found, exit 0).

After this change:

  • Hosted rewires the alias mirror node like any other mirror node. npm 6
    then fails closed (EINTEGRITY) instead of installing unpatched bytes,
    and the run warns redirect_npm_legacy_alias_client.
  • Vendored rewires it, and npm 6 installs the patched bytes.
  • vex withholds the attestation (patched_ref_unattributable) while a
    v2 mirror still resolves the package from the registry. That is the
    state of any lock written before this fix.
  • A v1 alias lock gets pinned, with the existing
    redirect_npm_legacy_client npm 6 caveat.

Root cause

Every reader and writer of the legacy dependencies tree identified a
node by its key and version. An alias node's key is the alias and its
version is the npm:<name>@<version> spec, so the node never matched
the patched package. Four call sites had this: the hosted writer
(rewrite_npm_v2_deps), the vendored writer (rewrite_legacy_tree,
which noticed the alias but skipped it), the hosted rollback restorer
(upstream::npm::v2_hits, which would have looked up lp@npm:left-pad@1.3.0
on the registry), and the lock-inventory walk behind VEX for v1 locks.
VEX also never looked at the v2 mirror, so it could not see that npm 6
would install unpatched bytes.

Fix

  • lock_inventory::npm_legacy_identity is the single rule for what a
    legacy node stands for: npm:<name>@<version> decodes to the target,
    including scoped names. Anything else is the key at its own version.
    All four sites use it.
  • Hosted writer: alias mirror nodes are rewired. A new
    redirect_npm_legacy_alias_client warning fires for v2 locks when an
    alias node was rewired. v1 locks already carry
    redirect_npm_legacy_client, so the new warning is not repeated there.
  • Vendored writer: alias mirror nodes are rewired and recorded for
    revert. vendor_legacy_alias_skipped is gone.
  • VEX: new drop_mirror_unwired drops a packages ref when the v2
    mirror resolves the same package from a non-Socket source, and records
    the package as resolved elsewhere, so the sibling npm lock's ref is
    contested too.
  • Docs: docs/ecosystems.md and docs/testing/npm-compatibility.md.

What real npm does (probed locally, Node 22.22)

Lock Alias mirror node resolved npm 6.14.18 ci npm 8.19.4 / 10.9.4 ci
v2 registry (hosted before this fix) unpatched, exit 0 patched
v2 hosted URL (hosted after this fix) fails closed: EINTEGRITY (npm 6 refetches an alias from the registry) patched
v2 file:.socket/vendor/... (vendored after this fix) patched patched
v1 hosted URL (after this fix) fails closed (existing v1 caveat) patched

Hosted mode can't make npm 6 install an aliased hosted tarball. So the
fix makes that case fail closed and warn loudly, and the warning points
npm 6 users at vendored mode.

Test evidence

New tests, each red with the fix disabled (identity decode, no mirror
check) and green with it:

Test Without fix With fix
patch::redirect::tests::npm_v2_legacy_alias_mirror_is_rewired_and_warned (hosted v2, plain and scoped alias) FAILED ok
patch::redirect::tests::npm_v1_alias_entry_is_rewired (hosted v1 alias) FAILED ok
vendor::npm_lock::tests::v2_legacy_alias_node_is_rewired_and_reverted (vendored rewire and byte-exact revert) FAILED ok
vex::discover::npm::tests::v2_alias_mirror_left_on_the_registry_contests_the_ref (hosted and vendored) FAILED ok
vex::discover::npm::tests::lockfile_v1_alias_node_is_its_target FAILED ok
vex::discover::npm::tests::stale_alias_mirror_contests_the_sibling_lock_ref (Bugbot: a stale shrinkwrap mirror also contests package-lock.json) FAILED ok
redirect_golden case npm/package-lock-v3/legacy-alias-mirror-v2 (byte-exact rewrite, edits, warnings) FAILED ok
upstream_restore_golden round trip of the same case (rewrite → discovery → rollback back to the original bytes) — ok
vex::discover::npm::tests::v2_alias_mirror_that_agrees_attests (guard: an agreeing mirror still attests) ok ok
e2e e2e_vendor_npm_build::npm6_installs_a_vendored_v2_alias_from_its_legacy_mirror (real npm 6 installs patched bytes; revert byte-exact) — ok (npm 6.14.18, lock written by npm 10.9.4)

The VEX discovery golden (vex-discover-golden/redirect-npm.json) only
gained the two new fixture entries. No existing entry changed.

Local checks:

  • cargo fmt --all -- --check: main itself is not fmt-clean with the
    pinned 1.93.1 toolchain (31 pre-existing diffs in redirect/mod.rs,
    CI does not run fmt). This PR adds no new fmt diffs, and the e2e file
    it touches stays fmt-clean.
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test --workspace --all-features: 9,730 passed, 253 ignored, 12 failed. All 12 are write-failure tests (*_write_failure_*, *unremovable*, read-only .socket/vendor) that need a non-root user to make chmod stick; this sandbox runs as root (uid 0). None touch npm lock code, and CI runs them as non-root.
  • npm e2e: e2e_vendor_npm_build --include-ignored passes in full under npm 10.9.4 and under npm 6.14.18 (lock writer npm 10.9.4), 18/18 each. e2e_redirect_npm_build --include-ignored passes 13/15 under npm 10.9.4 and 14/15 under npm 6.14.18. The failures are the hosted rollback tests, whose live registry.npmjs.org fetch can't verify this sandbox's TLS-intercepting proxy: reqwest is built with bundled webpki roots and ignores SSL_CERT_FILE. That fetch is for a plain (non-alias) package, a path this PR doesn't change, and it runs normally on CI.

Per-issue checklist

Follow-ups

None. The npm/pypi/gem wrappers only dispatch to the binary, so they
need no change.

🤖 Generated with Claude Code


Note

Medium Risk
Touches npm lockfile rewrite, VEX discovery, and vendoring paths that affect install integrity and attestation; behavior change for alias-heavy v2 locks but heavily tested with fail-closed warnings for npm 6 hosted aliases.

Overview
Fixes #432: npm alias installs in the legacy dependencies tree (v1 locks and the v2 mirror npm 6 reads) are now treated as their real name@version instead of matching only the alias key.

Lock handling: Shared npm_legacy_identity decodes "version": "npm:left-pad@1.3.0". Hosted redirect and vendored rewire those mirror nodes (removes vendor_legacy_alias_skipped). Hosted adds redirect_npm_legacy_alias_client when a v2 alias mirror was rewritten—npm 6 still fetches aliases from the registry, so installs fail EINTEGRITY unless users vendor.

VEX: drop_mirror_unwired withholds attestation when packages is patched but the v2 legacy mirror still points at the registry (pre-fix locks), including contesting sibling npm locks.

Tests/docs: New unit/golden/e2e coverage (npm 6 vendored alias + revert), refactored Npm6Cell e2e helper, and small vex_consumed adjustments for resolver behavior after #605.

Reviewed by Cursor Bugbot for commit 6548559. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A lockfileVersion 2 package-lock.json keeps a legacy `dependencies`
mirror for npm 6, which spells an alias install as
`"lp": {"version": "npm:left-pad@1.3.0"}`. Hosted scans matched mirror
nodes on a plain version only, so the alias node silently stayed on
the registry, and a lockfileVersion 1 alias lock pinned nothing at all
("no package-lock.json entry"). Rollback could not restore such a node
either.

Every reader of the legacy tree now decodes the alias through one
helper. Hosted scans rewire the alias node with the rest, and rollback
restores it. npm 6 fetches an aliased dependency from the registry
whatever `resolved` says, so under npm 6 the pinned lock fails closed
(EINTEGRITY) instead of installing unpatched bytes, and the run warns
`redirect_npm_legacy_alias_client`.

Refs #432

Assisted-by: Claude Code:claude-opus-5-5
Vendoring skipped the v2 mirror node of an npm alias with
`vendor_legacy_alias_skipped`, so npm 6 installed the unpatched
registry tarball through it. npm 6 does install an alias node from a
`file:` resolved (checked against npm 6.14.18), so the node is now
rewired like every other mirror node and revert restores it.

Refs #432

Assisted-by: Claude Code:claude-opus-5-5
Lockfile-only VEX read a v2 lock's `packages` half only, so it attested
`not_affected` for a package whose legacy mirror (what npm 6 installs
from) still resolved to the registry, as locks written before this fix
do for npm aliases. Such a ref is now diagnosed as unattributable and
not attested. A lockfileVersion 1 alias node is read as an install of
its target package.

Fixes #432

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 5, 2026 01:32
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/vex/discover/npm.rs
When npm-shrinkwrap.json's legacy mirror still resolved a package from
the registry, VEX dropped the shrinkwrap's own ref but still attested
the same package from package-lock.json, although npm 6 installs from
the shrinkwrap. A mirror node off Socket now counts as resolving the
package elsewhere, so the sibling lock's ref is contested too.

Refs #432

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Labeled Ready for review.

  • Head: ec455b8a
  • CI: all checks green on head (485 success, 6 skipped; 0 failing)
  • Bugbot: reviewed ec455b8a, no new issues; 0 unresolved review threads
  • Mergeable against main; only a human approval is left.

Slack announcement not sent this run (no Slack send tool available), so the next run will retry.


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) removed the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
Combined main's located npm walk (npm_lock_located_nodes, walk locate flag, unwired as purl→location map) with this PR's alias-aware legacy mirror nodes and drop_mirror_unwired; npm-compatibility table keeps main's takeover note and this PR's npm 6 alias row.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Main has been red since #605: two commands::vex_consumed tests assume
the name-keyed resolver never returns npm-aliased copies, but #605
taught it to probe bundled store trees. Port #851's test-only fix so
this PR's CI runs on a green base. It becomes a no-op once #851 lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBZwmqgXLZaRGyfDFoWwp
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] coverage went red on cb34188 (the main merge). It failed on two socket-patch-cli --lib tests: commands::vex_consumed::tests::hosted_expands_alias_only_copies and hosted_reuses_expanded_npm_copies_and_merges_alias_variants. These are not caused by this PR. They fail the same way on main at 4646693, where #605 conflicts with tests from #738. #851 is the fix, and it only changes tests. I ported its change in 6548559, which becomes a no-op once #851 lands. With it, all 840 cli lib tests pass locally and CI's clippy command is clean. Pushing a new commit dismisses the earlier approvals, so the PR needs approving again.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 6548559. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: Ready for review at 6548559.

  • CI: 491/491 check runs green (485 success, 6 skipped), mergeable clean.
  • Bugbot: reviewed 6548559, no findings; 0 unresolved review threads.
  • Reviewer focus: hosted scan/get and vendored mode now rewrite the npm 6 legacy dependencies mirror for alias installs ("lp": {"version": "npm:left-pad@1.3.0"}) alongside packages, so npm 6 no longer installs the unpatched alias.

Generated by Claude Code

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

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

4 participants