Fix npm 6 installing unpatched aliases (#432) - #813
Conversation
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
|
BugBot review Generated by Claude Code |
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
|
BugBot review Generated by Claude Code |
|
[agent] Labeled Ready for review.
Slack announcement not sent this run (no Slack send tool available), so the next run will retry. Generated by Claude Code |
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>
|
bugbot run Generated by Claude Code |
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
|
[agent] Generated by Claude Code |
|
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 6548559. Configure here.
|
Burn-down agent: Ready for review at
Generated by Claude Code |
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) carriestwo copies of every install: the
packagesmap that npm 7+ reads, andthe legacy
dependenciesmirror that npm 6 reads. npm 6 spells an npmalias install in that mirror as
"lp": {"version": "npm:left-pad@1.3.0"}.Before this change:
scan/getrewired onlypackages["node_modules/lp"]. Thealias mirror node silently stayed on the registry.
vendor_legacy_alias_skippedwarning.
vexattestednot_affectedfrom thepackageshalf while npm 6
npm ciinstalled the unpatched registry tarball(exit 0).
scanpinned nothing(
redirect_npm_entry_not_found, exit 0).After this change:
then fails closed (EINTEGRITY) instead of installing unpatched bytes,
and the run warns
redirect_npm_legacy_alias_client.vexwithholds the attestation (patched_ref_unattributable) while av2 mirror still resolves the package from the registry. That is the
state of any lock written before this fix.
redirect_npm_legacy_clientnpm 6 caveat.Root cause
Every reader and writer of the legacy
dependenciestree identified anode by its key and
version. An alias node's key is the alias and itsversion is the
npm:<name>@<version>spec, so the node never matchedthe 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 uplp@npm:left-pad@1.3.0on 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_identityis the single rule for what alegacy 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.
redirect_npm_legacy_alias_clientwarning fires for v2 locks when analias node was rewired. v1 locks already carry
redirect_npm_legacy_client, so the new warning is not repeated there.revert.
vendor_legacy_alias_skippedis gone.drop_mirror_unwireddrops apackagesref when the v2mirror 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/ecosystems.mdanddocs/testing/npm-compatibility.md.What real npm does (probed locally, Node 22.22)
resolvedcicifile:.socket/vendor/...(vendored after this fix)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:
patch::redirect::tests::npm_v2_legacy_alias_mirror_is_rewired_and_warned(hosted v2, plain and scoped alias)patch::redirect::tests::npm_v1_alias_entry_is_rewired(hosted v1 alias)vendor::npm_lock::tests::v2_legacy_alias_node_is_rewired_and_reverted(vendored rewire and byte-exact revert)vex::discover::npm::tests::v2_alias_mirror_left_on_the_registry_contests_the_ref(hosted and vendored)vex::discover::npm::tests::lockfile_v1_alias_node_is_its_targetvex::discover::npm::tests::stale_alias_mirror_contests_the_sibling_lock_ref(Bugbot: a stale shrinkwrap mirror also contestspackage-lock.json)redirect_goldencasenpm/package-lock-v3/legacy-alias-mirror-v2(byte-exact rewrite, edits, warnings)upstream_restore_goldenround trip of the same case (rewrite → discovery → rollback back to the original bytes)vex::discover::npm::tests::v2_alias_mirror_that_agrees_attests(guard: an agreeing mirror still attests)e2e_vendor_npm_build::npm6_installs_a_vendored_v2_alias_from_its_legacy_mirror(real npm 6 installs patched bytes; revert byte-exact)The VEX discovery golden (
vex-discover-golden/redirect-npm.json) onlygained the two new fixture entries. No existing entry changed.
Local checks:
cargo fmt --all -- --check:mainitself is not fmt-clean with thepinned 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: cleancargo 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 makechmodstick; this sandbox runs as root (uid 0). None touch npm lock code, and CI runs them as non-root.e2e_vendor_npm_build --include-ignoredpasses 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-ignoredpasses 13/15 under npm 10.9.4 and 14/15 under npm 6.14.18. The failures are the hosted rollback tests, whose liveregistry.npmjs.orgfetch can't verify this sandbox's TLS-intercepting proxy:reqwestis built with bundled webpki roots and ignoresSSL_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
npm_v2_legacy_alias_mirror_is_rewired_and_warned, goldenlegacy-alias-mirror-v2v2_legacy_alias_node_is_rewired_and_reverted, e2enpm6_installs_a_vendored_v2_alias_from_its_legacy_mirrorv2_alias_mirror_left_on_the_registry_contests_the_ref,stale_alias_mirror_contests_the_sibling_lock_refnpm_v1_alias_entry_is_rewired,lockfile_v1_alias_node_is_its_targetupstream_restore_goldenround tripFollow-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
dependenciestree (v1 locks and the v2 mirror npm 6 reads) are now treated as their realname@versioninstead of matching only the alias key.Lock handling: Shared
npm_legacy_identitydecodes"version": "npm:left-pad@1.3.0". Hosted redirect and vendored rewire those mirror nodes (removesvendor_legacy_alias_skipped). Hosted addsredirect_npm_legacy_alias_clientwhen a v2 alias mirror was rewritten—npm 6 still fetches aliases from the registry, so installs fail EINTEGRITY unless users vendor.VEX:
drop_mirror_unwiredwithholds attestation whenpackagesis 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
Npm6Celle2e helper, and smallvex_consumedadjustments for resolver behavior after #605.Reviewed by Cursor Bugbot for commit 6548559. Configure here.
Generated by Claude Code