技術 約10分で読めます

npm v12でinstall scriptsが既定で停止、allowScriptsとapprove-scriptsの移行手順

いけさん目次

TL;DR

変更 npm v12では、依存パッケージの preinstallinstallpostinstall、一部の prepare、暗黙の node-gyp rebuild が既定で停止。許可したパッケージだけ allowScripts で実行

準備 npm 11.16.0以上で通常のinstallを走らせ、npm approve-scripts --allow-scripts-pending で止まりそうな依存を確認。信頼するものだけ npm approve-scripts、残すものは npm deny-scripts

差分 min-release-age は公開直後の汚染版を踏む確率を下げる設定。npm v12の変更は、取得済みパッケージのinstall時コード実行を止める設定


npm v12で、npm install の安全側の初期値が変わる。
GitHubの告知では、npm v12は2026年7月リリース見込みで、依存パッケージのinstall時スクリプトを既定では実行しなくなる。
Aikidoはこの変更を、Nx、Shai-Hulud、axios、Mini Shai-Hulud、Red Hat系Miasmaで繰り返されたpostinstall系の入口を閉じる変更として見ている。

前に書いた pnpm 11、Yarn 4.10、npm v11.10のrelease-age gate は、「公開されたばかりのバージョンをすぐ取らない」ための話だった。
今回のnpm v12はそこから一段違う。
パッケージを取得して node_modules に展開しても、許可していないinstall時コードは走らない。

過去事例で見るとどこが止まるか

npm v12の変更が効くのは、依存解決の途中で勝手にコードが走る場所だ。
過去記事で見た事例を並べると、止まる入口と残る入口が分かれる。

事例公開・観測時期install時の入口npm v12で止まる範囲
Nx s1ngularity2025年8月postinstalltelemetry.js未許可なら依存取得後のscript実行が止まる
Shai-Hulud 2.02025年11月setup中にBunとsecret scannerを動かすinstall時payload未許可ならpayload起動前に止まる
axios汚染2026年3月偽依存 plain-crypto-jspostinstallplain-crypto-js が未許可なら、axiosをimportする前に止まる
Mini Shai-Hulud TanStack波2026年5月11日UTCpreinstall とGit依存側の prepareallowScripts--allow-git の両方に当たる
Mini Shai-Hulud @antv波2026年5月19日UTC314パッケージ、637悪意あるバージョンで preinstall とGit依存npmレジストリ側のscriptとGit依存の別経路を同時に絞れる
Red Hat系Miasma npm波2026年6月2日公表32パッケージ、90超バージョンの preinstallBunを落として第2段を動かす入口を止める
Microsoft系73リポジトリ停止2026年6月5日npmではなくAIツール・IDE設定npm v12では止まらない。ワークスペース設定を別に洗う
node-ipc 3悪意あるバージョン2026年5月14日UTCrequire("node-ipc") 時にCommonJS側で実行npm installだけでは止まらない。lockfileと実行ログを見る

この表の上6つは、npm install 中の自動実行が入口になっている。
下2つは、リポジトリをAIツールで開く、アプリやテストがパッケージを読み込む、といった別の発火点だ。
npm v12は大きな入口を閉じるが、開発端末・CI runner上の認証情報を守る作業は残る。

自動実行からプロジェクトごとの許可へ

今のnpmでは、依存ツリー内の任意のパッケージが preinstallinstallpostinstall を持っていると、npm install の途中で自動実行される。
アプリ側でそのパッケージを import していなくても関係ない。
依存解決で入った瞬間、開発端末やCIランナーの権限でコードが走る。

npm v12では、この実行が許可制になる。
GitHubの告知では、許可リストは npm approve-scripts で作り、拒否するものは npm deny-scripts で記録する。
結果は package.jsonallowScripts に入り、プロジェクトの他の設定と同じようにコミットする。

npm install
npm approve-scripts --allow-scripts-pending
npm approve-scripts sharp esbuild
npm deny-scripts some-package

npm docsでは、--allow-scripts-pending は読み取り専用だと説明されている。
未レビューのinstall script持ちパッケージを列挙するだけで、package.json は変更しない。
最初にこの一覧をCIとローカルで出し、ネイティブビルドが本当に必要な依存だけ許可する流れになる。

approve-scripts は既定で pkg@1.2.3 のようなバージョン固定の許可を書く。
--no-allow-scripts-pin を付けるとパッケージ名だけの許可になるが、サプライチェーン防御としては固定のほうが扱いやすい。
「このバージョンのinstall scriptをレビューした」という記録になるからだ。

package.json には、形として次のような記録が残る。

{
  "allowScripts": {
    "sharp@0.33.5": true,
    "esbuild@0.25.0": true,
    "some-package": false
  }
}

実際のバージョンは、手元で解決されているものに置き換わる。
false は名前だけで残り、将来のバージョンも含めてそのパッケージのinstall scriptを拒否する。
npm docsには、npm approve-scriptsnpm deny-scripts はworkspacesを理解しないという注記もある。モノレポでは、ルートだけでなく、個別workspaceでinstall script持ちの依存がないかを確認する。

binding.gypも暗黙のビルドとして止まる

今回の変更で見落としやすいのは、scripts フィールドに何も書かれていないパッケージも止まる点だ。
GitHubとAikidoは、binding.gyp を持つパッケージでnpmが暗黙に実行する node-gyp rebuild も、install scriptと同じ扱いになると書いている。

つまり、package.json だけ見て「postinstallがないから安全」とは読めない。
ネイティブアドオンを含むパッケージは、明示スクリプトなしでもビルドが走る場合がある。
npm v12では、そのビルドも許可リストに入っていなければ止まる。

壊れやすいのは、sharpcanvasbcryptbetter-sqlite3、古い node-sass、社内のネイティブアドオンあたりだ。
このへんは悪意のあるコードというより、正規のビルド手順としてinstall時実行を使っている。

依存の種類v12で起きること先に見る場所
純JavaScript何も走らず通常install追加作業なし
postinstall でバイナリを取る依存未許可ならセットアップが止まるscripts.postinstall
binding.gyp 持ち暗黙の node-gyp rebuild が止まるtarball内の binding.gyp
Git、file、link依存の prepare未許可ならprepareが止まるlockfileと依存指定

移行で引っかかりやすい依存は、だいたい次の種類に寄る。

パッケージ例install時にやること止まったときの症状
esbuildOS/CPU別バイナリの配置bundler起動時に実行ファイルが見つからない
sharpnative bindingとlibvips周辺の準備画像処理時にmodule loadで落ちる
canvasbcryptbetter-sqlite3node-gyp 経由のC/C++ビルドnative bindingが見つからない
古い node-sassnative binaryの取得またはビルドSassビルドがinstall後に失敗する
@prisma/clientclient生成やengine準備実行時にPrisma Client未生成で落ちる
cypressテスト用バイナリの取得cypress run でbinary not found系の失敗

この表は「許可候補」の一覧であって、安全判定ではない。
sharpesbuild のような実用上必要な依存でも、許可した瞬間にそのバージョンのinstall時コード実行を認めることになる。
PRでは、allowScripts の差分をlockfile更新と同じ扱いで見る。

Git依存とリモートURLも既定で閉じる

npm v12では、install script以外にも2つの入口が閉じる。

Git依存は、--allow-git を渡さない限り解決されなくなる。
Git依存の .npmrc がGit実行ファイルを差し替え、--ignore-scripts を付けていてもコード実行へ進める経路があったためだ。
この変更はnpm 11.10.0以降で先に使える。

リモートURL依存も、--allow-remote を渡さない限り解決されなくなる。
https://.../package.tgz のような指定は、レジストリのバージョン履歴や削除処理から外れやすく、攻撃者が外部URL側の中身を差し替える余地が残る。
このフラグはnpm 11.15.0以降で使える。

前に Mini Shai-Huludの@antv波 で見た github:antvis/G2#... のようなGit依存は、まさにここに入る。
release-age gateはnpmレジストリ上の公開時刻を見るが、Git依存はnpmのバージョン番号ではなくコミットハッシュで降ってくる。
npm v12の --allow-git は、その別経路を既定で閉じる。

axiosやMiasmaの発火点はinstall直後

axiosの乗っ取りでは、正規パッケージ本体ではなく、追加された偽依存 plain-crypto-jspostinstall がRAT(遠隔操作マルウェア)を落とした。
axiosの記事で書いた通り、被害側はaxiosをimportする前に踏んでいる。
npm install 中に発火したからだ。

Red Hat系Miasmaも同じ発火点を使った。
Microsoft系73リポジトリ停止の記事では、Red Hat npm波が preinstall でBunを落とし、第2段の悪意あるコードを実行したと整理した。
npm v12の既定ブロックは、このinstall時実行に直接ぶつかる。

ただし、同じMiasmaでもMicrosoftリポジトリ停止の波は別だ。
あちらはnpm installではなく、Claude Code、Gemini CLI、Cursor、VS Codeの設定読み込みが入口になっていた。
npm ci が安全側になっても、リポジトリをAIツールやIDEで開いた瞬間に読む設定ファイルはnpmの制御外に残る。

11.16.0で壊れる依存を先に出す

移行作業はv12を待たなくていい。
GitHubとAikidoのどちらも、npm 11.16.0以上で3つの変更が警告として使えると書いている。
現時点でやるなら、普段のinstallをnpm 11.16.0以上で走らせ、警告と approve-scripts の出力を見る。

npm -v
npm install
npm approve-scripts --allow-scripts-pending

ここで出る一覧は、「v12に上げたら止まる候補」になる。
CIでネイティブビルドが必要な依存、ローカルだけで必要な依存、もう使っていない依存を分ける。
全部を --all で許可すると、v12の意味が薄くなる。

CIでv12相当の失敗を先に見たいなら、npm 11.16.0で strict-allow-scripts=true を入れて別ジョブを作る。
npm docsでは、この設定を有効にすると、未レビューのinstall scriptが警告ではなくinstall失敗になると説明されている。

# .npmrc
strict-allow-scripts=true

一時的に全部を通す逃げ道として dangerously-allow-all-scripts=true もある。
ただし、これはdenyしたパッケージも含めてallowScriptsの方針を迂回する設定なので、CIの常用設定には置かない。
移行中にどうしても切り分ける場合だけ、短いブランチかローカルで使う。

レビュー後に許可したパッケージだけ、allowScripts としてコミットする。
denyしたものは false として残り、後から approve-scripts しても勝手に再許可されない。
npm docsはこの挙動を、既存の false が常に勝つルールとして説明している。

CIでは、最初からv12相当の壊れ方を見たい環境と、段階移行したい環境を分ける。
本番ビルドに入る前に、ネイティブ依存のセットアップ失敗を別ジョブで拾えるようにするほうが運用しやすい。

npm execnpxnpm install -g のようにプロジェクトの package.json がない場面は別扱いになる。
npm docsでは、その場合は allowScripts フィールドではなく allow-scripts config の領域だと説明されている。
グローバルに入れるCLI、特にAIコーディングツールやブラウザ操作ツールは、プロジェクト依存よりも強い権限で動きがちなので、ローカル端末側の許可とプロジェクト側の許可を混ぜない。

release-age gateとは役割が違う

5月に書いたrelease-age gateは、公開直後に消える悪意あるバージョンを踏む確率を下げる防御だった。
Mini Shai-Hulud、axios、debug/chalkのように、数時間で削除される汚染版を解決対象から外せる。
ただし、lockfileに既に入ったものや、長く居座る悪意あるバージョンは残る。

npm v12のallowScripts既定ブロックは、取得後の自動実行を止める。
悪意あるバージョンがlockfileに入ってしまっても、install時スクリプトが未許可ならそこで止まる。
逆に、許可済みのパッケージが後日侵害された場合は、許可の粒度で挙動が分かれる。
pkg@version で許可していれば、新しいバージョンにそのまま実行権限が渡らない。

この2つは置き換えではない。
公開直後の取得を遅らせる設定と、取得後の実行を許可制にする設定を両方置く。
そこにGit依存とリモートURLの明示許可を足すと、2025年から2026年にかけて見えていたnpm系の入口はかなり狭くなる。

参考