ラボまとめコラムニュース
ブログ/記事一覧/npm v12でnpm installが変わる、依存スクリプトの自動実行が止まる
npm-v12-install-scripts-opt-in-no-auto-run-cover-ja

npm v12でnpm installが変わる、依存スクリプトの自動実行が止まる

毎日叩く「npm install」の挙動がnpm v12(2026年7月予定)で変わり、依存パッケージのインストール時スクリプトが自動実行されなくなります。v11以前に何が起きていたかを実際に再現しつつ、変更点・気づかず受けていた恩恵・今やるべき準備を解説します。

ラボ 本日更新
avatar-m-1

堀川 慎

Backend Engineer / AWS / Django / Go

2026.06.119 min6 views
この記事のポイント

毎日叩く「npm install」の挙動がnpm v12(2026年7月予定)で変わり、依存パッケージのインストール時スクリプトが自動実行されなくなります。v11以前に何が起きていたかを実際に再現しつつ、変更点・気づかず受けていた恩恵・今やるべき準備を解説します。

結論: npm v12で「npm install」が黙ってスクリプトを動かさなくなる

2026年7月に予定されているnpm v12で、毎日のように打っているnpm installの挙動が変わります。これまでは、インストールした依存パッケージが持つ「インストール時スクリプト」が許可なしに自動実行されていました。v12からは、これが既定でブロックされ、自分で許可したものしか動かなくなります。

JavaScript / TypeScriptを書くエンジニアにとって、npm installはおそらく一番よく叩くコマンドです。そのコマンドの「既定の動き」が変わるのですから、影響範囲は広い。しかも変更の理由は、ここ数年npmを悩ませ続けているサプライチェーン攻撃です。

「依存パッケージのコードが、インストールしただけで自分の権限で動く」というのが、いまいちピンと来ない人もいると思います。なので最初に、手元で実際に動かして確かめました。先に結果のスクリーンショット代わりのターミナル出力を見てください。

npm installを1回叩いただけで、依存パッケージに仕込んだコードがホームディレクトリにファイルを書き込みました。画面には「added 1 package」としか出ません。何かが実行された痕跡は、既定では表示すらされない——これがv12でなくなる動きです。

そもそも npm install は今まで何をしていたのか

npm(Node.jsの標準パッケージ管理ツール)でパッケージを入れるとき、npmは依存をダウンロードして展開するだけではありません。各パッケージのpackage.jsonライフサイクルスクリプトという項目があると、それを決まったタイミングで実行します。代表的なのが次の3つです。

  • preinstall: インストールの直前に走る
  • install: インストール時に走る(ネイティブモジュールのビルドなどに使われる)
  • postinstall: インストールの直後に走る(一番よく使われる)

問題は、これらが自分のプロジェクトのスクリプトだけでなく、依存パッケージ(さらにその依存の依存…)のスクリプトまで自動で実行されることです。`node_modules`の奥深くにある、名前も知らないパッケージのコードが、あなたがコマンドを打った瞬間に、あなたのユーザー権限で動きます。

v11以前で自動実行・自動解決されていたのは、スクリプトだけではありません。npm公式がv12の変更点として挙げているのは次のものです。

v11以前に自動で行われていたこと中身
インストール時スクリプト依存の preinstall / install / postinstall を自動実行
ネイティブビルドbinding.gyp を持つパッケージの node-gyp rebuild を自動実行
prepare スクリプトgit / file / link 依存の prepare を自動実行
Git 依存GitHub などのリポジトリ参照を自動で解決
リモートURL依存HTTPS などのURL参照を自動で解決

v12では、この表のすべてが「明示的に許可しない限り動かない」に変わります。

実際に試した: 依存の postinstall が勝手に動く

言葉だけだとピンと来ないので、手元のnpm(執筆時点で npm 10.9.7 / Node.js 22。スクリプト自動実行の挙動はv11まで同じです)で再現しました。やったことは単純で、悪意ある依存パッケージ役を1つ自作し、それを普通のプロジェクトに入れただけです。

依存パッケージ役のpackage.jsonはこれだけ。postinstallで、ホームディレクトリにファイルを書き込むコードを仕込んであります(本物の攻撃ならここで認証情報の窃取や外部送信が行われます)。

{
  "name": "evil-dep",
  "version": "1.0.0",
  "scripts": {
    "preinstall":  "echo '依存パッケージのコードが私の権限で動き出した'",
    "postinstall": "node -e \"fs.writeFileSync(os.homedir()+'/.npm-demo-pwned', 'stolen')\""
  }
}

これを依存に持つプロジェクトでnpm installを実行します。怖いのは、既定では何も表示されないことです。

$ npm install
added 1 package in 300ms

$ ls -a ~ | grep npm-demo
.npm-demo-pwned          # ← スクリプトはしっかり動いてファイルを書いていた

「added 1 package」としか出ていないのに、ホームディレクトリには.npm-demo-pwnedが作られています。スクリプトは確かに実行されたのに、その事実は画面に出ません。--foreground-scriptsを付けて、ようやく中で何が起きたかが見えます。

$ npm install --foreground-scripts

> evil-dep@1.0.0 preinstall
> echo '依存パッケージのコードが私の権限で動き出した'
依存パッケージのコードが私の権限で動き出した

> evil-dep@1.0.0 postinstall
> node -e "...ホームディレクトリにファイルを書き込む..."
ホームディレクトリにファイルを書き込んだ(=任意コード実行成功)

added 1 package in 98ms

これがv11以前の既定です。一方、v12の新しい既定に近い動きは、現行のnpmでも--ignore-scriptsで体感できます(厳密には--ignore-scriptsは自分のプロジェクトのスクリプトまで止めますが、v12は依存のスクリプトだけを止める、より絞った変更です)。

$ npm install --ignore-scripts
added 1 package in 87ms

$ ls -a ~ | grep npm-demo
                         # ← 何も作られない。スクリプトはブロックされた

念のため既定値も確認しました。npm config get ignore-scriptsfalse。つまり今は「スクリプトを無視しない=自動実行する」が初期設定です。v12は、この初期設定を実質ひっくり返す変更だと考えると分かりやすいです。

なぜ変えるのか: これがサプライチェーン攻撃の主経路だった

なぜnpmがここまで踏み込むのか。理由は、上で再現したpostinstallこそが、npmを狙うサプライチェーン攻撃の定番の入口だからです。攻撃者は人気パッケージを乗っ取ったり、紛らわしい名前の偽パッケージを公開したりして、そのpostinstallに悪意あるコードを仕込みます。あとは誰かがnpm installするのを待つだけ。被害者は何も操作していないのに、コードが走ります。

これは机上の話ではありません。当ブログでも、実際に起きた事件を何度も取り上げてきました。

「だったら--ignore-scriptsを常に付ければいいのでは」と思うかもしれません。npm公式もそこを認めていて、v12のRFC(変更提案)では「Git依存に含まれる.npmrcがGit実行ファイルのパスを上書きでき、--ignore-scriptsを使っていてもコード実行につながる経路がある」と説明しています。つまり、これまでの自己防衛策だけでは穴が残っていた。だから既定そのものを安全側に倒す、という判断です。

依存に潜む脆弱性を自分で点検したい人は、当ブログのOSSサプライチェーン スキャナー(npm / Pythonの依存を貼るだけでチェック)も合わせてどうぞ。

npm v12 で具体的に何が変わるのか

v12の核心は「禁止」ではなく「明示許可制(opt-in)」です。スクリプトを完全に使えなくするのではなく、動かしたいものを自分で名指しして許可する方式に変わります。許可リストはpackage.jsonの新しいallowScriptsフィールドに記録され、Gitにコミットしてチームで共有することが推奨されます。

操作は、npmに新しく加わる2つのコマンドで行います。

やりたいことコマンド / フラグ
ブロックされる依存を一覧するnpm approve-scripts --allow-scripts-pending
信頼する依存を許可するnpm approve-scripts
依存を明示的に拒否するnpm deny-scripts
Git依存を許可する--allow-git
リモートURL依存を許可する--allow-remote

もう1つ重要なのが、許可がバージョン単位で固定されること。たとえばesbuild@0.21.5を許可しても、その許可はesbuild@0.22.0には自動では引き継がれません。バージョンが上がるたびに、人の目で確認し直すことになります。乗っ取りは「信頼していたパッケージが、ある日のアップデートで悪意あるコードを混ぜてくる」形で起きるので、この版固定は理にかなっています。

リリースは2026年7月予定。いきなり来るわけではなく、npm 11.16.0以降では事前に警告が出るようになっていて、移行準備ができます。

自動実行には、気づかないうちに受けていた恩恵もあった

ここまで「自動実行は危ない」と書いてきましたが、公平のために逆側も書きます。postinstallの自動実行は、長年エンジニアが意識しなくても受けていた便利さを支えてきました。むしろ「npm installすれば全部いい感じに整う」のは、この仕組みのおかげです。

自動実行が支えていたもの具体例
ネイティブモジュールのビルドbetter-sqlite3 / bcrypt など、環境に合わせてC/C++をコンパイル
バイナリ・ブラウザのダウンロードesbuild / Cypress / Puppeteer が実行ファイルやブラウザを取得
トランスパイラ等の後処理@swc/core / core-js などが postinstall で初期化・後処理

では、自分が使っているライブラリは具体的にどうなるのか。実際にnpmレジストリで各パッケージのインストール時スクリプトを確認し、影響を早見表にまとめました(執筆時点の最新版で確認)。

人気ライブラリインストール時にやること許可しないと起きること
esbuildpostinstall でプラットフォーム別バイナリを取得esbuild本体が動かない。Vite など多くのビルドツールが内部で依存するため影響が広い
better-sqlite3install で node-gyp によるネイティブビルドSQLiteドライバを読み込めず、DBアクセスが落ちる
bcryptinstall でネイティブビルドパスワードのハッシュ処理が動かない
Cypresspostinstall でテスト実行用バイナリを取得E2Eテストが起動しない
Puppeteerpostinstall で Chromium 本体をダウンロード操作対象のブラウザが無く、スクレイピングやテストが不可
@swc/corepostinstall でネイティブ変換まわりの後処理ビルド・トランスパイルに影響が出る場合がある
core-jspostinstall で後処理(メッセージ表示など)実害は小さく、許可しなくても通常は問題ない

逆に、よく使われるHusky(Gitフック管理ツール)は影響を受けにくい側です。Huskyのフック設定は依存パッケージではなく、あなた自身のpackage.jsonに書いたprepareスクリプトで動きます。v12がブロックするのは依存パッケージのスクリプトだけで、自分のプロジェクトに書いたスクリプトは今まで通り動くからです。同じくsharpのように、最近インストール時スクリプトをやめて事前ビルド済みバイナリの配布に切り替えたライブラリも、影響を受けません。

これらはnpm installの裏で黙って走り、私たちはその存在すら意識せずに「動く環境」を手にしていました。v12以降は、こうした正規の便利なスクリプトも、最初の1回は自分で許可する必要があるということです。安全と引き換えに、ほんの少しの手間が戻ってくる。要は「黙って全部やる」から「やっていいか一度聞く」への転換で、聞かれて困るのは攻撃者だけ、という設計です。

自分の package.json で試す: 影響チェッカー

早見表だけでは「結局うちのプロジェクトはどうなの」が分かりにくいので、簡易的なチェッカーを用意しました。お手元のpackage.jsonを丸ごと貼り付けて「チェックする」を押すと、dependenciesなどの中から、v12で承認が必要になりそうなパッケージを洗い出します。

すべての処理はあなたのブラウザ内だけで完結します(外部への送信は一切ありません)。判定はnpmレジストリで実際に確認した既知パッケージのリストとの照合です。

注意: これは直接依存(package.jsonに書かれたもの)だけを、既知リストと照合する簡易判定です。さらに奥にある推移的依存や、リストにない自作・マイナーなパッケージは判定できません。正確な一覧は、npm 11.16.0以降でnpm approve-scripts --allow-scripts-pendingを実行して確認してください。

v12が来る前に、今やっておくべきこと

7月を待たずに、今のうちに慣らしておくと安全です。手順はシンプルです。

  • 1. npmを11.16.0以降に上げる(npm install -g npm@latest
  • 2. npm approve-scripts --allow-scripts-pendingで、スクリプトを持つ依存を洗い出す
  • 3. 中身を確認し、信頼できるものだけnpm approve-scriptsで許可する
  • 4. 更新されたpackage.jsonコミットして共有する

とくに気をつけたいのがCI(継続的インテグレーション)です。クリーンな環境でnpm ciを回しているプロジェクトは、許可リストをコミットし忘れると、v12へ上げた瞬間にネイティブビルドやバイナリ取得が止まり、ビルドが壊れる可能性があります。あらかじめallowScriptsを整えておけば、その日も静かに通過できます。

まとめ: 「黙ってやる」から「一度だけ聞く」へ

実際に手元で動かしてみて、いちばん怖かったのは攻撃そのものより、「added 1 package」としか出ないのにコードが走っていたあの静けさでした。普段あれだけ叩いているコマンドが、裏で何をしていたかを正直あまり考えていなかったな、と反省しました。

v12の変更は、便利さを少し削る代わりに、その静けさをなくします。これまで自動で受けていた恩恵は、最初の1回だけ「許可」という形で意識することになります。手間は増えますが、その手間で困るのは攻撃者だけ。npm installという、世界で最も打たれるコマンドの1つにとっては、妥当な落としどころだと思います。7月が来る前に、一度approve-scriptsを触っておきましょう。

参照元