先に結論だけ
npmサプライチェーン攻撃対策の最初の一歩として、以下の3つを設定します。
ignore-scripts=true
min-release-age=7
cooldown:
default-days: 7
追加パッケージは不要です。設定ファイルの変更だけで被害を軽減できます。
はじめに
対象読者
npmを使うNode.js開発者で、サプライチェーンセキュリティ対策をどこから手をつけていいかわからない方を対象とします。サプライチェーン攻撃の仕組みや、セキュリティツールの経験は前提としません。
対象環境
- npm v11.10.0以降(
min-release-ageの利用に必要) - GitHubリポジトリでDependabotを利用している環境
npmのバージョンはnpm --versionで確認できます。
npmサプライチェーン攻撃とは
npmパッケージのインストールや更新を悪用して、開発者の環境で悪意あるコードを実行させる攻撃です。攻撃者は正規のパッケージを乗っ取ったり、名前が似た悪意あるパッケージを公開したりして、npm installの実行時にマルウェアを仕込みます。
攻撃は以下の2つの手法を組み合わせて成立します。
- 悪意ある依存の注入 : 正規パッケージの依存先に悪意あるパッケージを追加し、間接的にマルウェアをインストールさせる
- ライフサイクルスクリプトの悪用 : 注入されたパッケージの
postinstallスクリプトで悪意あるコードを実行し、npm installだけでマルウェアを起動させる
悪意あるパッケージの多くは公開後短期間で検出・対処されます。以下はいずれも公開当日に検出された事例です。
本記事の対策は、公開直後のパッケージのインストールを遅らせて防御します。コミュニティやセキュリティ企業が短時間で悪意あるパッケージを発見・削除できることがこの防御策の前提です。
攻撃のより詳しい仕組みについては1Password Shell Plugin + GitHub Fine-Grained PATによるnpmサプライチェーン攻撃の感染拡大防止策を参照してください。
対策1: ignore-scripts
概要
npmパッケージにはpreinstall、postinstallなどのライフサイクルスクリプトを設定でき、npm install時に自動実行されます。サプライチェーン攻撃の主要な実行経路がこのライフサイクルスクリプトです。ignore-scripts=trueを設定すると、これらのスクリプト実行を無効化できます。
設定方法
グローバルのnpm設定ファイル~/.npmrcに以下を追加します。
ignore-scripts=true
この設定により、npm install時のライフサイクルスクリプト(preinstall、install、postinstallなど)が実行されなくなります。npm startやnpm testなどのユーザー定義スクリプトは引き続き実行されます。
例外的な有効化
bcryptやsharpなどネイティブアドオンを含むパッケージは、ビルドスクリプトの実行が必要です。ignore-scripts=trueの環境ではnpm rebuildで個別にビルドスクリプトを実行します。
npm rebuild sharp
npm rebuildは指定したパッケージのビルドスクリプトのみを実行します。プロジェクト内の全パッケージのスクリプトが実行されるわけではありません。
npm v11時点では、パッケージ単位でスクリプト実行を許可するビルトイン機能はありません。npm RFCs Discussion #80でallowedScriptsフィールドが提案されていますが、未実装です。
対策2: min-release-age
概要
min-release-ageはnpm v11.10.0で導入されたオプションで、公開から指定日数が経過していないパッケージのインストールを拒否します。悪意あるパッケージが検出・削除されるまでの猶予を確保する仕組みです。
zizmorの作者William Woodruffの分析によると7日間のクールダウンが推奨されています。
Woodruffの分析はサンプルサイズ10件と小さく、本人も "very small sample set" と認めています。7日間は目安であり、プロジェクトのリスク許容度に応じて調整してください。
設定方法
npm設定ファイル~/.npmrcに以下を追加します。
min-release-age=7
ターミナルから以下のコマンドでも設定できます。
npm config set min-release-age 7 --global
値は日数で指定します。上記の設定では、公開から7日未満のパッケージバージョンのインストールが拒否されます。
min-release-ageとnpx
min-release-ageオプションは、npxコマンドにも影響を与えます。パッケージのダウンロードと即時実行を合わせて行うnpxコマンドでも、min-release-ageオプションが尊重され、この条件を満たさないパッケージは実行されません。
対策3: Dependabot cooldown
概要
Dependabotのcooldownは、パッケージの公開日から一定期間が経過するまでPR作成を遅らせる機能です。CI/CDパイプラインでのnpm installは開発者のローカル.npmrc設定を参照しないため、ignore-scriptsやmin-release-ageによる保護が効きません。cooldownはこのCI/CD側の保護を担います。
設定方法
.github/dependabot.ymlにcooldownフィールドを追加します。
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
cooldownはバージョン更新(Dependabot version updates)にのみ適用されます。セキュリティアップデート(Dependabot security updates)には適用されません。セキュリティアップデートは脆弱性修正のため、遅延なく適用されます。
semverレベル別の待機期間
cooldownではsemverレベルごとに待機日数を設定できます。
| フィールド | 説明 |
|---|---|
default-days |
デフォルトの待機日数 |
semver-major-days |
majorバージョン更新の待機日数 |
semver-minor-days |
minorバージョン更新の待機日数 |
semver-patch-days |
patchバージョン更新の待機日数 |
個別のフィールドが設定されていない場合はdefault-daysの値が使われます。majorバージョン更新は破壊的変更を伴うことが多く、慎重な対応が求められるため、より長い待機期間を設定するのが一般的です。
includeとexcludeフィールドで対象パッケージを制限することもできます。excludeはincludeより優先されます。詳細はDependabot公式ドキュメントを参照してください。
3つの対策の補完関係
3つの対策はそれぞれ異なるレイヤーと攻撃経路を保護します。
| 対策 | 保護対象 | 防御メカニズム | カバーする攻撃経路 |
|---|---|---|---|
ignore-scripts |
ローカル環境 | スクリプト実行の遮断 | ライフサイクルスクリプト |
min-release-age |
ローカル環境 | 時間ベースのゲート | 両方 |
Dependabot cooldown
|
CI/CDパイプライン | 時間ベースのゲート | 両方 |
ignore-scriptsはライフサイクルスクリプト経路の攻撃を即座に遮断しますが、悪意ある依存の注入には効果がありません。min-release-ageとcooldownは時間ベースのゲートとして、経路を問わず悪意あるパッケージが検出・削除される猶予を確保します。
また、ignore-scriptsとmin-release-ageはローカルの.npmrcで機能しますが、CI/CDパイプラインにはおよびません。Dependabot cooldownがCI/CD側の保護を担うことで、開発ワークフロー全体をカバーします。
この3つの対策を足がかりに、あなたのプロジェクトに合わせた防御層を重ねていくことで、それぞれの環境に合った防御策を組み上げてください。
まとめ
本記事では、npmサプライチェーン攻撃対策の最初の一歩として3つの設定を紹介しました。
-
ignore-scripts=true: ライフサイクルスクリプトの実行を無効化し、攻撃の主要な実行経路を遮断する -
min-release-age=7: 公開直後のパッケージのインストールを拒否し、検出・削除までの時間的猶予を確保する -
Dependabot cooldown: CI/CDレベルで公開直後のバージョンへのPR作成を遅延し、自動更新による意図しないインストールを防ぐ
これらの設定だけでは完全な防御にはなりません。認証情報の保護やGitHubリポジトリへの感染拡大防止については、1Password Shell Plugin + GitHub Fine-Grained PATによるnpmサプライチェーン攻撃の感染拡大防止策を参照してください。
参考
- npm config: ignore-scripts
- npm config: min-release-age
- npm CLI changelog (v11.10.0)
- Dependabot cooldown configuration
- Dependabot supports configuration of a minimum package age (GitHub Changelog)
- William Woodruff "We should all be using dependency cooldowns"
- William Woodruff "cooldowns redux"
- Andrew Nesbitt "Package Managers Need to Cool Down"
- pnpm minimumReleaseAge
- npm RFCs Discussion #80 (opt-in scripts)
- Popular Tinycolor npm Package Compromised in Supply Chain Attack (Socket.dev)
- Supply Chain Attack on Axios Pulls Malicious Dependency (Socket.dev)
- Trivy Under Attack Again: Widespread GitHub Actions Tag Compromise (Socket.dev)
- 1Password Shell Plugin + GitHub Fine-Grained PATによるnpmサプライチェーン攻撃の感染拡大防止策