サプライチェーン攻撃対策

この記事はClaude Opus 4.8 の言語処理能力を確認する意味も込めてAIで書かせた記事となります

プロンプト
Q. ブログ記事では、どんなトピック/テーマを扱いますか
A. サプライチェーン攻撃とその対策

Q. 記事の主軸はなんですか
A. パッケージマネジャーによる対策、感染した場合の被害の最小限化

完璧な防御は存在しない。「防ぐ」と「諦めない」の両構えで

広く使われるOSSパッケージが汚染される事例は、近年あとを絶ちません。開発者の手元やCIが攻撃の起点になるリスクも、確実に高まっています。ソフトウェアサプライチェーン攻撃について、まず受け入れておきたい現実があります。それは、侵入を100%防ぐことはできないということです。自分が書いていないコードを大量に取り込んで動かす以上、どうしてもどこかに信頼の穴は残ってしまうからです。

完璧に防げない以上、対策は「入られない努力」と「入られた前提の設計」の両面で考えておきたいところです。本記事では、その具体的な切り口として次の二段構えを取り上げます。

  1. 予防(侵入されにくくする):汚染されたパッケージやスクリプトを取り込みにくい仕組みを作る
  2. 被害局所化(侵入されても被害を最小化する):万一取り込んでしまっても、認証情報や本番環境への被害を限定する

この2つを両輪で回していく、というイメージです。

もちろん、サプライチェーン対策はこれだけではありませんが、本記事ではその中から、「予防」と「被害局所化」という切り口を一例として取り上げ、パッケージマネージャやシークレット管理の具体的な設定レベルまで落とし込んで整理していきます。

サプライチェーン攻撃とは何か

まずは、攻撃経路を少し整理しておきましょう。主な侵入口は次の3つです。

  • 依存パッケージの汚染:正規のパッケージが乗っ取られたり、メンテナのアカウントが侵害されたりして、悪意あるコードが正規バージョンとして公開されてしまうケースです。typosquatting(正規名に酷似した偽パッケージ)もここに含まれます。
  • ビルド/CIの侵害:ソースコード自体はクリーンでも、ビルドパイプラインやCI環境に侵入され、成果物に悪意あるコードが混入してしまうケースです。
  • インストール時スクリプトの悪用:npmの postinstall など、install するだけで実行されるスクリプトに悪意あるコードを仕込む手口です。npm install した瞬間に、開発者の手元やCIで任意コードが走ってしまいます。

影響範囲の大きさを示す実例として、2026年3月31日に発生した axios のサプライチェーン侵害を見てみましょう。侵害されたメンテナのnpmアカウント経由で、悪意ある2バージョン [email protected][email protected] がnpmに公開されました。公開時間はおよそ3時間(約 09:21 公開〜12:29 削除)と短かったのですが、axios は週1億回以上ダウンロードされるため、その間に npm install した開発環境やCI/CDが侵害されうる、甚大な影響範囲でした。

手口はかなり巧妙でした。axios本体のソースは改変せず、攻撃用に作られた依存 [email protected] を新バージョンの依存に追加していたのです。インストール時に postinstall フック(node setup.js が走り、二重難読化されたドロッパーがOSを判別してC2サーバから各OS向けのRAT(リモートアクセス型マルウェア)を取得・実行し、GitHubトークンやクラウド認証情報などを窃取したとされています。攻撃者はクリーンな [email protected] を約18時間前に先行公開して履歴を作り、「新着パッケージ」検知をかわそうとした形跡もありました。

なぜ今、リスクが高まっているのか

サプライチェーン攻撃が以前より深刻になっている理由は、大きく3つあります。

  • OSS依存の増大:現代のアプリケーションは、コードの大半を外部パッケージに依存しています。直接依存は数十でも、推移的依存(依存の依存)まで含めると数百〜数千に膨らむことも珍しくありません。
  • 依存関係の深さ:自分が選んだわけではないパッケージが、依存ツリーの奥深くで実行されています。攻撃者は「広く使われているのに、目の届きにくいパッケージ」を狙ってきます。
  • AIの発達による攻撃ハードルの低下:脆弱性探索やスクリプトの生成といった作業を、攻撃者がAIで効率化しうる、という側面もあります。結果として、攻撃のスピードと量が増す方向に働く可能性があります。

3つ目については断定的な統計があるわけではないのですが、防御側の前提を「攻撃は速く・多く・巧妙になりうる」へ置き換えておきたい、という問題提起として捉えていただければと思います。

対策①:サプライチェーン攻撃の侵入を防ぐ(予防)

予防の主戦場は、パッケージマネージャの設定にあります。デフォルト設定のまま使うのではなく、攻撃面を少しずつ減らしていきましょう。

インストール時スクリプトを無効化する

postinstall などのライフサイクルスクリプトは、攻撃者にとって「インストールだけで任意コードを実行できる」格好の入口です。実際に前述のaxios侵害でも、追加された依存の postinstallnode setup.js)が起点になっていました。ここを既定で無効化しておきましょう。

# 単発で無効化
npm install --ignore-scripts
# .npmrc に設定して恒常化
ignore-scripts=true

pnpm では、依存パッケージのビルド/ライフサイクルスクリプトを既定でブロックする挙動が v10.0.0(2025年1月) で導入されました。許可するパッケージだけを onlyBuiltDependencies に明示する、「既定でブロック → 許可リストで明示許可」という方針が取れます。

# pnpm-workspace.yaml(pnpm v10以降)
onlyBuiltDependencies:
  - esbuild
  - sharp

トレードオフ:ネイティブモジュールのビルドなど、スクリプト実行を前提とするパッケージは動かなくなることがあります。その場合は全面禁止にするのではなく、「既定は無効化し、必要なパッケージだけ明示的に許可リストへ加える」という運用が現実的でしょう。

公開直後のバージョンを即採用しない(min-release-age)

悪意あるバージョンは、公開直後に発見・撤回されるケースが多いです。axiosの汚染版も数時間で削除されました。公開からの経過時間で足切りをすれば、撤回前の汚染バージョンや、検知回避のために先行公開された依存を即座に取り込んでしまうリスクを下げられます。

npm では npm 11.10.0(2026年2月リリース)以降min-release-age 設定が利用できます。値は日数で指定し、指定日数より新しいバージョンはインストールがブロックされます。

# .npmrc(npm 11.10.0 以降)
# 公開から7日経過したバージョンのみ採用
min-release-age=7

pnpm では、同等の機能を minimumReleaseAge(単位は)として pnpm-workspace.yaml に設定します。この設定は pnpm 10.16.0 以降で導入されました。

# pnpm-workspace.yaml(pnpm 10.16.0 以降)
# 公開から一定時間が経過したバージョンのみ採用(例: 7日 = 10080分)
minimumReleaseAge: 10080

設定キー名と単位がツールで異なる点(npm=日、pnpm=分)には注意しておきたいところです。

数日〜1週間の「寝かせ期間」を設けるだけで、コミュニティが異常に気づくまでの猶予を確保できます。最新を即追いしたい気持ちと、安全マージンとのバランスを見ながら日数を決めていきましょう。

lockfile・バージョン固定・信頼できるレジストリ

  • lockfileを必ずコミットし、CIでは npm ci / pnpm install --frozen-lockfile を使う:解決済みの依存とハッシュを固定し、ビルドごとに依存がすり替わるのを防ぎます。
  • バージョンを固定する^~ による広い許容範囲は、意図しないバージョンの自動取り込みにつながります。重要な依存は厳密に固定しておくと安心です。
  • レジストリを限定する:社内ミラーや信頼できるレジストリを経由し、取り込み口を一本化します。

対策②:サプライチェーン攻撃で侵入されても被害を最小化する(被害局所化)

予防をすり抜けて悪意あるコードが動いてしまった場合、クレデンシャル(認証情報)の窃取は代表的な攻撃目標の一つです。ここを守れるかどうかで、被害の大きさが大きく変わってきます。

シークレットを集中管理し、必要時にだけ注入する

認証情報を守る第一歩は、シークレットを「どこに・どう置くか」を見直すことです。避けたいのは、.env ファイルに本番のAPIキーやトークンを平文で書き、それがローカルや各所に散在している状態。汚染パッケージが process.env やファイルシステムを読むだけで、すべてが流出してしまいます。

対策は、シークレットを 1Password などのシークレットマネージャ / Vault で集中管理し、実行時に必要なものだけ注入する設計にすることです。

# 例: 1Password CLI でシークレットを実行時にだけ注入する
# .env に平文で置かず、参照(op://...)だけをコミットする
op run --env-file=.env.tmpl -- npm start

コミットするのは秘密値そのものではなく、1Passwordへの参照だけです。テンプレート(.env.tmpl)には、次のように参照記法(KEY=op://vault/item/field)を書いておきます。

# .env.tmpl(このファイルだけをコミットする)
DATABASE_URL=op://Production/database/url
STRIPE_API_KEY=op://Production/stripe/api_key

op run の実行時に参照が解決され、実値が環境変数として注入されます。

平文の秘密情報を手元に常駐させないことで、漏洩時の影響範囲を「その瞬間に注入されていた最小限」に抑えられます。

最小権限・環境分離・通信制御

  • 最小権限・スコープ限定のトークン:トークンには必要最小限の権限と、短い有効期限を与えましょう。本番への書き込み権限を、開発用トークンに持たせないのがポイントです。
  • CI/開発環境の分離:開発環境の汚染が本番の認証情報へ直結しないよう、権限と環境を分けておきます。
  • 外向き通信の制限:ビルドやCIの環境から、不要な外部への通信を制限(エグレス制御)します。シークレットを盗まれても外部へ送信できなければ、被害は完結しにくくなります。

これらは「侵入された後、攻撃者が手にできるものを減らす」ための施策です。予防がザルでも、ここが固ければ致命傷は避けられます。

まとめ:対策は「効果が高くて、すぐ効く」順に積む

サプライチェーン攻撃は防ぎきれません。だからこそ大事なのは、限られた工数をどの対策から振り向けるかです。本記事で挙げた打ち手を導入コストと効果で並べ直すと、着手すべき順番は自ずと決まってきます。

まず今日:コストほぼゼロ、効果大

  • .npmrcignore-scripts=true を設定する — axios侵害でも起点になった「インストールした瞬間に任意コードが走る」経路を、一行で塞げます。
  • lockfileのコミットを徹底し、CIを npm ci / --frozen-lockfile に統一する — ビルドごとに依存がすり替わるのを止められます。

次の一手:少し設計が要る、効果大

  • min-release-age / minimumReleaseAge で「寝かせ期間」を導入する — 撤回前の汚染バージョンを踏むリスクを下げられます。
  • シークレットをシークレットマネージャ/Vaultで集中管理し、実行時にだけ注入する — 万一窃取されても、漏れる範囲を最小限に抑えられます。

腰を据えて:運用と権限の設計

  • トークンの最小権限化、開発/本番の認証情報の分離、外向き通信の制限 — 「侵入された後、攻撃者が手にできるもの」を削っていきます。

私たちのチームでも、最初に手を付けたのは上の2つ(ignore-scripts とCIの npm ci 統一)でした。コストはほぼゼロなのに、塞げる経路は大きい。上から順に潰していくだけで、攻撃面は確実に縮んでいきます。

慣れてきたら、本記事では深掘りしなかったSCAやSBOM、署名・provenanceの検証といった打ち手も重ねていけば、防御はさらに厚くなります。「予防」と「被害局所化」を、効く順に少しずつ厚くしていくこと。それが、変化し続ける脅威に対する現実的な備えになるはずです。

コメントを残す

メールアドレスが公開されることはありません。*がついている欄は必須項目です。

日本語が含まれない投稿は無視されますのでご注意ください。