Vendored npm rollback / remove / vendor --revert on a lockfileVersion 2 lock fail after any `npm install`, yet half-revert the lock: `packages` goes back to the registry while the legacy mirror keeps the patched integrity, and npm 6 then fails EINTEGRITY

Aperta
#1,937 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@mikolalysenko ci sta già lavorando.

Dal 1/11/2026.

  • #1938 di @mikolalysenko — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
nodejs, rust

Direzione di ricerca

Start with the drift verdict in crates/socket-patch-core/src/vendor/npm_lock.rs near line 1365, where a missing resolved on a KIND_LOCK_LEGACY_ENTRY record is treated as not ours. Compare it with the drop_mirror_unwired rule in vex/discover/npm.rs, then check how rollback.rs around line 1029 reports the partial revert. Run the repro's npm install then rollback sequence to confirm the half-reverted lock. Done means a revert exits 0 and rewrites the packages entry and mirror node consistently, with a mirror-node twin of the test near line 4910 passing.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

agent:claimed agent:triaged bug bughunt pm:npm priority:p1

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

Summary

This is the revert-side sibling of #879. After a vendored scan of an npm lockfileVersion 2 project, an ordinary npm install (npm 7, 8, 10 or 12 on an existing v2 lock) re-saves the lock. npm never writes resolved for a file: resolution in the legacy dependencies mirror, so the mirror node keeps only version and the patched integrity. #879 taught vex and vendor --check that this is still wired, and both pass on main. The revert engine wasn't updated:

  • rollback, remove <purl> and vendor --revert warn vendor_lock_entry_drifted "lock entry /dependencies/left-pad was re-resolved since vendoring (no resolved field); left alone" and keep the vendored state. rollback and remove exit 1. rollback --json reports rollback_failed: "nothing was rolled back: 1 patch failed", with the event "lockfile wiring drifted; vendored state left untouched".
  • That claim is false: the same run did rewrite the packages["node_modules/left-pad"] entry back to the registry resolved and integrity. The mirror node still carries the patched integrity. The lock is left half-reverted, and the ledger plus .socket/vendor/ artifact are kept.

Impact

  • Every npm 7/8 team, and every npm 9–12 team with an existing v2 lock, that vendors a patch and then runs npm install once can no longer roll it back or remove it with one command. That's the normal workflow: the scan's own "Next steps" tells them to run npm install.
  • The failed run leaves a lock that no longer matches itself. npm 7+ npm ci installs the upstream bytes from packages. npm 6 reads the mirror and fails npm ci with EINTEGRITY (wanted sha512-<patched> but got sha512-<registry>). After that, vendor --check exits 1 ("vendored wiring drifted … node_modules/left-pad still resolve [email protected] outside the vendored artifact"), even though the user asked for the patch to be removed.
  • The remedy it prints ("undo the drift … or re-vendor, and re-run vendor --revert") does work: scan --mode vendored re-adds the mirror resolved, and a revert then succeeds. But it means re-vendoring a patch you're trying to remove, and the next npm install before the revert brings the problem back.

Repro (Linux, Node 22.23.4, npm 8.19.4; a local mock patch API serving one free patch for the real npmjs [email protected])

mkdir p && cd p && git init -q
echo '{"name":"p","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0"}}' > package.json
npm install                                   # npm 8 → lockfileVersion 2
socket-patch scan --mode vendored --yes $API  # exit 0; both packages entry and mirror get resolved file:.socket/vendor/...
git add -A && git commit -qm vendored
rm -rf node_modules && npm ci                 # patched
npm install                                   # re-saves the lock: drops `resolved` from dependencies.left-pad (git diff: 1 deletion)
socket-patch vendor --check                   # exit 0 (fixed by #879)
cp package-lock.json before.json
socket-patch rollback; echo $?                # exit 1: "Kept vendored state …: lockfile wiring drifted; vendored state left untouched"
diff <(jq . before.json) <(jq . package-lock.json)
#  packages["node_modules/left-pad"].resolved:  file:.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgz → https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz
#  packages["node_modules/left-pad"].integrity: sha512-<patched> → sha512-<registry>
#  dependencies.left-pad: unchanged (version + PATCHED integrity, no resolved)
ls .socket/vendor/state.json                  # still there
npx npm@6 ci --cache "$(mktemp -d)"           # EINTEGRITY wanted sha512-<patched> but got sha512-<registry>
socket-patch vendor --check; echo $?          # 1: vendored wiring drifted

remove pkg:npm/[email protected] (exit 1, "1 matching entry was drift-kept … re-run scan --mode vendored to normalize, then remove again") and vendor --revert (exit 0, "Kept 1 drifted package") leave exactly the same half-reverted lock.

Expected vs actual

  • Expected: a mirror node with no resolved and the recorded vendored integrity is treated as ours (#879 already established that this is npm's normal serialization, and vex / vendor --check accept it), so the revert restores both the packages entry and the mirror and exits 0. At the very least, when one record of a package is drift-kept, none of that package's records should be rewritten. Per CLI_CONTRACT.md, an explicit reversal (rollback, remove, vendor --revert) undoes the vendoring, and the event text says "vendored state left untouched".
  • Actual: exit 1 (rollback / remove) with "nothing was rolled back", while the packages entry was rolled back. The result is a half-reverted, self-inconsistent lock.

Matrix (Linux only: probe branches are still paused for this routine)

npm lock after npm install rollback vendor --revert remove
7.24.2 v2 mirror resolved dropped fail (exit 1, half-revert) not run not run
8.19.4 v2 dropped fail (3×) fail (exit 0, half-revert) fail (exit 1, half-revert)
8.19.4 v2 + overrides: {"left-pad": "$left-pad"} dropped fail not run not run
10.9.9 existing v2 (--lockfile-version 2) dropped fail not run not run
12.2.1 existing v2 dropped fail not run not run
8.19.4 v2, npm ci only (no npm install) resolved kept pass not run not run
10.9.9 / 12.2.1 v3 (no mirror) n/a pass pass pass

Same result on main cbbbdb9 and the v5.0.1 release, so it isn't a recent regression. It has probably been there since the npm mirror revert records were added; I didn't bisect further.

Suspect code

  • crates/socket-patch-core/src/vendor/npm_lock.rs:1365: let ours = match live_resolved { … None => false }. For a KIND_LOCK_LEGACY_ENTRY record, a missing resolved should count as ours when the live integrity equals the recorded vendored (rec.new) integrity, the same rule #879 added to vex/discover/npm.rs (drop_mirror_unwired).
  • The revert applies the package's other records (the packages entry) before the drift verdict, then crates/socket-patch-cli/src/commands/rollback.rs:1029 reports "vendored state left untouched". The per-package keep decision should either gate every record of the package or be reported as a partial revert.
  • The unit test at npm_lock.rs ~4910 (resolved-less entry left alone) pins the drift verdict for a packages entry. A mirror-node twin of that test with the vendored integrity should expect a clean revert.

Related: #879 (the same npm serialization on the check/vex side, fixed), #1155 / #1187 (version-moved-off drift-keep for npm), #989 (the revert-engine tracking issue).

Lingua principale
Rust
Stelle
9
Fork
2
Merge medio
18h 47m
PR unite (30g)
637

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di SocketDev/socket-patch

Tutte le issue di SocketDev/socket-patch

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.