npm サプライチェーン攻撃に対するシンプルな防御策
npm install を実行するたびに、あなたは一度も読んだことのない何百ものパッケージのコードを信頼していることになります。攻撃者が悪用するのは、まさにその信頼です。2025年9月の Shai-Hulud ワームでは、悪意あるインストール時スクリプトが認証情報を密かに収集し、侵害されたパッケージを大規模に再公開しました——しかもコマンドプロンプトが戻ってくる前にすべてが完了していました。2026年3月の Axios 侵害も同じパターンを踏襲しており、悪意あるバージョン(1.14.1 および 0.30.4)が postinstall フックを持つ依存関係を引き込み、リモートアクセスペイロードを実行しました。
この種のリスクを軽減するために、重厚なツールは必要ありません。たった1つの設定変更で、最も一般的な攻撃ベクトルに直接対処できます。
重要なポイント
- npm のライフサイクルスクリプト(
preinstall、install、postinstall、prepare)は、ユーザーの完全な権限で自動的に実行されるため、サプライチェーン攻撃にとって最も摩擦の少ない経路となる。 .npmrcにignore-scripts=trueを設定することで、最も一般的な攻撃ベクトルをわずか1行の設定でブロックできる。sharp、bcrypt、sqlite3などのネイティブバイナリ系パッケージも、スクリプトなしでインストールした後にnpm rebuildを使って選択的にビルドできる。package-lock.jsonに新たな"hasInstallScript": trueエントリが追加されていないかを CI でチェックすることで、マージ前にレビューが必要な依存関係を検出できる。ignore-scripts=trueをmin-release-age=7と組み合わせることで遅延が加わり、侵害された直後のパッケージバージョンを避けやすくなる。
なぜライフサイクルスクリプトが核心的なリスクなのか
npm パッケージをインストールすると、Node はその package.json に定義されたスクリプト——preinstall、install、postinstall、prepare——を自動的に実行します。これらのフックはあなたの完全なユーザー権限で実行され、~/.ssh、~/.aws/credentials、~/.npmrc のトークン、そしてシェル内のすべての環境変数にアクセスできます。
確認プロンプトもありません。サンドボックスもありません。ただ静かに実行されるだけです。
Axios 攻撃では、plain-crypto-js という名前の悪意ある間接依存関係が postinstall フックを使ってリモートアクセス型トロイの木馬を投下しました。Bitwarden CLI なりすまし攻撃では、preinstall フックが大規模な難読化されたペイロードをブートストラップし、クラウド認証情報を収集して、被害者の npm 公開アクセスを通じて伝播しようと試みました。
ライフサイクルスクリプトのセキュリティが重要なのは、JavaScript の依存関係セキュリティ全体において最も摩擦の少ない攻撃経路を表しているからです。
中核的な防御策: npm ignore-scripts
プロジェクトの .npmrc に1行追加します:
ignore-scripts=true
これにより、インストール時にすべてのライフサイクルスクリプトをスキップするよう npm に指示します。悪意ある postinstall フックは単純に実行されません。
.npmrc を変更せずに一度だけインストールする場合:
npm install --ignore-scripts
重要な注意点: これは完全な解決策ではありません。Bitwarden 攻撃では package.json の bin フィールドを介して悪意あるバイナリを登録しており、--ignore-scripts が有効でも、ユーザーが bw コマンドを実行した時点でペイロードが実行されました。単一のフラグですべてのリスクを排除することはできません。
何が壊れるか、そしてどう対処するか
一部の正当なパッケージは、ネイティブバイナリのコンパイルにライフサイクルスクリプトを必要とします。一般的なものとしては、sharp、bcrypt、sqlite3、canvas、そして(Chromium をダウンロードする)puppeteer などがあります。
プロジェクトでこれらが使われているかチェックします:
npm ls --all | grep -E "(sharp|bcrypt|sqlite3|canvas|puppeteer|node-gyp)"
該当があれば、インストール後にそれらのパッケージのみをリビルドします:
npm install --ignore-scripts
npm rebuild sharp --ignore-scripts=false
npm rebuild bcrypt --ignore-scripts=false
このアプローチにより、グローバルにはスクリプトを無効化したまま、明示的にレビュー済みのパッケージに対してのみ選択的にコンパイルを許可できます。
Discover how at OpenReplay.com.
実行前に新しいインストールスクリプトを検出する
事後対応するのではなく、インストールスクリプトを持つ新しい依存関係をマージ前にフラグ付けしましょう。次のチェックを CI パイプラインに追加します:
# PR内で新たに追加された install script を持つパッケージを検出
if git diff origin/main -- package-lock.json | \
grep -E '^\+\s*"hasInstallScript": true' > /dev/null; then
echo "⚠️ New install script detected—manual review required"
exit 1
else
echo "✅ No new install scripts"
fi
Axios 攻撃では、plain-crypto-js は postinstall フックを持つ完全に新しい依存関係でした。このチェックがあれば、PR がマージされる前に検出できていたはずです。
npm がロックファイルにインストールスクリプトをどのように記録するかの詳細は、公式の package-lock.json ドキュメントを参照してください。
これらの防御策がカバーしないもの
その限界について正直に認識しておきましょう:
- ロックファイルは、開発者が侵害期間中に
npm installを実行し、あなたが気付く前にロックファイルを汚染した場合の攻撃を防げません。 npm auditは既知の悪意あるパッケージのみを検出します——新規の攻撃はそのデータベースには現れません。- npm アカウントの 2FA は、侵害されたメンテナーによって正規に公開されたパッケージから利用者を保護しません。
--ignore-scriptsは、パッケージのランタイム JavaScript 自体に埋め込まれた悪意あるコードはブロックしません。
階層的アプローチが有効です。.npmrc で ignore-scripts=true と min-release-age=7 を組み合わせることで(npm v11.10.0 以降が必要)、過去1週間以内に公開されたパッケージのインストールを回避できます——多くの攻撃が活発に行われ、まだ検出されていない期間です。この設定は npm の config ドキュメントの min-release-age に記載されています。
まずはここから始めよう
今日、.npmrc に次の行を追加してください:
ignore-scripts=true
min-release-age=7
そして、package-lock.json に変更を加えるすべての PR に対して、新たな hasInstallScript エントリを検出する CI チェックを追加します。これら2つの変更で、最近の npm サプライチェーン攻撃で使われた攻撃パターンに対処できます——新しいツール、有料サービス、または大幅なワークフロー変更を必要とせずに。
結論
依存しているパッケージのすべての行を読むことはできませんが、それらのパッケージがインストール中に行えることを大幅に制限することはできます。デフォルトでライフサイクルスクリプトを無効化し、信頼するネイティブパッケージのみをリビルドし、CI で新しい hasInstallScript エントリをフラグ付けすることで、npm エコシステムで最も激しく悪用される攻撃ベクトルを無効化できます。これらの対策はいずれも新しいベンダーや予算を必要としません——.npmrc への数行の追加と単一の CI チェックだけです。今日それらを採用すれば、次の Shai-Hulud 型攻撃があなたのプロジェクトに与える影響は大幅に小さくなります。
よくある質問
sharp、bcrypt、sqlite3、canvas、puppeteer など、インストール時にネイティブバイナリをコンパイルするパッケージが壊れる可能性があります。スクリプトを無効化した状態で npm install を実行した後、npm rebuild にパッケージ名と --ignore-scripts=false を続けて指定し、レビュー済みの信頼できるパッケージのみをコンパイルしてください。純粋な JavaScript の依存関係のほとんどは変更なしで動作します。
いいえ。postinstall などのライフサイクルフックという最も一般的なベクトルはブロックしますが、パッケージのランタイム JavaScript 内の悪意あるコードや、bin フィールドを介して登録された悪意あるバイナリは止められません。Bitwarden CLI なりすまし攻撃はこの方法で ignore-scripts を回避しました。多層防御のために min-release-age、ロックファイルレビュー、依存関係監査と組み合わせてください。
min-release-age 設定には npm バージョン 11.10.0 以降が必要です。npm --version でバージョンを確認し、必要に応じて npm install -g npm@latest を使ってアップグレードしてください。設定後は npm config get min-release-age で設定が有効になっているか確認してください。
ignore-scripts=true を含む .npmrc ファイルをリポジトリにコミットすれば、CI が自動的に設定を継承します。本当にネイティブコンパイルが必要なビルドでは、必要な特定のパッケージに対して(--ignore-scripts=false 付きの)明示的な npm rebuild コマンドを追加してください。これにより、安全なデフォルトを維持しながら、どのパッケージにインストールコードの実行を許可しているかを明確に文書化できます。
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.