第11回 秘密情報とサプライチェーン — 漏らさない仕組みと、拾わない仕組み
AI開発セキュリティ実践の第11回。秘密情報の置き場所と push protection、依存パッケージの検知とインストール時の多層防御、署名だけでは止まらない汚染事例、そしてエージェントに依存を追加させるときの手順を、CI に置く最小構成まで含めて整理します。
この回のキーメッセージ
秘密情報も依存パッケージも、対策の本質は「人が毎回気をつける」を仕組みへ置き換えることです。漏らさない側は push で止め、拾わない側はインストールの手前で止めます。
OWASP Top 10 の 2025年版では、Software Supply Chain Failures が独立したカテゴリとして新設されました。旧版の Vulnerable and Outdated Components を拡張したもので、対象が「古いライブラリを使っていること」から「依存が入ってくる経路そのもの」へ広がっています。実行基盤層のなかでも、この回で扱う2つは影響範囲が最も広い部分です。
秘密情報とサプライチェーンは、本来は別の話題です。片方は自分が外へ出してしまう問題、もう片方は外から入ってくるものを受け入れてしまう問題で、防御の方向が逆を向いています。それでも同じ回にまとめる理由は、AIを使った開発ではこの2つが同じ根から同時に悪化するからです。
第10回まではプラットフォーム側の設定を扱ってきました。この回はリポジトリと開発マシンの側の話です。第12回で扱う初動と地続きなので、「漏れた後どうするか」は最後にそちらへ渡します。
1. AI開発でこの2つが同時に効いてくる理由
第1回で触れた数字をもう一度置きます。GitGuardian の State of Secrets Sprawl 2026 は、Claude Code の支援を受けたコミットでの秘密漏洩率を 3.2%、全体のベースラインを 1.5% と報告しています。同レポートでは、2025年に公開 GitHub コミットへ新規に混入したハードコード秘密が 2,865 万件、前年比 34% 増とされています。
この差の解釈は「AIが秘密情報を作り出す」ではありません。エージェントは動くコードを最短で作るため、接続文字列やトークンをその場に書いた状態をいったん作ります。人が書いていれば「これは後で環境変数へ出す」と覚えているところが、生成物では覚えている主体がいません。差分が大きくなるほど、その1行はレビューの視界から外れます。
依存の側も構造は同じです。エージェントに実装を任せると、「この処理はこのライブラリでできます」という提案とともにインストールが走ります。人間が npm のページを開いて週間ダウンロード数と最終更新日を見る回数は、確実に減ります。名前を目で確認する工程が消えていることが、この層で起きている変化です。
| 何が変わったか | 秘密情報の側 | 依存の側 |
|---|---|---|
| 生成される量 | 動かすための仮の値が量産される | 提案されるパッケージが増える |
| 確認される割合 | 差分が大きくなるほど下がる | 名前を目視する回数が下がる |
| 失敗したときの範囲 | 鍵1本で到達できる範囲すべて | インストールした全マシンとCI |
方針は第1回と同じです。目視を増やして追いつこうとせず、機械で落ちるものを機械で落とします。
2. 秘密を漏らさない — 置き場所を先に決める
検知の話に入る前に、置き場所の原則を確定させます。ここが曖昧なままだと、検知は「毎回同じ場所で鳴るアラート」になります。
環境変数ファイルは、関連するプロジェクトのルート直下に置きます。 /etc 配下のような共有領域へ集約しません。理由は運用時の追跡性で、共有領域に集めると「どのファイルがどのプロジェクトのものか」を毎回調べ直すことになります。systemd の EnvironmentFile=、Docker の --env-file、CI の設定も、同じくプロジェクトルート直下を指すようにします。複数プロジェクトで同じ値を使う場合も、共有ファイルへ集約せず各プロジェクトのルート直下に持たせます。
ルート直下へ置く以上、Git 管理外であることを無視ルールで担保することが前提条件になります。ここは「たぶん入っている」で済ませず、追加した瞬間に確認します。
# 追跡されていないことの確認(何も出力されなければ管理外)
git ls-files --error-unmatch "$ENV_FILE" 2>/dev/null
# 無視ルールがどのファイルのどの行で効いているか
git check-ignore -v "$ENV_FILE"
本番の秘密値は、リポジトリではなく実行基盤の Secrets 機能または Secret Manager へ置きます。プラットフォームごとの具体的な保管先と等級の違いは第10回で扱いました。原則として押さえるのは次の3点です。
- 秘密値をコード、Git 管理下の設定ファイル、DB、KV、ログ、例外メッセージ、API レスポンスへ平文で保存しない
- 設定が不足しているときに認証や検証を迂回しない。値が無いなら起動時か初回利用時に明確に失敗させる
- クライアントバンドルへ公開される接頭辞を秘密値に使わない
3点目は、AI生成コードで実際に混入しやすい形です。NEXT_PUBLIC_ や PUBLIC_ を付ければブラウザ側から参照できるようになるため、クライアントコンポーネントで値が取れないときの最短の解決策として選ばれてしまいます。接頭辞の意味は「公開してよい」であって「クライアントから読める」ではありません。レビューでは、接頭辞付きの変数が増えた差分を必ず1件ずつ見ます。
3. 秘密を漏らさない — コミットではなく push で止める
置き場所を決めたうえで、機械側の防御を入れます。目的は「コミットしてから気づく」を「push で止まる」へ変えることです。この差は大きく、コミット後に気づいた場合は履歴からの除去とローテーションの両方が必要になりますが、push が止まればローテーションは不要です。
GitHub の secret scanning と push protection は、パブリックリポジトリではライセンス不要・設定不要で無料で有効になっています。検出器は継続的に追加されており、2026年3月には 15 プロバイダから 28 種の検出器が追加されました(Lark、Vercel、Snowflake、Supabase など)。既定で push protection が有効な検出器も拡張され、Airtable、Databricks、Heroku、PostHog、Shopify などを含む 39 検出器が対象になっています。
もうひとつ効くのがパートナープログラムです。検出時に発行元のサービスへ自動通知され、鍵が失効される経路が用意されています。自分が気づく前に鍵が使えなくなるのは不便に見えますが、露出した鍵が有効なまま残るより望ましい挙動です。
プライベートリポジトリで同等の保護を有効にする場合はプランの条件が変わるため、自分のリポジトリで実際に有効かどうかを設定画面で確認してください。「パブリックだから無料で入っている」ことと「自分のプライベートリポジトリでも入っている」ことは別です。
CI 側では、push protection をすり抜けた場合の二段目としてスキャンを置きます。既存の CI/CDガードレール構築 で扱っている構成と同じ場所に入ります。
そして、漏れたときは値を再掲しないという運用ルールを先に決めておきます。Slack へ貼って共有する、Issue に「このキーが漏れました」と書いて全文を載せる、といった対応は露出面を増やすだけです。やることは失効・ローテーション・影響確認の3つで、手順は第12回で扱います。
4. 依存を拾わない — まず検知を常設する
ここから依存の側です。最初に置くのは、既知の脆弱性を持つバージョンを使い続けていることの検知です。
GitHub の Dependabot と依存関係グラフによる SCA は、パブリック・プライベートを問わず全リポジトリで無料で、利用上限もありません。対応エコシステムは npm / pip / Go / Cargo / Maven / Gradle / NuGet / Bundler / Composer / Hex / Docker / GitHub Actions / Terraform に及びます。GitHub Actions のワークフローで参照している action のバージョンも対象に入るのは、見落とされやすい利点です。
2026年4月には、Dependabot と code scanning が GitHub Actions の OIDC トークンで認証できるようになりました。長期の秘密情報を保持せずに済むという点で、この回のもう一方のテーマとも接続します。private registry を使っている場合、長期トークンを Actions secrets へ置いている構成は見直しの候補になります。
ただし Dependabot が拾うのは「公開済みの既知脆弱性」です。この回で扱うインシデントの大半は、公開直後の悪性バージョンが正規のパッケージ名で配布される形なので、脆弱性データベースへ載る前に手元へ届きます。検知は必要条件であって十分条件ではありません。
5. 依存を拾わない — インストールの手前で止める
そこで、インストール時点での多層防御を組みます。npm 系で推奨されている構成は次のとおりです。
| 防御 | 設定 | 何を止めるか |
|---|---|---|
| lifecycle script の無効化 | ignore-scripts=true | インストール時に自動実行される postinstall などの処理 |
| lockfile の強制 | lockfile をコミットし、CI では npm ci | 意図しないバージョンへの解決 |
| 新バージョンの待機期間 | cooldown 期間の設定 | 公開直後に取り下げられる悪性バージョン |
| 公開側の保護 | publish は OIDC + 2FA | 自分が出す側になったときのトークン窃取 |
ignore-scripts には限界があります。全体一律の ON/OFF であり、ビルドに lifecycle script を必要とする正当な依存があると外さざるを得なくなります。Git リポジトリを直接参照する依存に対しては効果が限定的である点も知られています。「入れたから安全」ではなく「1枚目として入れておく」ものとして扱ってください。
cooldown、つまり公開から一定期間が経過したバージョンだけを採用する考え方は、この層で有効性が高い部類です。悪性バージョンは検知され次第取り下げられるため、数日待つだけで多くを回避できます。pnpm では 2026年4月リリースの 11 で minimumReleaseAge、strictDepBuilds、blockExoticSubdeps が既定で有効になりました。パッケージマネージャの選択がそのままセキュリティ設定の初期値になる、という状況が生まれています。
なお、SBOM(SPDX / CycloneDX)による構成管理はこの層の話題ですが、生成ツールと運用方法は本稿では扱いません。導入する場合は別途調べてください。
6. 署名では止まらないもの — 実際に起きたこと
対策として provenance(来歴証明)へ期待したくなるところですが、ここは正確に理解しておく必要があります。
TanStack のパッケージが汚染された事例では、42 のパッケージに対し 6 分未満で 84 の悪性バージョンが公開され、それらは SLSA Build Level 3 の Sigstore provenance を正当に保持していました。攻撃者が署名を偽造したのではなく、正規の公開経路が乗っ取られたため、生成された provenance も正規のものになっています。
つまり provenance が証明するのは「どのソースから、どのビルド環境で出たか」であって、「中身が安全か」ではありません。攻撃者がメンテナの資格情報を握った時点で、正規の経路から正規の署名付きで悪性バージョンが出ます。署名の検証は必要ですが、それだけを根拠に自動更新を許可する設計は成り立ちません。
同種の事例は継続しています。
| 時期 | 事例 | 規模 |
|---|---|---|
| 2025年9月〜 | Shai-Hulud | npm エコシステム初の自己複製型ワーム。メンテナのトークンを窃取して他パッケージへ汚染バージョンを配布。796 パッケージ、月間 1.32 億ダウンロード規模に影響したと報告 |
| 2026年3月 | Axios の侵害 | トップ 10 に入るパッケージが対象 |
| 2026年5月 | Mini Shai-Hulud | npm 170 以上のパッケージ、PyPI 2 パッケージ、404 の悪性バージョンに拡大したと報告 |
| 2026年6〜7月 | npm / PyPI で追加4件 | — |
これらの事例で注目したいのは規模ではなく、攻撃の目的です。報告されているのは、クラウドの鍵、SSH 鍵、Kubernetes の秘密情報、環境変数の窃取です。ワームが自己複製できたのも、開発マシンにメンテナの公開用トークンが置かれていたからです。
ここから出る結論は、依存の話でありながら第2回・第3回と同じ場所へ着地します。開発マシンに何を置いているか、そのプロセスがどこまで読めるかが、被害の大きさを決めます。 インストール時に任意のコードが動く前提に立つなら、エージェントの権限設計と同じ問いになります。ホームディレクトリのクラウド認証情報へ、そのプロセスは到達できるか。到達できるとして、外へ出せるか。
7. AIエージェントに依存を追加させるときのルール
以上を踏まえて、エージェントに実装を任せるときの運用ルールを3点に絞ります。
1. AIが提案したパッケージ名は必ず目視する。 存在しないパッケージ名が提案され、それを見越した名前が先回りして公開されている、という攻撃面が知られています。エージェントは名前の存在を確認せずにインストールを提案することがあるため、初めて見る名前は npm / PyPI の該当ページを開いて、公開元・最終更新・週間ダウンロード数を見ます。1件あたり 30 秒の作業です。
2. 依存追加は差分レビューの対象にする。 追加時に見るのは、install script の有無、保守状況、要求している権限、そして lockfile の差分です。lockfile の差分は、直接追加した1件の裏で何十件が入ってきたかを示します。ここを開かずにマージすると、依存追加の判断をしていないのと同じです。
3. MCP サーバーの追加も依存追加と同じ重さで扱う。 MCP サーバーはローカルで実行されるプロセスであり、多くの場合パッケージとして配布されます。「ツールが増えるだけ」ではなく「実行される第三者のコードが増える」ことです。判断基準は第5回で扱いました。
これを人の意志に任せず仕組み化する場所が Hooks です。パッケージインストールを伴うコマンドの実行前に確認を挟めば、「気づいたら入っていた」という状態を防げます。Hooks の設計は第4回で扱いました。エージェントの権限設定でインストール系コマンドを許可リストから外し、都度承認にする方法も同じ目的に使えます(第2回 / 第3回)。
8. CI に置く最小構成
この回の内容を、リポジトリへ入れるチェック項目の形にまとめます。すべて入れて初めて意味が出るものではなく、上から順に効果が高いものです。
| # | 項目 | 確認方法 |
|---|---|---|
| 1 | 環境変数ファイルが Git 管理外である | git check-ignore -v で無視ルールを確認する |
| 2 | push protection が有効である | リポジトリの設定画面で状態を確認する |
| 3 | 依存の脆弱性検知が有効である | Dependabot alerts が届く状態にする |
| 4 | CI が lockfile どおりにインストールする | インストールコマンドを npm ci に固定する |
| 5 | CI に秘密情報スキャンがある | push protection をすり抜けた場合の二段目 |
| 6 | 公開接頭辞の変数が棚卸しされている | PUBLIC_ / NEXT_PUBLIC_ の一覧を定期的に見る |
| 7 | 依存追加が lockfile 差分ごとレビューされる | PR テンプレートへ確認項目を入れる |
CI ワークフローの具体的な書き方と、他の品質ゲートとの組み合わせ方は CI/CDガードレール構築 にあります。この回で追加したい観点は、セキュリティ系のジョブを品質ジョブと分けておくことです。型チェックの失敗と秘密情報の検出は、対応する人も緊急度も違います。
# 公開接頭辞の変数の棚卸し(値は表示せずキー名だけを見る)
grep -rhoE '(NEXT_)?PUBLIC_[A-Z0-9_]+' src/ | sort -u
値そのものを出力しないことは、こうした確認コマンドでも守ります。キー名だけで棚卸しは成立します。
まとめ
- 秘密情報と依存は防御の方向が逆ですが、AI開発では「人が毎回確認する工程が消える」という同じ原因で同時に悪化します
- 環境変数ファイルはプロジェクトルート直下に置き、Git 管理外であることを確認します。本番の秘密値は実行基盤の Secrets 機能へ置き、クライアントへ公開される接頭辞を秘密値に使いません
- 目標は「コミット後に気づく」ではなく「push で止まる」ことです。パブリックリポジトリでは secret scanning と push protection が設定不要で有効になっています
- Dependabot は既知脆弱性を拾いますが、公開直後の悪性バージョンには届きません。lockfile 強制・
npm ci・cooldown・lifecycle script の抑制を重ねます - provenance は「誰が出したか」を証明しますが「中身が安全か」は証明しません。TanStack の事例では悪性バージョンが正当な SLSA Build Level 3 の provenance を持っていました
- 一連の攻撃の目的は開発マシン上の鍵と環境変数です。依存対策の実効は、エージェントの権限設計と同じ問いに帰着します
参考リンク
- OWASP Top 10:2025 — Software Supply Chain Failures の新設
- About supply chain security(GitHub Docs) — Dependabot と依存関係グラフの対象範囲
- Secret scanning pattern updates, March 2026(GitHub Changelog) — 検出器の追加と push protection の既定有効範囲
- NPM Security Cheat Sheet(OWASP) — インストール時防御の実務指針
- Shai-Hulud 2.0: guidance for detecting, investigating, and defending(Microsoft Security) — 自己複製型ワームの挙動と対処
- TanStack npm supply chain worm(Orca Security) — provenance を保持したまま公開された悪性バージョンの分析