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.json の pnpm.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.json の packageManager フィールドで 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 buildpackage.json 側はこんな感じで packageManager と pnpm.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.json の onlyBuiltDependencies に追加して、もう一度 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 側に任せる前提で。
新しい攻撃が出るたびにリストの更新が必要、という意味では補助的なチェックです。
移行してみての感想
正直、移行作業そのものはコマンド数行で終わるんですが、onlyBuiltDependencies と rebuild のあたりで地味にハマるので、初回はちょっと時間がかかりました。
逆に1回手順が固まれば、他のプロジェクトに展開するのはサクッと行けそうです。
- Takumi Guard = レジストリ側で既知悪性をブロック
- pnpm 10 = クライアント側で postinstall を明示許可制に
- この2層が組み合わさると、攻撃のすり抜けが起きてもすぐ発火しない
「とりあえずこれくらいやっておけば、いまのニュース見ながら過度に怯えなくて済むかな」というラインまで持っていけた気がします。
pnpm 自体の使い勝手も悪くないので、移行先としても普通におすすめできる印象でした。
今回は以上です!