はじめに
第1回でaxiosの事例を通じて供給チェーン攻撃がどのように機能するかを確認し、第2回で実際のプロジェクトに適用した3つの防御戦略を解説しました。3つの戦略に共通する原則は一言で要約できます。
新しく公開されたものをすぐに受け入れない。
この記事で検証したい問いは2つです。
- このシンプルな原則は実際にどれほど有効か?
- そして何を防げないのか?
攻撃の時間的パターン
供給チェーン攻撃が時間ベースの防御で防げるという主張は、次の観察から出発します。
悪意あるパッケージが公開されてから検出・削除されるまでの時間は、ほぼ常に数時間から数日以内に収まる。
このパターンが成立する理由は次のとおりです。
- 人気パッケージほど自動セキュリティスキャナー(Socket、Snyk、GitHub Advisoryなど)が即座に分析
- 不審な動作を発見した開発者がすぐにIssueを起票
- メンテナー本人またはnpm運営側が発見次第パッケージをunpublish
つまり攻撃者には、悪意あるバージョンがレジストリに生きている時間 — 公開された瞬間から検出・削除されるまでの短い期間 — の間だけ攻撃の機会が与えられます。この期間をセキュリティ分野では露出ウィンドウ(window of exposure)と呼び、供給チェーン攻撃ではこの期間が通常非常に短いです。数日も経てば悪意あるバージョンはレジストリから消え、その間に新しい正規バージョンが公開されています。新バージョンを数日遅らせて受け取ると決めた人は、この露出ウィンドウを自然に回避できます。
既知インシデントの露出ウィンドウ
代表的なnpm供給チェーンインシデントの露出ウィンドウをまとめると次のとおりです。
| インシデント | 時期 | 露出ウィンドウ(公開→削除) |
|---|---|---|
| ua-parser-js | 2021 | 約4時間 |
| Ledger Connect Kit | 2023 | 約5時間 |
| Solana web3.js | 2024 | 約5時間 |
| axios | 2026 | 約4時間 |
このいずれも24時間を超えていません。つまり、たった1日待つだけで、上記インシデントの悪意あるバージョンを実際のプロダクト環境に取り込まずに済みました。
供給チェーン防御におけるcooldown期間
先に見たように供給チェーン攻撃の露出ウィンドウは通常非常に短いため、「新バージョンを数日遅らせて受け取る」というシンプルなゲートだけで相当な防御効果が得られます。では、そのゲート期間は何日に設定すべきでしょうか?
このゲート期間はツールによって呼び方が異なります。
- Dependabot:
cooldown - pnpm · Renovate · Yarn:
minimumReleaseAge(またはnpmMinimalAgeGate) - bun · uv: それぞれ
minimumReleaseAge、exclude-newer
呼び方は違っても本質は同じです — 「公開されてからN日が経過していない新バージョンはインストール・PR作成の対象から除外する」。そしてこのNの業界推奨値はほぼ一貫して7日前後に収束しています。
このセクションでは、その7日という値を根拠 → 推奨 → 副作用の3つの観点から順番に見ていきます。
1. 根拠: 主要ツールとセキュリティ分析が収束する値
7日は任意に選んだ数字ではなく、実際に複数のパッケージマネージャーとセキュリティ研究・ツールが同様に採用・推奨している値です。
| 出典 | 推奨・デフォルト値 | 備考 |
|---|---|---|
GitHub Dependabot cooldown公式ドキュメント | サンプル設定で7日 | 公式referenceの推奨例 |
pnpm minimumReleaseAge (v11) | デフォルト1日 | 後方互換を考慮した保守的なデフォルト値。ガイドはより長い期間を推奨 |
| pnpm供給チェーンセキュリティガイド | 7日 | 公開後7日未満のバージョンのインストールを強く非推奨 |
| Renovate minimum-release-age | ガイドを提供 | 公式キーコンセプトドキュメントに掲載 |
| Socket | 7日フラグ | 公開後7日未満のバージョンを自動でリスクシグナルとして表示 |
GitHub公式ドキュメント、主要パッケージマネージャー(pnpm・Renovate)、そして供給チェーンセキュリティSaaS(Socket)がいずれも7日前後を「短命な悪意ある公開(short-lived malicious publish)をフィルタリングするのに十分な期間」と見ている点が重要です。
2. 推奨: 7日を出発点として設定する
総合すると推奨は次のとおりです。
新規導入プロジェクトであれば7日を出発点として設定する。
7日が標準的な推奨値として定着した理由は、1日や30日と比較すると明確です。
- 1日では不十分です。利用者が少ないパッケージ・休日・週末の公開・メンテナーの休暇などにより検出が遅れる場合があります。1日は核心的な4〜5時間のインシデントをフィルタリングできる程度です。
- 30日は過剰です。ブロックできるインシデントは若干増えますが、その分新機能・機能改善の導入が遅れます。また依存関係lockfileの鮮度が落ちて別の運用コストが生まれます。
- 7日はその中間の合意点です。ほぼすべての短命な悪意ある公開をフィルタリングしながら、正当な新バージョンを1週間以内に受け取ることができます。
これを実際のインシデントデータで検証すると、既知の主要供給チェーンインシデント21件のうち、7日ゲートでブロック可能なインシデントは11件です。半数以上をシンプルな設定1行で防げるということです。
| インシデント | 時期 | エコシステム | 露出ウィンドウ | 7日ゲートでブロック |
|---|---|---|---|---|
| axios | 2026 | npm | 約4時間 | ブロック可能 |
| Trivy-Action | 2026 | GitHub Actions | 約12時間 | ブロック可能 |
| Nx / S1ngularity | 2025 | npm | 約1日 | ブロック可能 |
| Gluestack / React Native ARIA | 2025 | npm | 数日 | ブロック可能 |
| tj-actions/changed-files | 2025 | GitHub Actions | 約3日 | ブロック可能 |
| reviewdog/action-setup | 2025 | GitHub Actions | 約2時間 | ブロック可能 |
| Ultralytics YOLO | 2024 | PyPI | 約1〜2日 | ブロック可能 |
| Solana web3.js | 2024 | npm | 約5時間 | ブロック可能 |
| Polyfill.io | 2024 | CDN | 約4か月 | ブロック不可 |
| XZ Utils | 2024 | OSパッケージ | 2年以上 | ブロック不可 |
| Ledger Connect Kit | 2023 | npm | 約5時間 | ブロック可能 |
| 3CX | 2023 | デスクトップアプリ | 数週間 | ブロック不可 |
| PyTorch torchtriton | 2022 | PyPI | 約5日 | ブロック可能 |
| node-ipc | 2022 | npm | 数週間以上 | ブロック不可 |
| colors / faker | 2022 | npm | 数日〜数週間 | 部分ブロック |
| Log4Shell | 2021 | Java | 該当なし | ブロック不可 |
| ua-parser-js | 2021 | npm | 約4時間 | ブロック可能 |
| Codecov | 2021 | SaaS | 約2か月 | ブロック不可 |
| Dependency Confusion (Alex Birsan) | 2021 | 複数 | ケースごとに異なる | 部分ブロック |
| SolarWinds | 2020 | ビルドシステム | 約9か月 | ブロック不可 |
| event-stream | 2018 | npm | 約2か月 | ブロック不可 |
部分ブロックは攻撃の一部の段階だけ防げる場合(例:最初の数日間の露出は防ぐが、それ以降の段階は通過)を意味します。単純集計ではブロック可能11件 / ブロック不可8件 / 部分ブロック2件です。
表からもう1つ読み取れる事実は、ブロック不可のインシデントはいずれも7日ゲートの本質的な限界と直結しているという点です。XZ Utils・SolarWinds・event-streamのように露出期間が数か月から数年に及ぶ長期潜入、Polyfill.io・Codecovのようにnpmレジストリ外部で発生した攻撃、Log4Shellのような正常コードの欠陥は、いずれもcooldownでは防げない領域です。これらの限界は後述のどんな攻撃は防げないかで詳しく解説します。
14日・30日に延ばせばブロックできるインシデントが若干増えますが、その分正当なセキュリティパッチを受け取るのも遅くなるというコストが伴います。7日はそのバランスが比較的うまく取れている点であることが、現在の業界のコンセンサスに近いです。
どんな攻撃を防げるか
7日ゲートは次のタイプに効果的です。
1. メンテナーアカウント乗っ取り型
最も多く、最も早く検出されるタイプです。第1回のaxios事例がこれに該当します。乗っ取り → 悪意ある公開 → 検出 → 削除までのサイクルが通常24時間以内に完結するため、7日ゲートでほぼすべてブロックできます。
2. タイポスクワッティング(typosquatting)
react-doomのような人気パッケージに似た名前の偽パッケージを作り、タイポによるインストールを狙う攻撃です。こうしたパッケージは公開直後に自動スキャナーに引っかかり、数日以内に削除されます。
3. 依存関係コンフュージョン(dependency confusion)
内部プライベートパッケージと同じ名前のパッケージを公開レジストリに公開し、ビルドシステムが誤ってそれを取得するよう誘導する攻撃です。これも発見次第削除されるパターンに従います。
4. 素早い依存関係注入型
axios事例のplain-crypto-jsのように、侵害されたパッケージが新しい悪意あるパッケージを新しい依存関係として追加する形です。重要なのは、その新しい依存関係自体が公開されたばかりの新規パッケージだという点です。7日ゲートは侵害されたメインパッケージの新バージョンも、それが引き込む新しい依存関係もいずれもブロックします。
どんな攻撃は防げないか
防御戦略を評価する際に重要な問いは「これが防げないのは何か」です。7日ゲートが無力なケースは次のとおりです。
1. 長期潜入型 — XZ Utils事例
2024年に発見されたXZ Utilsのバックドアは、攻撃者が2年かけてメンテナーとしての信頼を築いた後にバックドアを挿入した事例です。通常の正式リリースを通じてバックドアが入り込み、そのコードは数か月間誰にも疑われませんでした。
7日ゲートは「公開されたばかりのもの」を防ぎますが、公開から数か月が経過して正常と認められたバージョンに対しては無力です。
2. メンテナー本人による意図的なサボタージュ
colors.js(2022)とnode-ipc(2022)の事例のように、メンテナー本人が意図的に悪意あるコードを挿入する場合があります。この場合もコードは正式リリースの手順を経て公開されます。7日ゲートは最初の数日はブロックできますが、メンテナーが意図的にゆっくり進めれば、ゲートを通過した後に悪意あるコードが広まります。
3. ビルドシステムの侵害
SolarWinds(2020)、3CX(2023)のようなインシデントは、パッケージレジストリではなくビルドシステム自体が侵害されたケースです。正常なバージョン番号で正常な手順を通じて配布されましたが、ビルド成果物自体にバックドアが含まれていました。パッケージマネージャーレベルの防御はここには届きません。
4. レジストリ外部からの攻撃
Codecov(2021)はCI環境でダウンロードされるシェルスクリプトが改ざんされたインシデントで、Polyfill.io(2024)はCDNを通じて直接JavaScriptをロードしていたところ、ドメインが買収されて悪意あるコードが注入されたインシデントでした。いずれもnpmゲートとは無関係な経路で侵入します。
5. 合法的なコードに潜む通常の脆弱性
Log4Shellのように、悪意なく書かれた正規ライブラリにセキュリティの欠陥がある場合です。これは供給チェーン攻撃ではなく一般的な脆弱性であり、むしろ素早くパッチを受け取るのが正解です。7日ゲートはこのケースではむしろ欠点になります。
| 攻撃タイプ | 7日ゲートの効果 |
|---|---|
| メンテナーアカウント乗っ取り | 非常に効果的 |
| タイポスクワッティング | 非常に効果的 |
| 依存関係コンフュージョン | 非常に効果的 |
| 素早い依存関係注入 | 非常に効果的 |
| 長期潜入(XZ Utils型) | 無力 |
| メンテナーのサボタージュ | 限定的 |
| ビルドシステムの侵害 | 無力 |
| レジストリ外部(CDN、スクリプト) | 無力 |
| 正規ライブラリの脆弱性 | むしろ欠点 |
防御のコスト
この防御が「シンプルで効果的」と評価される理由は、コストがほとんどかからないからです。
| コスト項目 | レベル |
|---|---|
| 設定変更量 | 設定ファイル数行 |
| 学習コスト | ほぼなし |
| CI/CDパイプラインの変更 | Yarn 4アップグレード時の--immutable置き換え程度 |
| 正当なパッチ受信の遅延 | 平均7日(セキュリティアラートは即時) |
| 運用負担 | ほぼなし(Dependabotが自動処理) |
最大のコストは「正当な新バージョンを7日遅れで受け取ること」ですが、セキュリティアラートはcooldownの対象外であるため、実際には大きな問題になることはほとんどありません。
defense-in-depthの一手段として
重要なのは、この戦略が万能ではなく、そうあるべきでもないという点です。7日ゲートは素早い検出・削除パターンに合わせた時間ベースのフィルターに過ぎません。上で見たように長期潜入、ビルドシステムの侵害、レジストリ外部からの攻撃は別の防御手段が必要です。
ここで言うdefense-in-depth(多層防御)とは、1つの強力な防御だけに依存せず、性質の異なる複数の防御手段を重ねて運用するセキュリティ原則です。ある手段が防げなかった攻撃を次の手段が防ぐという形で、互いの弱点を補い合うように配置します。cooldownはその複数の手段のうちの1つに過ぎません。
cooldownと合わせて運用すると効果的な他の防御手段は次のとおりです。
| 防御手段 | 防ぐ攻撃 |
|---|---|
enableScripts: false | postinstallで実行される即時RCE |
| GitHub Actions SHAピン固定 | Actions自体の改ざん |
| Hardened Mode(Yarn 4) | lockfileの改ざん |
| OIDC / signed publish | 非公式ルートからの公開 |
| SBOM管理 | 依存関係の可視性 |
| 自動セキュリティスキャナー | 既知の脆弱性・悪意あるパターンの検出 |
| コードレビュー | 意図的なサボタージュ(依存関係変更への注意) |
| CDN完全性(SRI) | レジストリ外部の資産改ざん |
第2回で取り上げた3つの戦略は、この表の一部に相当します。残りは組織の状況に応じて段階的に導入できます。
まとめ
このシリーズをまとめると次のとおりです。
- 第1回: npmの信頼モデルは構造的に攻撃にさらされており、
axiosの事例は悪意あるバージョンが生きている露出ウィンドウが非常に短いながらも確実に存在することを示した。 - 第2回: 実際のプロジェクト環境では、GitHub Actions SHAピン固定、Dependabot cooldown、Yarn
npmMinimalAgeGateの3つでその露出ウィンドウを自然に回避できる。 - 第3回(この記事): シンプルな時間ベースのゲートは既知の供給チェーンインシデントのかなりの数を防げるが、万能ではないため他の防御手段と合わせて運用する必要がある。
供給チェーンセキュリティは本質的に確実な一手がない領域です。すべての依存関係を自分で監査することもできないし、すべてのメンテナーの意図を検証することもできません。だからこそ「コストがほとんどかからないシンプルな防御」が輝きます。7日という数字1つに設定ファイル数行、それだけで既知の供給チェーン攻撃の半数以上を防げるという点は — かけた労力に対して得られる効果がこれほど大きい防御という意味で — セキュリティ領域ではなかなか見られない希少なケースです。
あなたのプロジェクトにまだcooldownやminimum age gateがなければ、今が導入する最良のタイミングです。
参考資料
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Dekuが開発したアプリを使ってみてください。Dekuが開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。