npm のサプライチェーン攻撃が続いてるので、pnpm 10 に移行してみました(ハマりどころメモ)

2026.05.15 09:00
2026.05.13 23:46
npm のサプライチェーン攻撃が続いてるので、pnpm 10 に移行してみました(ハマりどころメモ)

前回の記事で、.npmrc に Takumi Guard を仕込んで既知の悪性パッケージをブロックするようにしました。
これでひと安心…と思ったんですが、よく考えるとまだ不安なところが残ってたので、ついでに npm から pnpm 10 への移行 もやってみました。

結果として、思ったよりハマりどころが多くて軽くハマったので、その記録です。
同じことをやる人の役に立てばいいなと。

Takumi Guard だけだとカバーしきれない部分

Takumi Guard は「既知の悪性パッケージをレジストリ側で弾いてくれる」防御で、ここはとても優秀です。
ただ、よく考えるとこんな不安が残ります。

  • 新規攻撃が公開された直後は、ブロックリストへの反映までタイムラグがある
  • npm install 時に postinstall スクリプトがそのまま実行される構造は変わらない
  • つまり「すり抜けた瞬間に postinstall が走ってアウト」というルートが残る

このへんに対しては、レジストリ側じゃなくて クライアント側(パッケージマネージャ側) でも防御層を入れたい。
そこで出てくるのが pnpm 10 です。

pnpm 10 が防御層として効く理由

pnpm 10 のいちばん大きな変化は、postinstall スクリプトの実行が明示許可制になったこと。
package.jsonpnpm.onlyBuiltDependencies に書いたパッケージだけが postinstall を実行できるようになりました。

つまり、Takumi Guard をすり抜けた悪性パッケージが入ってきたとしても、postinstall が勝手に走らないので、すぐに発火する系の攻撃はだいぶ抑え込めます。
レジストリ側で1段、クライアント側でもう1段、という二重構えになるイメージ。

多層防御の関係を整理するとこんな感じ:

防御内容
Takumi Guardレジストリ側で既知の悪性パッケージをブロック
pnpm 10(onlyBuiltDependencies)postinstall を明示許可制にして勝手な実行を防ぐ
minimum-release-age(任意)公開直後のパッケージは一定期間使わせない
–ignore-scripts全 postinstall を無効化(インストール時の保険)

pnpm のバージョン選びでまず迷う

最初「pnpm 入れるか〜」と思って何も考えずに pnpm 9 を入れようとしたんですが、ここで一度立ち止まる必要がありました。
2026年5月時点での状況を整理すると:

  • pnpm 9 → 2026年4月30日に EOL(サポート終了済み)
  • pnpm 11 → 破壊的変更が多く、.npmrc の扱いも変わる
  • pnpm 10 → 現状もっとも実用的な落とし所

というわけで、結論は pnpm 10 系の最新 に。
うちは pnpm@10.18.0 で固定して進めました。

移行の手順(実際にやった流れ)

パイロットで1サイト分やってみた手順がこちら。
マシン側の準備は corepack enable しておけば、package.jsonpackageManager フィールドで pnpm のバージョンを縛れて便利でした。

# 1. corepack を有効化(マシンで1回だけ)
corepack enable

# 2. 既存の lockfile と node_modules を消す
rm -rf node_modules package-lock.json yarn.lock

# 3. まずは postinstall を全部止めて安全にインストール
pnpm install --ignore-scripts

# 4. lockfile の中身に被害パッケージが混ざってないか確認(後述)

# 5. ビルドが通るか確認
pnpm run build

package.json 側はこんな感じで packageManagerpnpm.onlyBuiltDependencies を追加します。

{
  "name": "your-project",
  "version": "0.1.0",
  "private": true,
  "packageManager": "pnpm@10.18.0",
  "pnpm": {
    "onlyBuiltDependencies": ["sharp", "unrs-resolver"]
  }
}

ハマり①:onlyBuiltDependencies に書いたのに警告が消えない

最初に踏んだ罠がこれ。
pnpm install したときに Ignored build scripts: sharp, unrs-resolver みたいな警告が出てきます。

「あ、許可しないとダメね」と思って package.jsononlyBuiltDependencies に追加して、もう一度 pnpm install
…なのに、また同じ警告が出ます。なんで?

正解は、すでにインストール済みのパッケージに対しては設定だけだと反映されない、というやつでした。
明示的に rebuild してあげる必要がある

# 許可リストに追加したパッケージを明示的に rebuild
pnpm rebuild sharp unrs-resolver

# そのあとで通常のインストールに戻す
pnpm install

これで警告が消えました。
「設定を書いただけでは効かない、rebuild が必要」というのは地味だけど大事なポイントでした。

ハマり②:unrs-resolver ってなんだ問題

もう1つ「これなに?」となったのが、警告に出てきた unrs-resolver という見覚えのないパッケージ。
許可するかどうか判断する前に、まず正体を知らないと怖くて触れない。

調べてみると、eslint-config-next が内部で使ってる Rust 製の高速モジュールリゾルバでした。
Next.js 16 系で導入されたやつで、ネイティブビルドが必要なため postinstall を走らせたい、という理由でした。

こういう「警告に出てきた見慣れないパッケージ」が出るたびに調べて判断するのが地味に大変なので、運用としては:

  • 警告に出たパッケージは1個ずつ素性を調べる
  • 納得できたら onlyBuiltDependencies に追加 → pnpm rebuild
  • 素性が分からないものは保留にして調べる

みたいな流れで進めていく感じです。
めんどくさいですが、その面倒くささこそが防御層として機能してる感もあり。

lockfile が汚染されてないか grep で確認

移行のついでにやっておくと安心なのが、pnpm-lock.yaml の中身に既知の被害パッケージが入っていないかチェックすること。
シンプルに grep で確認できます。

grep -E "@tanstack/(router|history|virtual-core|react-router)|@mistralai/|intercom-client@7\.0\.[45]|@cap-js/|mbt@1\.2\.48|guardrails-ai" pnpm-lock.yaml

何も出てこなければ、ひとまずこの既知リストの被害パッケージは入っていない、というところまで確認できます。
transitive な依存まで含めて引っかけてくれるので、数秒で終わる軽量チェックとして便利。

ただし、これはあくまで「既知の攻撃」しか検出できないやり方なので、根本的な防御は Takumi Guard と pnpm 10 側に任せる前提で。
新しい攻撃が出るたびにリストの更新が必要、という意味では補助的なチェックです。

移行してみての感想

正直、移行作業そのものはコマンド数行で終わるんですが、onlyBuiltDependenciesrebuild のあたりで地味にハマるので、初回はちょっと時間がかかりました。
逆に1回手順が固まれば、他のプロジェクトに展開するのはサクッと行けそうです。

  • Takumi Guard = レジストリ側で既知悪性をブロック
  • pnpm 10 = クライアント側で postinstall を明示許可制に
  • この2層が組み合わさると、攻撃のすり抜けが起きてもすぐ発火しない

「とりあえずこれくらいやっておけば、いまのニュース見ながら過度に怯えなくて済むかな」というラインまで持っていけた気がします。
pnpm 自体の使い勝手も悪くないので、移行先としても普通におすすめできる印象でした。

今回は以上です!