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

オープン
#1,937 コメント 3 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@mikolalysenko がすでに取り組んでいます。

2026年11月1日 から。

  • #1938 @mikolalysenko による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
35/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
nodejs, rust

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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).

主要言語
Rust
スター
9
フォーク
2
平均マージ
18時間 47分
マージ済み PR(30日)
637

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

SocketDev/socket-patch のほかの issue

SocketDev/socket-patch の issue をすべて見る

似ている issue

Rust の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。