npm `postinstall` の闇を暴く:サプライチェーン攻撃を封殺する「ゼロトラスト・パッケージ管理」の極意
テックリードとして現場を見ていて戦慄するのは、`npm install` を「ただの依存関係解決コマンド」だと思っているエンジニアの多さです。
`postinstall` スクリプトは、インストール直後に任意のコードをOS権限で実行できるバックドアそのものです。依存パッケージが汚染されていれば、あなたのローカル環境やCI環境の認証情報は、実行した瞬間に外部へ送信される可能性があります。
本稿では、利便性を殺さずに、かつ強固な防壁を築くための「npm/pnpmの防御的運用アーキテクチャ」を伝授します。
—
1. なぜ `postinstall` は「諸刃の剣」なのか
`postinstall` は、C++で書かれたネイティブモジュールのビルドや、環境に応じたアセットの生成に不可欠です。しかし、これが悪用されると以下のような攻撃が成立します。
- 環境変数の窃取: `.env` ファイルや `~/.ssh` へのアクセス。
- バックドアの仕込み: 開発環境のビルドプロセスに悪意あるコードを注入。
- 永続化: シェル設定ファイル(`.zshrc`など)への書き込み。
「信頼できるパッケージしか入れない」という精神論は、エコシステムの巨大化に伴い崩壊しました。我々が取るべきは、「デフォルトで全てを拒否し、必要なものだけを許可する」というゼロトラストの姿勢です。
—
2. 究極の防御術:`ignore-scripts` を戦略的に使う
npm v7以降、`–ignore-scripts` は単なるオプションではなく、セキュリティポリシーの一部として組み込むべきです。
全体的な無効化とCIでの運用
CI環境では、原則として全スクリプトの実行を禁止すべきです。
CI環境で実行するnpm installの鉄板コマンド
依存関係のインストールのみを行い、スクリプト実行を完全にブロックする
npm ci –ignore-scripts
しかし、これだけでは「必要なビルド」も止まってしまいます。ここでアーキテクトが推奨するのは、「pnpm」への移行または「npmのallow-scripts」による制御です。
pnpmによる「パッケージ単位の実行制御」
pnpmは最初からセキュリティを念頭に設計されており、`.npmrc` で実行可能なパッケージを明示的にホワイトリスト化できます。
`.npmrc` のベストプラクティス設定:
デフォルトで全スクリプトの実行をブロック
ignore-scripts=true
ただし、信頼できる特定のパッケージのみ実行を許可する(正規表現も可)
許可するパッケージを明示することで、サプライチェーン攻撃の範囲を限定する
allowed-deprecated-extensions=false
public-hoist-pattern[]=@types
特定のビルドツールのみ実行を許可
allowed-scripts=@my-org/core-builder,esbuild,node-gyp
—
3. 開発スピードを底上げする「設定共有化」の設計
チーム開発において、個々人の環境でセキュリティルールがバラバラなのは脆弱性の温床です。設定ファイルをプロジェクト管理下に置くルールを徹底してください。
プロジェクトルートでの一元管理
`package.json` の `scripts` を外部化し、CI/CDパイプラインとローカル環境で同じ挙動を保証します。
// package.json の構成案
{
“scripts”: {
// セキュリティリスクを考慮し、postinstallを直接書かず、
// 検証済みビルドスクリプト経由で実行させる
“postinstall”: “node scripts/verify-integrity.js && node scripts/build-native.js”,
“preinstall”: “node scripts/check-lockfile-version.js”
}
}
`scripts/verify-integrity.js` の役割:
インストールされるパッケージのハッシュ値を検証し、`package-lock.json` が改ざんされていないかをCIのフェーズで確認します。
—
4. 現場で震えるほど役立つ「神」テクニック
① `npm audit` をパイプラインのゲートキーパーにする
単に脆弱性を表示するのではなく、CIの終了コードを強制的に失敗させます。
重大度が高い脆弱性が見つかったら即座にビルドを中断する
npm audit –audit-level=high –json | jq -e ‘.metadata.vulnerabilities.high == 0’
② `npm-check-updates (ncu)` で依存関係をクリーンに保つ
古い依存関係はセキュリティの穴です。しかし、一括アップデートは事故の元。`ncu` でインタラクティブに更新し、かつ `npm shrinkwrap` や `lockfile` で依存のツリーを固定する運用を徹底してください。
—
5. まとめ:テックリードとしての提言
セキュリティと開発体験(DX)はトレードオフではありません。「無防備な便利さ」を捨て、「管理された安全性」を導入することこそが、長期的な開発速度の最大化に繋がります。
1. CI/CDでは常に `–ignore-scripts` をデフォルトにする。
2. `pnpm` を採用し、`.npmrc` で実行可能スクリプトをホワイトリスト化する。
3. 依存関係の更新プロセスを自動化し、定期的な監査をCIの標準パイプラインに組み込む。
あなたが今日設定したその一行が、明日発生するかもしれない数千万円規模のサプライチェーン事故を防ぐ盾となります。今すぐプロジェクトの `.npmrc` を開き、防御の要を固めてください。それが、プロのエンジニアの流儀です。