Slide 1

Slide 1 text

開発ワークフローの セキュリティについて考える クラウド統括本部 プロフェッショナルサービス事業本部 コンサルティング1部 モダンアプリコンサルティングチーム 石澤 拓哉

Slide 2

Slide 2 text

自己紹介

Slide 3

Slide 3 text

この講演を作った動機 やパッケージレジストリを経由する侵害が続いている。 個々の対応策は公開されているが、開発ワークフロー全体を通してまとめた資料が少ない。 GitHub Actions tjactions/changedfiles 2025 3 ( 月) 年 Nx s1ngularity 2025 8 ( 年 月) ワークフローへのコマンド 注入で npm トークンが盗 まれ、汚染版が端末の資格 Action の全タグが不正コミ ットへ付け替えられ、ラン 情報を AI エージェントに ナーのメモリから読み出さ 探索させた。 れたシークレットが公開ロ グに出た。 ( Shai-Hulud 2025 9 年 月) ( Trivy 2026 月) 年3 のワーム。 trivy-action のほぼ全タグ postinstall で秘密情報を盗 が強制更新され、下流のプ み、盗んだトークンで他の ロジェクトのランナーの資 パッケージへ自己増殖し 格情報が流出した。 た。 npm 3

Slide 4

Slide 4 text

この講演で渡すもの やるべきことを 80 項目に集めた。 開発者の作業順の分類と、全体に通底する原則を添える。 80 の項目 やるべきことの一覧。各項目に概 要と対応を付け、後から自分の環 境と照らして点検できる形にす る。 4 つのフェーズ 書く、取り込む、ビルドする、届 ける。開発者の作業の順に並べ、 各項目の置き場にする。 4 つの原則 フェーズをまたいで繰り返し現れ る考え方。各項目に、どの原則の 実装かを付記する。 4

Slide 5

Slide 5 text

前提 この講演は次のことを前提とする。 作るソフトウェアそのもののセキュリティは扱わない。 内容は特定の CI ツールや実行環境に依らない。 ただし、説明のために具体的な製品名を使う。 例示の CI ツールは GitHub Actions とする。 レジストリは npm、PyPI、Docker Hub、クラウドは AWS を例にする。 対応の実装例として OSS や製品を紹介することがある。 5

Slide 6

Slide 6 text

第 0 章 4 つの原則

Slide 7

Slide 7 text

扱う範囲 開発者が手元でコードを動かすところから、公開と本番デプロイまでを扱う。 扱う 扱わない 開発者が手元で動かすコード 開発者が持っている資格情報 公開と本番デプロイの時点まで 本番に到達した後の運用 端末の管理と社内ネットワーク 7

Slide 8

Slide 8 text

攻撃者の経路 攻撃者の行為は「仕込む」「動かす」「盗む」「遂行する」の 4 段階で進む。 遂行には、次の被害者へコードを仕込むことが含まれる。 コードを仕込む コードを動かす 資格情報を盗む ⽬的を遂⾏する 次の被害者にコードを仕込む 8

Slide 9

Slide 9 text

のサプライチェーン攻撃 Trivy のサプライチェーン攻撃(2026 年 3 月)では、Trivy への 4 段階の遂行が LiteLLM への仕込みになった。 Trivy フォークから 細⼯した PR を送る PR のコードが、本家の 権限で実⾏される コードを仕込む コードを動かす 汚染版の Trivy が LiteLLM の CI に取り込まれる の CI で 汚染版が実⾏される コードを仕込む LiteLLM コードを動かす aqua-bot の PAT 資格情報を盗む PyPI の公開トークン 資格情報を盗む 盗んだ PAT で 汚染版の Trivy を公開 ⽬的を遂⾏する LiteLLM PyPI の汚染版を に公開 ⽬的を遂⾏する 9

Slide 10

Slide 10 text

つの原則 通底する原則は、次の 4 つである。 4 原則 1 2 3 4 出所と内容の検証 実行されるコードの把握 盗まれる前提の資格情報 侵害後の手順の事前設計 内容 依存関係、コードの変更、成果物を、受け入れる前に出所と内容 で確かめる 何が実行されるかを把握し、動かすものを自分で決める 短命、最小範囲、実行主体ごとに作る 被害範囲、検知、失効と復旧の順序を事前に決める 10

Slide 11

Slide 11 text

経路と原則の対応 4 つの原則は、攻撃者の 4 段階にそれぞれ対応する。 コードを仕込む コードを動かす 資格情報を盗む ⽬的を遂⾏する 原則 1 原則 2 原則 3 原則 4 出所と内容の検証 実⾏される コードの把握 盗まれる前提の 資格情報 侵害後の⼿順の 事前設計 11

Slide 12

Slide 12 text

つのフェーズ 開発者の作業を 4 つのフェーズに分け、フェーズごとにやるべきことを列挙する。 4 章 1 2 3 4 フェーズ 書く 取り込む ビルドする 届ける 範囲 リポジトリをクローンしてから、変更を push するまで 変更がリポジトリに届いてから、main ブランチにマージされるまで ワークフローが起動してから、成果物ができるまで 成果物ができてから、リリースするまで 12

Slide 13

Slide 13 text

第 1 章「書く」

Slide 14

Slide 14 text

「書く」の範囲 開発者がリポジトリをクローンし、手元で開発して、push するまでを扱う。 端末 開発者 クローン コード リポジトリ push 14

Slide 15

Slide 15 text

LiteLLM 2026 の汚染版 年 3 月 24 日に PyPI へ公開された汚染版は、開発者の端末で次のように動いた。 取り込み に 1.82.7、10:52 UTC に 1.82.8 が公開され、報 告から約 3 時間で PyPI が削除 した。この間の pip install や 依存の更新で端末に入った 10:39 UTC 実行 収集と送信 は proxy_server の import 時、1.82.8 は wheel 内の .pth で Python の起動時 に動いた(import 不要) 環境変数、SSH 鍵、クラウド と DB の資格情報、各種トーク ン、LLM の API キーなどを集 め、AES-256-CBC と RSA4096 で暗号化して models.litellm[.]cloud へ送信 した 1.82.7 永続化 以外では、systemd のユー ザーユニット (Restart=always)として残 った CI 出典 Datadog Security Labs: https://securitylabs.datadoghq.com/articles/litellm-compromised-pypi-teampcp-supply-chain-campaign/ Trend AI Security: https://www.trendaisecurity.com/en-us/resources-insights/trendai-security-blog/inside-litellm-supply-chain-compromise BerriAI/litellm issue #24518: https://github.com/BerriAI/litellm/issues/24518 15

Slide 16

Slide 16 text

Nx s1ngularity 2025 年 8 月 26 日に npm へ公開された汚染版は、開発者の端末で次のように動いた。 取り込み 月 26〜27 日の約 4 時間、 nx と @nx/* の汚染版が公開さ れていた。VS Code の Nx Console は nx@latest を自動 で入れるので、エディタを開く だけで入った 8 出典 Nx 実行 探索と収集 公開と妨害 が telemetry.js を 実行した。Linux と macOS で のみ動いた 端末の Claude Code、Gemini CLI、Amazon Q を承認なしの フラグで起動し、トークンや SSH 鍵、.env、クラウドの資 格情報を探させた 被害者の GitHub トークンで被 害者自身のアカウントに公開リ ポジトリを作り、集めた情報を 約 8 時間公開した。シェルの 設定ファイルに shutdown を 追記した postinstall のアドバイザリ: https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c Wiz: https://www.wiz.io/blog/s1ngularity-supply-chain-attack 16

Slide 17

Slide 17 text

「書く」の項目 「書く」でやるべきことは、次の 18 項目である。 重⼤度 ⾼ 1 インストール時に⾛るスクリプトを⽌める 2 公開直後のバージョンを採⽤しない猶予期間を置く 3 社内パッケージの取得先をレジストリの設定で固定する 4 依存関係の解決結果をロックファイルで固定する 5 リポジトリを IDE で開いたときの⾃動実⾏を⽌める 6 AI エージェントの承認確認を無効にしない 7 本⼈のアカウントをフィッシング耐性のある MFA で守る 8 ⻑期の資格情報を端末に置かない 9 IDE 拡張を許可した発⾏元のものに限定する 10 依存関係の取り込み経路を承認されたレジストリに限定する 11 MCP サーバーを許可した発⾏元のものに限定する 重⼤度 中 12 秘密情報をコミットする前に⽌める 13 コミットに署名する 14 AI エージェントが読み書きできるファイルを限定する 15 AI エージェントの外向き通信を許可リストに制限する 16 AI エージェントに専⽤の ID を使う 17 ログインセッションを短命にする 18 端末上の資格情報の⼀覧と失効⼿順を持つ 17

Slide 18

Slide 18 text

インストール時に走るスクリプトを止める の postinstall のように、依存を入れただけで任意のコードが動く仕組みを、ワー ムは増殖に使う。 npm install 原則 実行されるコードの把握 侵害例 (2025 年 9 月): postinstall で秘密情報を盗み、他のパッケージへ自己増殖した Shai-Hulud https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack (2025 年 11 月): preinstall で Bun を入れてペイロードを実行した Shai-Hulud 2.0 https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/ (2026 年 3 月): コードから参照されない依存の postinstall でペイロードを実行した Axios https://github.com/axios/axios/issues/10636 18

Slide 19

Slide 19 text

インストール時に走るスクリプトを止める 対応 以降は、依存のインストール時スクリプトが既定で走らない npm approve-scripts で、実行を許す依存を個別に許可する npm 11 以前は .npmrc に ignore-scripts=true を置く リポジトリの .npmrc に置けば CI でも有効になる pnpm 10 以降は既定で止まる pnpm 10.26 以降は allowBuilds で許可する Python でインストール時に動くのは sdist のビルドで、pip の --only-binary :all: か uv の --nobuild で止める npm 12 19

Slide 20

Slide 20 text

公開直後のバージョンを採用しない猶予期間を置く 汚染版は、公開から数時間で取り下げられることが多い。 猶予期間の間に取り下げられた汚染版は、インストールの候補に入らない。 原則 出所と内容の検証 侵害例 (2025 年 9 月): 汚染版は公開から約 2 時間で取り下げられた chalk / debug https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk (2026 年 3 月): 汚染版は公開から約 3 時間で削除された Axios https://github.com/axios/axios/issues/10636 (2026 年 3 月): 1.82.7 と 1.82.8 は公開当日のうちに PyPI から削除された(報告から約 3 時間) LiteLLM https://securitylabs.datadoghq.com/articles/litellm-compromised-pypi-teampcp-supply-chain-campaign/ 20

Slide 21

Slide 21 text

公開直後のバージョンを採用しない猶予期間を置く 対応 以降は .npmrc に min-release-age を日数で置く pnpm 10.16 以降は minimumReleaseAge を分で設定する pnpm 11 は既定で 1440 分になる Bun は bunfig.toml の [install] minimumReleaseAge を秒で設定する uv は exclude-newer に 7 days のような期間を置く 各ファイルのアップロード時刻で判定し、uv pip とプロジェクトの両方で有効になる 急ぎの修正は pnpm の minimumReleaseAgeExclude などで個別に外す npm 11.10 21

Slide 22

Slide 22 text

社内パッケージの取得先をレジストリの設定で固定する 社内パッケージの取得先を名前で固定していないと、同名の公開パッケージを取り込みうる。 攻撃者は、社内のパッケージ名を推測して公開レジストリに登録するだけでよい。 原則 出所と内容の検証 侵害例 (2021 年 2 月): 研究者の Alex Birsan が内部パッケージ名を公開レジストリに登録し、35 社 超の端末やビルド環境でコードを実行した dependency confusion https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610 22

Slide 23

Slide 23 text

社内パッケージの取得先をレジストリの設定で固定する 対応 は .npmrc に @myorg:registry=https://... を置き、スコープごとにレジストリを固定する スコープのない名前は既定のレジストリに向く pip は --extra-index-url を使わない 全ての index を優先順位なしに探し、高いバージョンを選ぶため index は --index-url の 1 つにし、公開パッケージは社内 index を経由して取る uv は既定の first-index により、最初に見つかった index だけからパッケージを取る [[tool.uv.index]] の explicit = true と tool.uv.sources で index を固定する npm 23

Slide 24

Slide 24 text

依存関係の解決結果をロックファイルで固定する バージョン範囲で指定した依存は、解決のたびに違うものへ変わりうる。 ロックファイルは解決したバージョンとハッシュを記録し、次のインストールでは範囲を解決 し直さない。 原則 出所と内容の検証 侵害例 (2018 年 11 月): event-stream 3.3.6 に汚染版の flatmap-stream 0.1.1 が依存として加えられ、バー ジョン範囲で指定していた利用者に入った event-stream https://github.com/dominictarr/event-stream/issues/116 24

Slide 25

Slide 25 text

依存関係の解決結果をロックファイルで固定する 対応 ロックファイル(package-lock.json、pnpm-lock.yaml、uv.lock など)をコミットする pip は requirements にハッシュを付け、--require-hashes でインストールする Dockerfile の FROM はタグではなくダイジェスト(@sha256:...)で指定する Terraform は .terraform.lock.hcl をコミットする 固定されるのはプロバイダだけで、モジュールはバージョン固定か git のコミット SHA で参照する Go は go.sum をコミットする 25

Slide 26

Slide 26 text

リポジトリを IDE で開いたときの自動実行を止める リポジトリ内の設定ファイルに書かれたタスクやフックは、開発者がクローンしてエディタや AI エージェントで開き、信頼した時点で実行される。 原則 実行されるコードの把握 侵害例 (2026 年 6 月): Microsoft の Azure 組織のリポジトリに .claude/settings.json のフックと VS Code のタス クを仕込み、開いた端末でスティーラーを起動させた Miasma https://www.stepsecurity.io/blog/miasma-worm-hits-microsoft-again-azure-functions-action-and-72-otherrepositories-disabled-after-supply-chain-attack-targeting-ai-coding-agents (2026 年 8 月): VS Code のタスクと .claude の設定を使い、GitHub のブランチへも感染を広げた ChainDrop https://securitylabs.datadoghq.com/articles/npm-worm-compromises-popular-npm-packages/ 26

Slide 27

Slide 27 text

リポジトリを IDE で開いたときの自動実行を止める 対応 は Workspace Trust を有効にし、信頼していないリポジトリは制限モードで開く AI エージェントのフォルダを信頼するかの確認を無効にしない 信頼はリポジトリ単位で永続するので、.claude/ などの設定の差分は pull のたびに見る 組織では Claude Code の managed settings(管理者が配布し、利用者が上書きできない設定)に allowManagedHooksOnly: true を置く Claude Code を対話なしで実行するときは --bare か --settings '{"disableAllHooks": true}' でフ ックを止める VS Code 27

Slide 28

Slide 28 text

AI エージェントの承認確認を無効にしない 攻撃者は、AI エージェントが読むリポジトリの中身や Web ページに命令を書き込める。 承認なしモードでは、入力に書かれた命令も、マルウェアが起動した AI エージェントの操作 も、人の確認なしに実行される。 原則 実行されるコードの把握 侵害例 (2025 年 8 月): マルウェアが承認なしのフラグで Claude Code、Gemini CLI、Amazon Q を起動 し、端末の秘密情報を探索させた Nx s1ngularity https://www.wiz.io/blog/s1ngularity-supply-chain-attack 28

Slide 29

Slide 29 text

AI エージェントの承認確認を無効にしない 対応 承認なしで動かすのは、端末から隔離した devcontainer などの専用環境に限る 管理者が配布し、利用者が上書きできない設定で、承認なしモードを無効にする Claude Code は permissions.disableBypassPermissionsMode を "disable" にする MDM の managed-settings.json で配る。Team と Enterprise なら claude.ai コンソールか らも配れる Gemini CLI はシステム設定ファイルで security.disableYoloMode を有効にする 29

Slide 30

Slide 30 text

本人のアカウントをフィッシング耐性のある MFA で守る 開発者本人の GitHub、npm、クラウドのアカウントが乗っ取られると、全フェーズの権限が そのまま攻撃者に渡る。 TOTP(認証アプリのワンタイムコード)は、偽サイトが中継すればそのまま使われる。 原則 盗まれる前提の資格情報 侵害例 (2025 年 9 月): npmjs.help のドメインから 2FA の更新を求めるメールを送り、偽サイトで認証情報を 入力させてメンテナのアカウントを乗っ取った chalk / debug https://www.aikido.dev/blog/npm-debug-and-chalk-packages-compromised 30

Slide 31

Slide 31 text

本人のアカウントをフィッシング耐性のある MFA で守る 対応 と npm でパスキーかセキュリティキーを登録し、ログインにはそれを使う GitHub では SMS を 2FA から外す GitHub はパスキーだけの 2FA にできないので、TOTP は残したままパスキーを主に使う 組織の設定でメンバーの 2FA を必須にし、Only allow secure two-factor methods で SMS を無効 にする GitHub 31

Slide 32

Slide 32 text

長期の資格情報を端末に置かない ~/.aws/credentials や ~/.npmrc のトークン、.env、~/.ssh の秘密鍵は、端末で動くすべてのプロセ スから読める。 2025 年以降の端末を狙った侵害の多くが、これらを集めている。 原則 盗まれる前提の資格情報 侵害例 ( 年 月) エージェントで端末の秘密情報と SSH 鍵を探索し、被害者の GitHub に公開した Nx s1ngularity 2025 8 : AI https://www.wiz.io/blog/s1ngularity-supply-chain-attack ( 年 月) 端末の と のトークンを集め、自己増殖と永続化に使った Shai-Hulud 2025 9 : npm GitHub https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack 社内リポジトリの流出( 件が取得されたとされる 年 月) 汚染された 拡張が社員の端末で資格情報を集め、社内リポジトリ約 GitHub 2026 5 : Nx Console 3800 https://github.blog/security/investigating-unauthorized-access-to-githubs-internal-repositories/ 32

Slide 33

Slide 33 text

長期の資格情報を端末に置かない 対応 は IAM Identity Center の aws sso login で、短命な資格情報を使う npm は granular access token で範囲と期限を絞る GitHub は fine-grained PAT で期限と範囲を絞り、ランナー登録やワークフローの書き込みは許さな い SSH 鍵は FIDO 鍵にするか、使うたびに承認を求める agent 経由で使う git の資格情報は OS のキーチェーンの credential helper に保存し、平文ファイルに置かない 長期の鍵が必要なら OS のキーチェーンや 1Password の CLI 連携から渡し、ファイルに置かない AWS 33

Slide 34

Slide 34 text

IDE 拡張を許可した発行元のものに限定する 拡張は、開発者と同じ権限で動くコードであり、誰でも公開でき、自動で更新される。 自動更新は、正規の発行元の拡張が汚染版に更新された場合もそのまま端末に入れる。 IDE 原則 出所と内容の検証 侵害例 (2025 年 10 月): Open VSX の既存の拡張に不可視文字でコードが仕込まれ、盗んだ資格情報で他の拡張 GlassWorm へ広がった https://www.aikido.dev/blog/glassworm-returns-unicode-attack-github-npm-vscode 34

Slide 35

Slide 35 text

IDE 拡張を許可した発行元のものに限定する 対応 組織では、VS Code のポリシーで許可リストと自動更新の停止を配る AllowedExtensions ポリシーで extensions.allowed を配り、発行元か拡張 ID の許可リストに する 一覧にない拡張はインストールできず、後から一覧を外れた拡張は無効になる ExtensionsAutoUpdate ポリシーで extensions.autoUpdate を無効にする 個人では、拡張を棚卸しして数を減らし、発行元を確認する extensions.autoUpdate を false にし、更新は内容を確認して手動で入れる 35

Slide 36

Slide 36 text

依存関係の取り込み経路を承認されたレジストリに限定する プロキシレジストリは、既知の悪性パッケージをダウンロード前に拒否する検査を持てる。 公開レジストリへの直接アクセスを止めると、すべての取り込みがこの検査を通る。 原則 出所と内容の検証 36

Slide 37

Slide 37 text

依存関係の取り込み経路を承認されたレジストリに限定する 対応 取り込みの経路をプロキシレジストリに限る Artifactory、Nexus、Takumi Guard など ネットワークで公開レジストリへの直接アクセスを止め、プロキシレジストリ以外の経路をなくす 個人では .npmrc の registry に Takumi Guard を指定する 37

Slide 38

Slide 38 text

MCP サーバーを許可した発行元のものに限定する ( )サーバーは AI エージェントにツールを渡すプログラムで、手元で動 くものは端末の権限を持つ。 ツールの説明文は命令として読まれるので、発行元を確かめずに入れたサーバーは AI エージェントを 操れる。 MCP Model Context Protocol 原則 出所と内容の検証 侵害例 ( 年 9 月): Postmark 公式を装った非公式の MCP サーバーが、15 バージョンを正常に公開した後、1.0.16 で送信メールをすべて外部へ Bcc した postmark-mcp 2025 https://snyk.io/blog/malicious-mcp-server-on-npm-postmark-mcp-harvests-emails/ の ( 年 月) 信頼していないリモート MCP サーバーへの接続で、authorization_endpoint の URL か コマンドが注入された mcp-remote RCE 2025 7 : OS https://github.com/advisories/GHSA-6xpm-ggf7-wc3p ら 38

Slide 39

Slide 39 text

MCP サーバーを許可した発行元のものに限定する 対応 組織では、Claude Code の managed settings で MCP サーバーの許可リストを配る allowedMcpServers と allowManagedMcpServersOnly: true を組にして、利用者が自分の設 定で広げられなくする deniedMcpServers で既知の不正なサーバーを止める 配布は MDM のほか、claude.ai の管理コンソールからもできる 個人では、発行元を確認したサーバーだけを設定し、リモートのサーバーは接続前に確認する ツール定義のハッシュを記録し、更新での差分を検知する AI エージェントのスキルとプラグインも同じ対象にする 39

Slide 40

Slide 40 text

秘密情報をコミットする前に止める 一度 push した秘密情報は履歴に残り、失効させるしかない。 端末側で止めるのが最も低コストである。 原則 盗まれる前提の資格情報 40

Slide 41

Slide 41 text

秘密情報をコミットする前に止める 対応 .pre-commit-config.yaml に gitleaks のフックを置き、各自が pre-commit install で有効にする 41

Slide 42

Slide 42 text

pre-commit は、git のフックを .pre-commit-config.yaml で宣言し、各自の端末に同じフッ クを入れる管理ツールである。 フックはコミットのたびにステージした内容に対して動き、失敗するとコミットは作られな い。 pre-commit 42

Slide 43

Slide 43 text

コミットに署名する なりすましコミットは、他人の名前とメールを入れるだけで作れる。 署名があれば、本人のコミットと区別できる。 原則 出所と内容の検証 侵害例 のサプライチェーン攻撃(2026 年 3 月): メンテナの名前と日時を写したなりすましコミットが作られ、trivyaction のタグがそこへ向けられた Trivy https://snyk.io/articles/trivy-github-actions-supply-chain-compromise/ 43

Slide 44

Slide 44 text

コミットに署名する 対応 で gpg.format=ssh、user.signingkey、commit.gpgsign=true を設定し、GitHub に 署名鍵として登録する GitHub の vigilant mode を有効にし、自分名義の未署名コミットに Unverified を表示させる git config 44

Slide 45

Slide 45 text

AI エージェントが読み書きできるファイルを限定する エージェントが読むファイル、Web ページ、MCP の応答は、すべて命令になりうる。 読み書きできるファイルを絞れば、命令を注入されても資格情報は読めず、次回の起動を変える 設定も書けない。 AI 原則 実行されるコードの把握 侵害例 (2025 年 4 月): Invariant Labs が、ツール説明文に隠した命令で AI エージェントに ~/.ssh/id_rsa を 読ませ、ツールの引数として送らせることを実証した tool poisoning https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks 45

Slide 46

Slide 46 text

AI エージェントが読み書きできるファイルを限定する 対応 か Anthropic Sandbox Runtime(srt)などのサンドボックスで動かす 資格情報ファイル(~/.aws、~/.ssh、.env)を読ませない devcontainer は端末からマウントしない srt は denyRead で拒否する Claude Code は sandbox.credentials で拒否する 設定ファイル(.git/hooks、.mcp.json、.claude/)への書き込みは、srt と Claude Code が既定で 常に拒否する devcontainer 46

Slide 47

Slide 47 text

AI エージェントの外向き通信を許可リストに制限する 命令を注入された AI エージェントは、読み取った秘密情報を外向き通信で持ち出す。 通信先を許可リストに限れば、宣言していない宛先へは直接送れなくなる。 原則 実行されるコードの把握 47

Slide 48

Slide 48 text

AI エージェントの外向き通信を許可リストに制限する 対応 サンドボックスで外向き通信を許可リストにする devcontainer は iptables で許可した宛先以外を遮断する srt は allowedDomains 以外を既定で拒否する Claude Code は sandbox.network.allowedDomains と strictAllowlist: true で未列挙の宛先 を遮断する 48

Slide 49

Slide 49 text

AI エージェントに専用の ID を使う エージェントは、既定で開発者の権限を継承する。 専用の ID(identity)と短命なトークンを渡せば、命令を注入されても被害はその ID の権限の 範囲に留まる。 AI 原則 盗まれる前提の資格情報 侵害例 のプロンプトインジェクション(2025 年 5 月): 公開 Issue の命令で、AI エージェントが利用者のトーク ンで非公開リポジトリを読み、公開 PR に書き出した GitHub MCP https://invariantlabs.ai/blog/mcp-github-vulnerability (2026 年 2 月): Codespaces の Copilot が Issue の HTML コメントの命令を読み、GITHUB_TOKEN がジョブに自動で渡すトークン)を漏らした RoguePilot Actions ( https://thehackernews.com/2026/02/roguepilot-flaw-in-github-codespaces.html 49

Slide 50

Slide 50 text

AI エージェントに専用の ID を使う 対応 エージェントには本人のトークンを渡さず、専用の fine-grained PAT を期限とリポジトリと権限 を絞って発行する トークンは AI エージェントに渡さず、プロキシの MCP サーバーに持たせる プロキシは専用 ID のトークンで GitHub に接続し、AI エージェントに公開するツールを絞る プロキシは AI エージェントとは別のユーザー、コンテナ、ホストのいずれかで動かし、HTTP で公 開する FastMCP の create_proxy で作れる。上流の GitHub MCP サーバーへ専用 ID のトークンで接続 し、HTTP で公開する AI 50

Slide 51

Slide 51 text

ログインセッションを短命にする を通過した後のセッションも資格情報であり、盗まれれば再認証なしに使われる。 有効期間が長いほど、盗まれたセッションが使える時間も長い。 MFA 原則 盗まれる前提の資格情報 侵害例 (2023 年 1 月): エンジニアの端末のマルウェアが MFA を通過した SSO セッションを盗み、攻撃者がそのセ ッションで顧客の秘密情報を持ち出した CircleCI https://circleci.com/blog/jan-4-2023-incident-report/ (2023 年 10 月): サポートケースに添付された HAR ファイル(ブラウザの通信記録)から、セッショントークン が盗まれた Okta https://sec.okta.com/articles/2023/11/unauthorized-access-oktas-support-case-management-system-rootcause 51

Slide 52

Slide 52 text

ログインセッションを短命にする 対応 ( 、 など)でセッションの有効期間を短くする 管理操作と本番操作に、再認証を要求する 端末に束縛されたセッション(Okta の Device-Bound SSO など)が使えるなら使う IdP Okta Entra ID 52

Slide 53

Slide 53 text

端末上の資格情報の一覧と失効手順を持つ 端末には、クラウド、GitHub、npm、SSH の資格情報が散在する。 一覧がなければ失効に漏れが出て、順序を誤れば端末のデータを失う。 原則 侵害後の手順の事前設計 侵害例 (2026 年 5 月): 常駐プロセスがトークンの失効を検知するとホームディレクトリを消す作りで、除去より先 に失効させると端末のデータを失う TanStack https://tanstack.com/blog/npm-supply-chain-compromise-postmortem (2025 年 11 月): 流出と増殖の両方に失敗すると(トークンの失効後など)、ホームディレクトリを消 Shai-Hulud 2.0 去した https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/ 53

Slide 54

Slide 54 text

端末上の資格情報の一覧と失効手順を持つ 対応 、 、 、 、 、 、 ごとに、失効の手順と を書いておく 失効の順序を決めておく 端末をネットワークから切り離し、常駐プロセスを除去する 失効と再発行は侵害された端末では行わず、信頼できる別の端末から各サービスの管理画面で行う ~/.aws ~/.npmrc ~/.config/gh ~/.ssh ~/.docker ~/.kube .env URL 54

Slide 55

Slide 55 text

第 2 章「取り込む」

Slide 56

Slide 56 text

「取り込む」の範囲 開発者の push した変更が、PR、レビュー、マージを経て main ブランチに入るまで を扱う。 リポジトリ push PR レビュー 承認 マージ main 開発者 56

Slide 57

Slide 57 text

のサプライチェーン攻撃 2026 年 3 月 19 日、Trivy のリポジトリには、レビューを経ずに次のようにコードが仕 込まれた。 Trivy 残存 なりすまし タグ push 月 1 日の復旧で資格情報を一 斉に失効させなかったため、 aquasecurity/trivy に push できるアクセスが残った メンテナの名前と過去の日付を 写した未署名のコミットで、リ リースワークフローの actions/checkout の参照を書 き換えた そのコミットはどのブランチに も属さず、17:43 UTC に v0.69.4 のタグだけが push さ れてリリースワークフローが起 動した 3 出典 GitHub 公開 、書き換えられたワ ークフローで作られた汚染版 v0.69.4 が公開された 18:22 UTC アドバイザリ: https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 Wiz: https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack 57

Slide 58

Slide 58 text

「取り込む」の項目 「取り込む」でやるべきことは、次の 21 項目である。 重⼤度 ⾼ 19 依存関係の更新 bot に猶予期間を置く 20 秘密情報の push をリポジトリ側で拒否する 21 main ブランチへの変更にレビューを必須にする 22 古いブランチに残ったワークフローを削除する 23 タグとリリースを書き換えられなくする 24 履歴に残った秘密情報を失効させる 25 CI を起動できる主体と条件を限定する 重⼤度 中 26 依存関係を継続的に更新する 27 ⾃動実⾏される設定ファイルの変更に所有者の承認を必須に 28 バイナリのコミットを禁⽌する 29 コミット署名を必須にする 30 依存関係の健全性を継続的に監視する 31 組織の基本権限を最⼩にする 32 連携 App の権限を最⼩にする 33 bot の承認をマージ要件に数える範囲を限定する 34 リポジトリを組織の外へ出せる操作を制限する 35 隠し⽂字を lint で検出する 36 AI エージェントが⽣成した PR の受け⼊れ条件を決める 37 ⼈と bot と App の権限を定期的に棚卸しする 38 監査ログのイベントに通知を付ける 39 リポジトリと連携先の失効⼿順を持つ 58

Slide 59

Slide 59 text

依存関係の更新 bot に猶予期間を置く 依存関係を自動で更新する bot を使っている場合、公開直後のバージョンがそのまま PR で提 案される。 猶予期間を設定すれば、公開から数時間で取り下げられる汚染版を自動で避けられる。 原則 出所と内容の検証 侵害例 (2025 年 9 月): 汚染版は公開から約 2 時間で取り下げられた chalk / debug https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk (2026 年 3 月): 汚染版は公開から約 3 時間で削除された Axios https://github.com/axios/axios/issues/10636 59

Slide 60

Slide 60 text

依存関係の更新 bot に猶予期間を置く 対応 は renovate.json に minimumReleaseAge: "3 days" を置く 脆弱性修正の PR(vulnerabilityAlerts、GitHub のみ)は既定で猶予期間の対象外になる Dependabot は dependabot.yml の cooldown に default-days を置く cooldown は security updates には適用されず、脆弱性の修正は自動で例外になる Renovate 60

Slide 61

Slide 61 text

Dependabot は GitHub に組み込まれた依存関係の更新 bot で、dependabot.yml に書いた 対象を定期的に調べ、新しいバージョンへの PR を作る。 cooldown は、公開から指定した日数が経ったバージョンだけを候補にする設定である。 Dependabot 61

Slide 62

Slide 62 text

秘密情報の push をリポジトリ側で拒否する のフックは、設定していない端末や --no-verify の指定で迂回される。 リポジトリ側で受け取りを拒否すれば、検出できた秘密情報は履歴に入らない。 pre-commit 原則 盗まれる前提の資格情報 62

Slide 63

Slide 63 text

秘密情報の push をリポジトリ側で拒否する 対応 と push protection を有効にする 組織の設定で全リポジトリに一括で有効にする 非公開リポジトリでは GitHub Secret Protection のライセンスが要る delegated bypass でバイパスできるロールとチームを限定する(Who can bypass push protection for secret scanning を Specific roles or teams にする) 既定では、書き込み権限を持つ全員がバイパスできる secret scanning 63

Slide 64

Slide 64 text

main ブランチへの変更にレビューを必須にする レビューが必須でないと、乗っ取られた正規アカウントや内部者が単独で main ブランチを変 えられる。 直接 push、強制 push、レビューなしのマージが対象になる。 原則 出所と内容の検証 侵害例 (2026 年 6 月): 侵害された貢献者のアカウントで、[skip ci] と日時の偽装を付けて直接 push された Miasma https://www.stepsecurity.io/blog/miasma-worm-hits-microsoft-again-azure-functions-action-and-72-otherrepositories-disabled-after-supply-chain-attack-targeting-ai-coding-agents 64

Slide 65

Slide 65 text

main 対応 ブランチへの変更にレビューを必須にする で PR を必須にし、承認を 1 名以上にする 規模に応じて承認者数を増やす rulesets 「Dismiss stale pull request approvals when new commits are pushed」を有効にする rulesets の Bypass list を空にし、Repository admin も入れない 強制 push とブランチの削除を禁止する rulesets は組織で定義して全リポジトリに継承させる 必須ステータスチェックをマージ要件に含める 65

Slide 66

Slide 66 text

古いブランチに残ったワークフローを削除する ワークフローはブランチごとにファイルとして残るので、main ブランチで直しても、古いブラ ンチのファイルはそのブランチを対象にした push や PR で動く。 直したはずの脆弱性は、そのブランチが残る限り使える。 原則 実行されるコードの把握 侵害例 (2025 年 8 月): 仕様変更前の pull_request_target で、修正後も古いブランチに残っていたワークフ ローが悪用され、コード実行と公開トークンの窃取に直結した Nx s1ngularity https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c 66

Slide 67

Slide 67 text

古いブランチに残ったワークフローを削除する 対応 使われていないブランチを削除する 危険なワークフローを直したら、全ブランチに残っていないか確認する 67

Slide 68

Slide 68 text

タグとリリースを書き換えられなくする 利用者はタグを信じて取り込む。 タグを付け替えれば、main ブランチを触らずに全利用者へ汚染版を届けられる。 原則 出所と内容の検証 侵害例 Trivy のサプライチェーン攻撃(2026 年 3 月): trivy-action の 77 タグ中 76 タグが強制更新された https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 (2025 年 3 月): 全タグが不正コミット 0e58ed8 に付け替えられた tj-actions/changed-files https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised 系(2026 年 5 月): issues-helper の全タグが付け替えられ、9 月の再有効化後もタグは不正コミットを 指したままだった actions-cool https://thehackernews.com/2026/09/compromised-github-actions-came-back.html 68

Slide 69

Slide 69 text

タグとリリースを書き換えられなくする 対応 でタグの更新と削除を禁止する immutable releases を有効にし、公開後のリリース資産と、そのリリースが存在する間のタグを固 定する rulesets 69

Slide 70

Slide 70 text

履歴に残った秘密情報を失効させる が止めるのは新しい push だけで、履歴にある秘密情報は有効なまま残る。 アーカイブ済みや放置されたリポジトリも対象になる。 push protection 原則 盗まれる前提の資格情報 侵害例 (2022 年 4 月): アーカイブ済みの非公開リポジトリにあった機械アカウントのトークンが起点とな り、数十の組織の非公開リポジトリが取得された Heroku / Travis CI https://www.heroku.com/blog/april-2022-incident-review/ 70

Slide 71

Slide 71 text

履歴に残った秘密情報を失効させる 対応 の既存の検出結果を棚卸しし、失効させる アラートは自動では閉じないので、失効後に閉じる secret scanning 71

Slide 72

Slide 72 text

CI を起動できる主体と条件を限定する と issue_comment は、PR やコメントを送った側の入力を、本家の権限 で動くワークフローに渡す。 pull_request_target 原則 実行されるコードの把握 侵害例 ( 年 月) トークンが盗まれた のワークフローが、フォーク PR のタイトルに注入されたコマンドを実行 Nx s1ngularity 2025 8 : pull_request_target npm https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c し、 ( 年 月) 研究者が誤字修正の で貢献者になり、以後のフォーク を承認なしに動かした PyTorch 2024 1 : PR PR https://johnstawinski.com/2024/01/11/playing-with-fire-how-we-executed-a-critical-supply-chain-attack-on-pytorch/ ( 年 月) 作者名の末尾が なら無条件に信頼する作りで、攻撃者は自作の App で作っ Claude Code GitHub Action 2026 1 : [bot] Issue https://flatt.tech/research/posts/poisoning-claude-code-one-github-issue-to-break-the-supply-chain/ た から起動できた 72

Slide 73

Slide 73 text

CI 対応 を起動できる主体と条件を限定する の で、フォーク PR の実行承認を「Require approval for all external 」にする フォーク PR の処理に pull_request_target と issue_comment を使わない pull_request でビルドし、workflow_run で特権処理する二段構成にする workflow_run で受け取る成果物はデータとして扱い、実行しない bot のコメントやラベルをトリガーにしない AI エージェントを起動できる主体は書き込み権限を持つ人に限り、bot は名前の完全一致で判定する Settings Actions contributors 73

Slide 74

Slide 74 text

依存関係を継続的に更新する 更新を止めると差分が溜まり、脆弱性の修正版を取り込むときに大きな変更を強いられる。 定期的に更新していれば、問題が判明したとき、該当する依存だけを小さな差分で差し替えら れる。 原則 侵害後の手順の事前設計 74

Slide 75

Slide 75 text

依存関係を継続的に更新する 対応 の version updates を有効にする リポジトリの .github/dependabot.yml を置く。Settings の Advanced Security から生成もで きる 組織の設定で有効にできるのは Dependabot alerts と security updates で、version updates はリポジトリごとの dependabot.yml で有効にする Renovate を使う場合は、GitHub App をインストールして renovate.json を置く Dependabot 75

Slide 76

Slide 76 text

自動実行される設定ファイルの変更に所有者の承認を必須にする や .claude/ などを書き換えられる人は、CI の権限と、リポジトリを開く全員の 端末での実行を得る。 通常のレビューでは、これらのパスの変更も所有者以外の承認で main ブランチに入る。 .github/workflows/ 原則 出所と内容の検証 侵害例 ( 年 月) 種類の設定ファイルが置かれ、開いた全員の端末でスティーラーが起動した Miasma 2026 6 :4 https://www.stepsecurity.io/blog/miasma-worm-hits-microsoft-again-azure-functions-action-and-72-other-repositoriesdisabled-after-supply-chain-attack-targeting-ai-coding-agents ( 年 月) 追加されたワークフローがシークレットを外部へ送信した GhostAction 2025 9 : https://blog.gitguardian.com/ghostaction-campaign-3-325-secrets-stolen/ の 侵害( 年 月) 侵害されたアカウントで、攻撃者が 月までにワークフローを設定した Salesloft GitHub 2025 3 : 6 https://thehackernews.com/2025/09/github-account-compromise-led-to.html 76

Slide 77

Slide 77 text

自動実行される設定ファイルの変更に所有者の承認を必須にする 対応 で .github/workflows/ などのパスに所有者を置く rulesets の Require review from Code Owners を有効にする CODEOWNERS 自身と renovate.json と dependabot.yml も対象に含める CODEOWNERS 77

Slide 78

Slide 78 text

バイナリのコミットを禁止する バイナリは差分で中身を読めないので、レビューを通っても内容は確認されていない。 原則 出所と内容の検証 侵害例 (2024 年 3 月): テスト用のバイナリとして git に置かれたファイルが、バックドアのペイロードだった xz https://www.openwall.com/lists/oss-security/2024/03/29/4 78

Slide 79

Slide 79 text

バイナリのコミットを禁止する 対応 の push rules で、バイナリのパスと拡張子を制限する push rules が使えないリポジトリでは、バイナリの検出を CI の必須チェックにする rulesets 79

Slide 80

Slide 80 text

コミット署名を必須にする 付与した署名をリポジトリ側で必須にすれば、手元で作った未署名のなりすましコミットは main ブランチに入らない。 原則 出所と内容の検証 侵害例 のサプライチェーン攻撃(2026 年 3 月): メンテナを装った未署名のコミットに v0.69.4 のタグが push され、リ リースワークフローが起動した Trivy https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 80

Slide 81

Slide 81 text

コミット署名を必須にする 対応 で署名付きコミットを必須にする タグの rulesets にも適用し、未署名のコミットへのタグ付けを止める rulesets 81

Slide 82

Slide 82 text

依存関係の健全性を継続的に監視する 依存先の保守体制とワークフローは、採用した後に変わる。 変化の通知を受ける仕組みがなければ、弱くなった依存先が自分への入口になる。 原則 出所と内容の検証 侵害例 (2018 年 11 月): 保守が止まった依存の引き継ぎ先が攻撃者だった event-stream https://github.com/dominictarr/event-stream/issues/116 (2024 年 3 月): 新しいメンテナが加わり、年単位で権限を広げた xz https://research.swtch.com/xz-timeline 82

Slide 83

Slide 83 text

依存関係の健全性を継続的に監視する 対応 のスコアと個別のチェックを、API と deps.dev で追う Maintained、Branch-Protection、Signed-Releases、Dangerous-Workflow の低下を監視す る Socket の Install scripts と New author の alert で、インストール時スクリプトの追加や公開元の 変更を受け取る OpenSSF Scorecard 83

Slide 84

Slide 84 text

OpenSSF Scorecard は、リポジトリの保守状況やブランチ保護などの項目を自動で検査し、 各項目を 0〜10 で採点するツールである。 公開 API と deps.dev でスコアを追え、依存先の健全性の変化を受け取れる。 OpenSSF Scorecard 84

Slide 85

Slide 85 text

Socket Socket は、パッケージの中身を静的に解析し、危険な挙動を alert として知らせるサービスで ある。 依存の追加や更新の PR に GitHub App がコメントするほか、CLI では socket npm で npm を包んでインストール時に検査する。 alert の例 インストール時に動くスクリプトがある Obfuscated code: 難読化されたコードを含む Known malware: 既知の悪性パッケージである New author: 公開元が変わった Install scripts: 85

Slide 86

Slide 86 text

組織の基本権限を最小にする 組織の基本権限が既定で広いと、1 人のメンバーの侵害が全リポジトリに届く。 管理者の権限を持つアカウントが乗っ取られると、ブランチ保護そのものを外せる。 原則 盗まれる前提の資格情報 86

Slide 87

Slide 87 text

組織の基本権限を最小にする 対応 組織の基本権限を最小にし、リポジトリごとにロールを付ける オーナーを減らす classic PAT のアクセスを制限し、fine-grained PAT の既定の承認必須を外さない SSO を必須にして、未認可の PAT と SSH 鍵を使えなくする 87

Slide 88

Slide 88 text

連携 App の権限を最小にする と OAuth App は、連携先にトークンを預ける仕組みである。 連携先が侵害されると、預けたトークンが攻撃者に使われる。 GitHub App 原則 盗まれる前提の資格情報 侵害例 (2022 年 4 月): 連携先が預かっていた OAuth トークンで、npm を含む数十の組織の非公開リポジ Heroku / Travis CI トリが読まれた https://github.blog/news-insights/company-news/security-alert-stolen-oauth-user-tokens/ (2025 年 8 月): Drift の連携 App の OAuth トークンで、多数の組織の Salesforce のデータが読み出 Salesloft Drift された https://cloud.google.com/blog/topics/threat-intelligence/data-theft-salesforce-instances-via-salesloft-drift 88

Slide 89

Slide 89 text

連携 App の権限を最小にする 対応 の利用を承認制にする 連携先の GitHub App は、要求する権限を確認して入れるかを決め、アクセス先を Only select repositories に限る 後からの権限の追加要求は、承認しない限り付与されない 使わなくなった App は Suspend か Uninstall する bot には機械ユーザーではなく GitHub App を使い、短時間で失効するインストールトークンを渡す 他の SaaS の連携 App も、同じ基準で絞る OAuth App 89

Slide 90

Slide 90 text

bot の承認をマージ要件に数える範囲を限定する や AI エージェントの承認でマージできると、その bot に命令を注入した攻撃者が単独で main ブランチを変えられる。 自動マージでは、この承認だけで人を介さずにマージが成立する。 bot 原則 出所と内容の検証 90

Slide 91

Slide 91 text

bot の承認をマージ要件に数える範囲を限定する 対応 Copilot code review の承認は既定で無効であり、有効にするなら承認を許すファイルパスを指定す る 組織設定の「Allow GitHub Actions to create and approve pull requests」を無効にする 承認用の GitHub App のトークンで、条件を満たす PR だけを承認するワークフローを置く 作者名が完全一致する パッチ更新である 変更ファイルがロックファイルとマニフェストだけである 必須チェックを通過している 91

Slide 92

Slide 92 text

リポジトリを組織の外へ出せる操作を制限する 可視性の変更、非公開リポジトリのフォーク、リポジトリの移管は、メンバーのトークンが盗 まれたときの持ち出し経路になる。 原則 盗まれる前提の資格情報 侵害例 (2025 年 8 月): 盗んだトークンで非公開リポジトリ 5500 超が公開に切り替えられた Nx s1ngularity https://www.wiz.io/blog/s1ngularity-supply-chain-attack (2025 年 9 月): 非公開リポジトリが -migration の名前で公開された Shai-Hulud https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack 92

Slide 93

Slide 93 text

リポジトリを組織の外へ出せる操作を制限する 対応 組織の設定で、次の操作を制限する 可視性の変更、リポジトリの削除と移管をオーナーに限る 公開リポジトリの作成を制限する 非公開リポジトリのフォークを禁止する 93

Slide 94

Slide 94 text

隠し文字を lint で検出する 不可視文字と双方向の制御文字は、画面の表示と実際の内容を食い違わせ、人のレビューをす り抜ける。 原則 出所と内容の検証 侵害例 (2025 年 10 月): コミットに不可視文字でコードを埋め込んだ GlassWorm https://www.aikido.dev/blog/glassworm-returns-unicode-attack-github-npm-vscode (2025 年 3 月): 不可視文字で AI エージェントのルールファイルに命令を埋め込んだ Rules File Backdoor https://www.pillar.security/blog/new-vulnerability-in-github-copilot-and-cursor-how-hackers-can-weaponizecode-agents ( 年 月): 双方向の制御文字で、ソースの見た目と意味を変えた Trojan Source 2021 11 https://trojansource.codes/ 94

Slide 95

Slide 95 text

隠し文字を lint で検出する 対応 で制御文字とゼロ幅文字をエラーにし、必須チェックに含める 警告が出た PR は、VS Code などで隠し文字を表示して確認する lint 95

Slide 96

Slide 96 text

AI エージェントが生成した PR の受け入れ条件を決める エージェントの生成する PR が増えると、人がすべてを同じ深さでレビューできなくなる。 レビューが浅くなった分、依存の追加やテストの削除のような変更が見落とされる。 AI 原則 出所と内容の検証 96

Slide 97

Slide 97 text

AI エージェントが生成した PR の受け入れ条件を決める 対応 の大きさに上限を置く 変更理由の記載と、生成物であることの明示を求める AI エージェントが追加した依存は、人が承認する テストを削除か変更した PR は、人が承認する PR 97

Slide 98

Slide 98 text

人と bot と App の権限を定期的に棚卸しする 使われなくなったアカウントと連携は、削除しない限り権限を持ったまま増えていく。 持ち主が使っていないので、盗まれて使われても気付かれにくい。 原則 盗まれる前提の資格情報 侵害例 の GitHub 侵害(2025 年 3 月): 侵害されたアカウントで追加されたゲストユーザーが 6 月まで残り、ワーク フローの設定に使われた Salesloft https://thehackernews.com/2025/09/github-account-compromise-led-to.html 98

Slide 99

Slide 99 text

人と bot と App の権限を定期的に棚卸しする 対応 権限の一覧を定期的に確認し、未使用の連携と放置されたアカウントを外す IdP と SCIM(IdP からのアカウントの自動発行と削除)で、アカウントの発行と削除を連動させる 公開レジストリの maintainer と Trusted Publisher(CI の OIDC による公開)、クラウドのデプロ イロールの信頼ポリシーも対象にする 99

Slide 100

Slide 100 text

監査ログのイベントに通知を付ける 攻撃者は侵害の初期に、コラボレータや App、Deploy key、Webhook を追加して足場を作 る。 これらの操作は、組織の監査ログにイベントとして記録される。 原則 侵害後の手順の事前設計 侵害例 Salesloft になった の GitHub 侵害(2025 年 3 月): 侵害されたアカウントでゲストユーザーが追加され、6 月までの活動の足場 https://thehackernews.com/2025/09/github-account-compromise-led-to.html ( 年 月) セルフホストランナーの登録で永続化した Shai-Hulud 2.0 2025 11 : https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/ 100

Slide 101

Slide 101 text

監査ログのイベントに通知を付ける 対応 監査ログのストリーミングと API を有効にする ストリーミングは Enterprise の設定、API は Enterprise Cloud の組織で使える どちらもなければ、UI の手動エクスポートか組織 Webhook(member、repository、 workflow_run など)で取り出す 次のイベントも通知の対象にする リポジトリの作成と可視性の変更 セルフホストランナーの登録 ruleset と environment 保護ルールの変更 シークレットの変更と fine-grained PAT のアクセス要求 101

Slide 102

Slide 102 text

リポジトリと連携先の失効手順を持つ リポジトリにはトークン、App、Deploy key、Webhook と連携先が紐づく。 失効に漏れや誤りがあると、攻撃者はアクセスを保つ。 原則 侵害後の手順の事前設計 侵害例 Trivy った のサプライチェーン攻撃(2026 年 3 月): 2 月の侵害の後に資格情報を一斉に失効させず、攻撃者がアクセスを保 https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 ( 年 月) 失効させるトークンを誤り、露出したトークンが残って不正な公開に使われた Cline 2026 2 : https://cline.bot/blog/post-mortem-unauthorized-cline-cli-npm 102

Slide 103

Slide 103 text

リポジトリと連携先の失効手順を持つ 対応 、 、 、 、 のシークレット、OIDC の信頼ポリシーの一覧と、失 効の順序を書いておく 失効は一斉に行い、入れ替えの途中の資格情報を残さない 連携先が侵害されたときの一括失効の手順を含める PAT App Deploy key Webhook Actions 103

Slide 104

Slide 104 text

第 3 章「ビルドする」

Slide 105

Slide 105 text

「ビルドする」の範囲 main ブランチへの変更や PR でワークフローが起動し、ランナーの上でコードをビ ルドして成果物ができるまでを扱う。 ランナー main の変更や PR 起動 コード ビルド 成果物 依存関係、Action、シークレット 105

Slide 106

Slide 106 text

のサプライチェーン攻撃 2026 年 3 月 19 日以降、trivy-action をタグで参照していた CI では、次のように進ん だ。 Trivy 参照 実行 探索 流出 ワークフローの uses: はタグを 指していた。17:43 UTC 以 降、77 タグ中 76 タグが強制 更新され、汚染版のコミットを 指していた 汚染版の entrypoint.sh は、ス キャンの前にスティーラーを実 行した 環境変数と、ランナーのプロセ スのメモリから秘密情報を探し た。sudo で ptrace の制限を 回避した 暗号化して Aqua を装ったタイ ポスクワットのドメインへ送信 した。失敗すると、被害者の GitHub に公開リポジトリを作 って置いた 出典 GitHub アドバイザリ: https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 Socket: https://socket.dev/blog/trivy-under-attack-again-github-actions-compromise Wiz: https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack 106

Slide 107

Slide 107 text

「ビルドする」の項目 「ビルドする」でやるべきことは、次の 28 項目である。 重⼤度 ⾼ 重⼤度 中 40 CI が参照する外部コードをコミット SHA で固定する 53 ランナーを使い捨てにする 41 CI が呼び出せる外部コードを組織の許可リストに限定する 54 依存関係の来歴を検証する 42 取得したスクリプトとバイナリの完全性を検証する 55 ソフトウェア構成分析( SCA)を必須チェックにする 43 CI 上の AI エージェントの権限を絞る 56 CI でロックファイルどおりに解決させる 44 クラウドの資格情報を OIDC で短命に取る 57 シークレットを渡す範囲を使う処理に絞る 45 ⻑期のシークレットを CI から消す 58 ランナーの外向き通信を許可リストに制限する 46 信頼していないコードを動かすジョブにシークレットを渡さない 59 他のジョブから受け取った成果物のダイジェストを照合 47 アーティファクトに秘密情報を含めない 60 git のタグからビルドする 48 コンテナイメージのビルドに秘密情報を残さない 61 来歴を⽣成する 49 CI ジョブのリポジトリへの権限を最⼩にする 62 ランナーの外向き通信を記録する 50 イベントの⼊⼒をコマンドに直接展開しない 63 SBOM を⽣成する 51 キャッシュを信頼していない実⾏と共有しない 64 ランナー上のステップを⾮特権で動かす 52 CI でも依存関係を承認された経路から取る 65 再現可能ビルドにする 66 ワークフローの実⾏記録を保持する 重⼤度 低 67 ログにシークレットを出さない 107

Slide 108

Slide 108 text

CI が参照する外部コードをコミット SHA で固定する が参照し、実行する外部コードの多くは、Git タグで指定される。 タグは可変であり、付け替えられるとコードを変更していないのに汚染版が届く。 CI 原則 出所と内容の検証 侵害例 Trivy た のサプライチェーン攻撃(2026 年 3 月): trivy-action の 77 タグ中 76 タグが汚染版のコミットへ強制更新され https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 (2025 年 3 月): tj-actions がタグで参照していた reviewdog の Action が先に付け替えら れ、そこから全タグの付け替えに連鎖したと分析されている tj-actions/changed-files https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/ 108

Slide 109

Slide 109 text

CI が参照する外部コードをコミット SHA で固定する 対応 を完全なコミット SHA で参照し、バージョンをコメントで残す OSS の pinact で、タグの参照をコミット SHA とバージョンのコメントに変換する dependabot.yml に package-ecosystem: github-actions を置き、SHA とコメントのバージョ ンを更新させる リポジトリの Actions の設定で「Require actions to be pinned to a full-length commit SHA」 を有効にし、固定していない Action を動かさない zizmor の impostor-commit で、参照先が本家に属さないコミットを検出する 依存先のリポジトリは zizmor owner/repo の形で個別に検査する uses: 109

Slide 110

Slide 110 text

zizmor は、GitHub Actions のワークフローと Action の定義を静的に解析し、危険な書き方を 検査するツールである。 リポジトリのパスか owner/repo を渡して実行し、該当箇所を検査項目(audit)の名前と重大 度とともに指摘する。 zizmor 検査項目(audit)の例 参照先のコミットが、そのリポジトリ自体にはない unpinned-uses: uses: の参照がコミット SHA で固定されていない template-injection: テンプレート展開によるコード注入の経路がある dangerous-triggers: pull_request_target のような危険なトリガーを使っている artipacked: git の資格情報がアーティファクトに残りうる impostor-commit: 110

Slide 111

Slide 111 text

CI が呼び出せる外部コードを組織の許可リストに限定する 組織の Actions ポリシーは、配下の全リポジトリが uses: で呼べる Action を制限し、SHA で の固定を必須にできる。 原則 出所と内容の検証 111

Slide 112

Slide 112 text

CI が呼び出せる外部コードを組織の許可リストに限定する 対応 組織の Actions ポリシーで、GitHub 製と Marketplace の検証済み作者の Action、指定した Action だけを許可する 「Require actions to be pinned to a full-length commit SHA」を有効にする 汚染が判明した Action は ! 接頭辞でブロックする 112

Slide 113

Slide 113 text

取得したスクリプトとバイナリの完全性を検証する で取得したスクリプトや、ダウンロードした CLI のバイナリを検証せずに実行する と、配布元の改ざんがそのまま CI 内のコード実行になる。 curl | bash 原則 出所と内容の検証 侵害例 (2021 年 4 月): 配布元の Bash Uploader が 2 か月にわたり改ざんされ、CI の環境変数が外部へ送信された Codecov https://about.codecov.io/security-update/ 113

Slide 114

Slide 114 text

取得したスクリプトとバイナリの完全性を検証する 対応 バージョンを固定し、実行前に検証する チェックサムは、公開元のリリースページなど別の経路で得た値と sha256sum -c で照合する 署名は、cosign verify-blob で公開元の鍵か ID を検証する 固定したバージョンを社内のアーティファクト置き場にミラーし、CI はそこから取る 114

Slide 115

Slide 115 text

( Sigstore keyless 署名) は、長期の署名鍵を持たずに署名する仕組みで、署名者の OIDC の ID に紐づく短命 な証明書で署名し、記録を公開の透明性ログに残す。 検証は鍵ではなく、証明書の ID と発行者が期待どおりかを見る。 Sigstore cosign verify-blob cosign verify-blob trivy_Linux-64bit.tar.gz \ --bundle trivy_Linux-64bit.tar.gz.sigstore.json \ --certificate-identity https://github.com/myorg/tool/.github/workflows/release.yml@refs/tags/v1.4.0 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com GitHub Actions のワークフローの ID と、GitHub の OIDC 発行者で作られた署名だけを受け入れ る 115

Slide 116

Slide 116 text

CI 上の AI エージェントの権限を絞る で動く AI エージェントは、Issue や PR の本文を入力として受け取り、トークンを持ち、外 部と通信する。 この 3 つが揃うと、本文に書かれた命令でトークンが持ち出される。 CI 原則 盗まれる前提の資格情報 侵害例 (2026 年 1 月): Issue 本文の命令で、OIDC トークンを取得するための環境変数を Issue Claude Code GitHub Action に書き戻させた https://flatt.tech/research/posts/poisoning-claude-code-one-github-issue-to-break-the-supply-chain/ (2026 年 2 月): 任意の Issue を Claude に解析させ Bash を許していたため、Issue タイトルの命令で任意のコ ードが実行でき、キャッシュ汚染を経て公開トークンが盗まれた Cline https://cline.bot/blog/post-mortem-unauthorized-cline-cli-npm 116

Slide 117

Slide 117 text

CI 上の AI エージェントの権限を絞る 対応 エージェントに渡すトークンは permissions: で読み取り専用を既定にする 使えるツールを最小にし、シェルの実行を許さない claude-code-action では allowed_tools でツールを絞る 書き込み先のブランチを制限する 外向き通信の制限では GitHub API 経由の持ち出しを止められないので、トークンの権限で書き込み 先を絞る AI 117

Slide 118

Slide 118 text

クラウドの資格情報を OIDC で短命に取る に置いた長期の鍵は、ランナーで動くすべてのコードから読め、盗まれると失効まで使え る。 OIDC では、クラウドが CI の ID トークンを検証し、そのジョブにだけ有効な資格情報を発行 する。 CI 原則 盗まれる前提の資格情報 118

Slide 119

Slide 119 text

クラウドの資格情報を OIDC で短命に取る 対応 などへは OIDC フェデレーションにし、ジョブに id-token: write を付けてロールを引き受ける OIDC トークンの sub にオーナーとリポジトリの数値 ID を含める 名前を取り直したリポジトリによる偽装を防ぐ ID を含む形式が既定になるのは 2026 年 7 月 15 日以降に作成、改名、移管したリポジトリだけ 既存のリポジトリは設定で有効にし、クラウド側の信頼ポリシーも更新する AWS 119

Slide 120

Slide 120 text

長期のシークレットを CI から消す に置き換えた後も、使われなくなった長期のシークレットが残る。 残っている限り、使う予定のないシークレットも、ランナーで動くすべてのコードから読める。 OIDC 原則 盗まれる前提の資格情報 120

Slide 121

Slide 121 text

長期のシークレットを CI から消す 対応 リポジトリと組織のシークレットを棚卸しし、OIDC に置き換えたものを削除する クロスリポジトリ操作のために残る PAT は、GitHub App のインストールトークンに替える 残すものには期限と用途を記録し、定期的にローテーションする 121

Slide 122

Slide 122 text

信頼していないコードを動かすジョブにシークレットを渡さない テストやビルドのジョブは、信頼していないコードや依存を動かす。 シークレットを使うジョブと分ければ、シークレットを持たないジョブが侵害されてもシーク レットは読めない。 原則 盗まれる前提の資格情報 侵害例 (2025 年 3 月): ビルドジョブのランナーのメモリから、そのジョブが持っていたシークレッ tj-actions/changed-files トがログに出力された https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised のサプライチェーン攻撃( 年 月) 汚染された がランナーのメモリからシークレットを抽出した Trivy 2026 3 : Action https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 122

Slide 123

Slide 123 text

信頼していないコードを動かすジョブにシークレットを渡さない 対応 テストとビルドのジョブでは secrets.* を参照しない 公開とデプロイのシークレットは environment に置き、そのジョブだけが environment: を宣言す る 再利用ワークフローには secrets: inherit を使わず、必要なものだけを渡す pull_request_target では PR 側のコードを checkout しない actions/checkout の既定の拒否を外さない 更新 bot 用のシークレットは Dependabot secrets のように別に持つ 123

Slide 124

Slide 124 text

アーティファクトに秘密情報を含めない アーティファクトは指定したパスをそのまま保存し、リポジトリの読み取り権限があれば取得 できる。 作業ディレクトリごと渡す構成では、actions/checkout が残す GITHUB_TOKEN や .env も成 果物に入る。 原則 盗まれる前提の資格情報 侵害例 ( 年 月) 大手組織の公開リポジトリで再現され、アーティファクトに .git ディレクトリと が含まれていた ArtiPACKED 2024 8 : GITHUB_TOKEN https://unit42.paloaltonetworks.com/github-repo-artifacts-leak-tokens/ 124

Slide 125

Slide 125 text

アーティファクトに秘密情報を含めない 対応 に persist-credentials: false を指定する アーティファクトに .git や環境ファイルを含めない アップロード前に秘密情報をスキャンする 保持期間を短くする actions/checkout 125

Slide 126

Slide 126 text

コンテナイメージのビルドに秘密情報を残さない や ENV で渡したトークンと、COPY された .env はレイヤーに残る。 pull できる全員が docker history やレイヤーの展開で読め、公開イメージなら誰でも読める。 ARG 原則 盗まれる前提の資格情報 侵害例 (2021 年 4 月): Docker イメージの作成手順の誤りで抽出された資格情報が、Bash Uploader の改ざんの起 点になった Codecov https://about.codecov.io/security-update/ 126

Slide 127

Slide 127 text

コンテナイメージのビルドに秘密情報を残さない 対応 の secret mount でビルド中だけ秘密情報を渡す docker history --no-trunc や TruffleHog のイメージスキャンで、レイヤーに秘密情報がないか確認 する BuildKit 127

Slide 128

Slide 128 text

CI ジョブのリポジトリへの権限を最小にする のジョブには、リポジトリを操作するトークンが自動で渡される。 2023 年 2 月より前に作られた組織とリポジトリは、設定を変えていなければ既定が読み書き である。 CI 原則 盗まれる前提の資格情報 侵害例 (2025 年 8 月): 注入されたコマンドが、読み書きできる GITHUB_TOKEN で公開ワークフローを起動 し、npm トークンが盗まれた Nx s1ngularity https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c ( 年 月) セルフホストランナーで動く別ジョブの を取得し、書き込み権限を得た PyTorch 2024 1 : GITHUB_TOKEN https://johnstawinski.com/2024/01/11/playing-with-fire-how-we-executed-a-critical-supply-chain-attack-onpytorch/ 128

Slide 129

Slide 129 text

CI ジョブのリポジトリへの権限を最小にする 対応 組織の設定で GITHUB_TOKEN の既定を読み取り専用にする ワークフローとジョブの permissions: で、必要な権限だけを付ける 129

Slide 130

Slide 130 text

イベントの入力をコマンドに直接展開しない のタイトル、ブランチ名、Issue 本文、コミットメッセージは攻撃者が自由に書ける。 これらをテンプレートでシェルのコマンドに直接展開すると、任意のコマンドが実行される。 PR 原則 実行されるコードの把握 侵害例 (2025 年 8 月): PR タイトルが pull_request_target のワークフローでシェルに展開され、公開トーク Nx s1ngularity ンが盗まれた https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c ( 年 月) ブランチ名の展開からキャッシュ汚染へつながり、汚染版が公開された Ultralytics 2024 12 : https://blog.pypi.org/posts/2024-12-11-ultralytics-attack-analysis/ 130

Slide 131

Slide 131 text

イベントの入力をコマンドに直接展開しない 対応 入力は環境変数に入れてから、シェルで引用符付きの変数として参照する GitHub Actions では ${{ }} を run: に直接書かない actionlint や zizmor でテンプレートインジェクションを検出し、必須チェックに含める 131

Slide 132

Slide 132 text

キャッシュを信頼していない実行と共有しない キャッシュは、前回の実行が置いたものを無検証で復元する。 信頼していないコードが書いたキャッシュを信頼された実行が復元すると、実行内容が差し替 わる。 原則 出所と内容の検証 侵害例 (2026 年 5 月): フォーク PR が汚染した pnpm ストアのキャッシュを release.yml が復元し、OIDC トーク ンが盗まれて、正規の経路で汚染版が公開された TanStack https://tanstack.com/blog/npm-supply-chain-compromise-postmortem ( 年 月) キャッシュ汚染から公開ワークフローへつながり、汚染版が公開された Ultralytics 2024 12 : https://blog.pypi.org/posts/2024-12-11-ultralytics-attack-analysis/ 132

Slide 133

Slide 133 text

キャッシュを信頼していない実行と共有しない 対応 信頼していないコードを既定ブランチの文脈(pull_request_target、issue_comment、issues)で 動かさず、キャッシュを書かせない 公開とデプロイのワークフローではキャッシュを復元しない ツールチェーンはキャッシュから復元せず、取得し直す 133

Slide 134

Slide 134 text

CI でも依存関係を承認された経路から取る 手元で取り込み経路を限定しても、CI が公開レジストリから直接取得すれば、既知の悪性パッ ケージが CI に入る。 公開レジストリから入った既知の悪性パッケージは、ランナーのシークレットに届く。 原則 出所と内容の検証 134

Slide 135

Slide 135 text

CI でも依存関係を承認された経路から取る 対応 の .npmrc や pip の index を、手元と同じプロキシレジストリに向ける コンテナは ECR のプルスルーキャッシュを経由させ、上流への直接の pull は外向き通信の制限で塞 ぐ 公開レジストリへの直接アクセスはランナーの外向き通信で止める CI 135

Slide 136

Slide 136 text

ランナーを使い捨てにする ランナーが残ると、ジョブ間で状態が持ち越される。 前のジョブで仕込まれたものが、次のジョブのシークレットを読む。 原則 実行されるコードの把握 侵害例 (2024 年 1 月): 残存するセルフホストランナーにフォーク PR から入り、別ジョブの GITHUB_TOKEN を得た PyTorch https://johnstawinski.com/2024/01/11/playing-with-fire-how-we-executed-a-critical-supply-chain-attack-onpytorch/ 136

Slide 137

Slide 137 text

ランナーを使い捨てにする 対応 ホストのランナーを使う セルフホストが必要なら JIT(1 ジョブで登録が消える)の使い捨てランナーにし、公開リポジトリで は使わない ランナーグループを非公開リポジトリに限定する GitHub 137

Slide 138

Slide 138 text

依存関係の来歴を検証する 来歴(Provenance)には、パッケージを公開したリポジトリとワークフローが署名付きで記録 される。 普段から来歴付きで公開されているパッケージなら、盗んだトークンで外部から公開されたバ ージョンは来歴の欠落で見分けられる。 原則 出所と内容の検証 侵害例 (2024 年 12 月): パイプラインの外から公開された 8.3.45 と 8.3.46 には来歴(attestation)がなく、欠 落で見分けられた Ultralytics https://blog.pypi.org/posts/2024-12-11-ultralytics-attack-analysis/ 138

Slide 139

Slide 139 text

来歴(Provenance) 来歴は、成果物がどのソースからどのワークフローで作られたかを記した、署名付きの記録で ある(npm では provenance、PyPI と GitHub では attestation)。 検証では署名の有無ではなく、記録されたリポジトリとワークフローが期待どおりかを見る。 139

Slide 140

Slide 140 text

来歴の信頼の連鎖(Trust Chain) 来歴の信頼は、Sigstore のルート CA と GitHub の OIDC 発行者を起点(Trust Anchor)に、 証明書、署名、来歴、成果物へと連鎖する。 検証は成果物からこの連鎖を逆にたどり、起点に届くことと、証明書の ID が期待どおりである ことを見る。 Trust Anchor 信頼の起点 のルート CA と GitHub の OIDC 発⾏者 Sigstore 証明書 ワークフローの ID を 結び付けた短命な証明書 Rekor に記録 署名 証明書に対応する鍵で 来歴に署名し、Rekor に記録 秘密鍵は署名後に廃棄 来歴 リポジトリ、ワークフロー、 ビルダー、ソースのコミット 成果物 の ダイジェストで結び付く sha256 (信頼の連鎖) Trust Chain 検証は成果物から起点へ逆にたどり、途中の証明書の ID が期待どおりかを⾒る 140

Slide 141

Slide 141 text

依存関係の来歴を検証する 対応 以降は trustPolicy: no-downgrade で、来歴の信頼度が前のバージョンより下がった 依存を失敗させる 信頼度は Trusted Publishing(CI の OIDC による公開)、来歴あり、なしの順 npm audit signatures で、レジストリの署名と来歴の正しさを検証する ベースイメージは cosign(Sigstore の署名ツール)の verify-attestation で来歴を検証する pnpm 10.21 141

Slide 142

Slide 142 text

ソフトウェア構成分析(SCA)を必須チェックにする は、依存関係の一覧を作り、既知の脆弱性と既知の悪性パッケージのデータベースと照合 する検査である。 照合はビルドのたびに行うので、依存を追加した PR の時点で既知の問題が分かる。 SCA 原則 出所と内容の検証 142

Slide 143

Slide 143 text

ソフトウェア構成分析(SCA)を必須チェックにする 対応 Dependabot alerts と dependency-review-action、または Socket や OSV-Scanner を CI の必 須チェックにする 新しく追加された依存は、承認するまで失敗させる 143

Slide 144

Slide 144 text

CI でロックファイルどおりに解決させる ロックファイルとマニフェストがずれていると、通常のインストールはロックファイルを書き 換えて解決し直す。 ロックファイルからの逸脱を失敗にすれば、CI で入るものがコミットした解決結果と一致す る。 原則 出所と内容の検証 144

Slide 145

Slide 145 text

CI でロックファイルどおりに解決させる 対応 や pnpm install --frozen-lockfile を使う pip は --require-hashes、Terraform は init -lockfile=readonly を使う イメージはダイジェストで pull する npm ci 145

Slide 146

Slide 146 text

シークレットを渡す範囲を使う処理に絞る ジョブ全体の環境変数に置いたシークレットは、そのジョブで動くすべての処理から読める。 使う処理にだけ渡せば、他の処理からは読めない。 原則 盗まれる前提の資格情報 侵害例 (2021 年 4 月): 改ざんされたスクリプトが env の出力を外部へ送信した Codecov https://about.codecov.io/security-update/ 146

Slide 147

Slide 147 text

シークレットを渡す範囲を使う処理に絞る 対応 シークレットは、それを使う処理の環境変数にだけ置く GitHub Actions では、ワークフローやジョブの env: ではなく、ステップの env: に置く 147

Slide 148

Slide 148 text

ランナーの外向き通信を許可リストに制限する ランナーからの通信先を許可リストに限れば、宣言していない宛先へは直接送れなくなる。 依存の取得を終えたステップは全遮断にでき、ビルドの入力も宣言済みのものに限れる。 原則 実行されるコードの把握 侵害例 (2021 年 4 月): curl で CI の環境変数を外部へ送信した Codecov https://about.codecov.io/security-update/ (2025 年 9 月): curl でシークレットを外部へ送信した GhostAction https://blog.gitguardian.com/ghostaction-campaign-3-325-secrets-stolen/ Trivy のサプライチェーン攻撃(2026 年 3 月): シークレットをタイポスクワットのドメインへ送信した https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack 148

Slide 149

Slide 149 text

ランナーの外向き通信を許可リストに制限する 対応 StepSecurity の Harden-Runner の egress-policy: block と allowed-endpoints で許可リストに する 宛先は audit の記録から作る セルフホストでは、ネットワークポリシーで制限する 149

Slide 150

Slide 150 text

Harden-Runner は StepSecurity の GitHub Actions 向けの Action で、ランナーの外向き通 信、ファイルの改変、プロセスを監視し、宛先の許可リストで通信を止める。 Community(無料)は GitHub ホストのランナー上の公開リポジトリだけで、非公開リポジト リとセルフホストランナーは Enterprise(有料)になる。 Harden-Runner 主な設定 で宛先を記録し、block で許可リスト以外の通信を止める(既定は block) allowed-endpoints: block のときに許可する宛先を列挙する disable-sudo-and-containers: ランナーの sudo とコンテナの利用を外す egress-policy: audit 150

Slide 151

Slide 151 text

他のジョブから受け取った成果物のダイジェストを照合する ジョブ間で渡す成果物(Terraform の plan など)は、途中で差し替えられうる。 生成したジョブが記録したダイジェストで照合しなければ、レビューされていない内容が本番 へ出る。 原則 出所と内容の検証 151

Slide 152

Slide 152 text

他のジョブから受け取った成果物のダイジェストを照合する 対応 生成したジョブがダイジェストをジョブの出力に記録し、受け取るジョブはその値と照合する ダイジェストを成果物と同じ場所へ置くと、差し替えと同時に書き換えられる 成果物に attestation を付け、受け取る側で gh attestation verify で検証する 生成と消費を同一ジョブに限る フォーク PR 由来の成果物は照合の基準も攻撃者が作れるので、コードとして実行せずデータの扱い に留める 152

Slide 153

Slide 153 text

git のタグからビルドする リリース tarball は、git の内容と一致する保証がない。 CI が git のタグから直接ビルドすれば、リリース tarball にだけある内容は成果物に入らな い。 原則 出所と内容の検証 侵害例 (2024 年 3 月): git にない内容がリリース tarball に混入し、バックドアの起動に使われた xz https://www.openwall.com/lists/oss-security/2024/03/29/4 153

Slide 154

Slide 154 text

git のタグからビルドする 対応 上流のソースを取り込むときは、リリース tarball ではなく git のタグをチェックアウトしてビルドす る 配布する tarball は CI が Git タグから生成し、手元で作ったものを配布しない 154

Slide 155

Slide 155 text

来歴を生成する 来歴は、どのソースからどのワークフローで作ったかの記録に署名したもので、成果物のダイ ジェストに結び付く。 利用者とデプロイ先は、これを機械で検証して受け入れを決められる。 原則 出所と内容の検証 155

Slide 156

Slide 156 text

来歴を生成する 対応 成果物とコンテナイメージに、GitHub の artifact attestations で来歴を付ける コンテナイメージは push-to-registry でレジストリに attestation を置く 生成を組織の再利用ワークフローに集約し、呼び出し側のジョブからは来歴を偽造できなくする 156

Slide 157

Slide 157 text

( ) SLSA Supply-chain Levels for Software Artifacts は、成果物の作られ方を段階に分けて表す枠組みであり、来歴の有無と偽造の難しさに応 じて Build レベルが上がる。 来歴の形式(SLSA provenance)もこの枠組みが定義している。 SLSA レベル L1 L2 L3 来歴を作る主体 来歴の偽造に要るもの GitHub Actions では ビルドのステップ(署名なし) ビルドのステップで生成、基盤の ID で署名 基盤が生成と署名 書き換えるだけ ワークフローの書き換え 基盤の脆弱性の悪用 自前のスクリプトで来歴を出す attestation を付ける 組織の再利用ワークフローでビルド 157

Slide 158

Slide 158 text

ランナーの外向き通信を記録する ランナーの外向き通信を記録すれば、侵害の後に何がどこへ送られたかを特定できる。 記録した宛先は、許可リストを作る材料にもなる。 原則 侵害後の手順の事前設計 侵害例 (2025 年 3 月): StepSecurity のネットワーク異常検知で発見された tj-actions/changed-files https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised のサプライチェーン攻撃(2026 年 3 月): Aqua を装ったタイポスクワットのドメインへ送信しており、宛先の記 録で検知できた Trivy https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack 158

Slide 159

Slide 159 text

ランナーの外向き通信を記録する 対応 を egress-policy: audit で入れ、宛先の記録を取る セルフホストでは、ネットワークのログを取る Harden-Runner 159

Slide 160

Slide 160 text

SBOM を生成する (ソフトウェア部品表)は、成果物に何が入っているかの記録である。 成果物ごとに残せば、依存先の問題が後から判明したとき、どのリリースが含んでいたかを再 ビルドなしで特定できる。 SBOM 原則 侵害後の手順の事前設計 160

Slide 161

Slide 161 text

SBOM 対応 を生成する ( )や docker buildx build --sbom=true で、ビルド時に SPDX か の を生成する 成果物と同じリリースかレジストリに保存する Syft anchore/sbom-action CycloneDX SBOM 161

Slide 162

Slide 162 text

ランナー上のステップを非特権で動かす ホストのランナーは passwordless sudo が付いており、ステップは root になれる。 root になると、他のプロセスのメモリからシークレットを読める。 GitHub 原則 実行されるコードの把握 侵害例 のサプライチェーン攻撃(2026 年 3 月): ペイロードが sudo で ptrace の制限を回避し、他プロセスのメモリか らシークレットを抽出した Trivy https://socket.dev/blog/trivy-under-attack-again-github-actions-compromise 162

Slide 163

Slide 163 text

ランナー上のステップを非特権で動かす 対応 ホストのランナーでは、Harden-Runner の disable-sudo-and-containers で sudo とコン テナの利用を外す コンテナジョブでは、次の 3 つを守る privileged を使わない capability を落とす Docker ソケットをマウントしない ステップを非 root で動かす セルフホストの Kubernetes ベースのランナーでは、特権 Pod と hostPath を禁止する GitHub 163

Slide 164

Slide 164 text

再現可能ビルドにする 同じソースを独立した環境で再ビルドして成果物と一致すれば、ビルド環境の汚染を検出でき る。 原則 出所と内容の検証 侵害例 (2020 年 12 月): SUNSPOT(ビルドサーバーに置かれたマルウェア)がビルド中にソースファイルを差し SolarWinds 替えた https://www.crowdstrike.com/en-us/blog/sunspot-malware-technical-analysis/ 164

Slide 165

Slide 165 text

再現可能ビルドにする 対応 ビルドの入力とツールチェーンを固定し、タイムスタンプなどの非決定要素を除く SOURCE_DATE_EPOCH で成果物に埋め込む時刻を固定する Reproducible Builds プロジェクトの文書に非決定要素の一覧と対処法があり、diffoscope は成果物 の差分を可読な形で示す Nix は入力のハッシュで成果物を識別し、Linux では既定のサンドボックスがネットワークと未宣言 の入力を遮断する Go 1.21 以降のツールチェーンはソースのみを入力とする再現可能ビルドで、-trimpath がビルドパ スを除く 独立した環境で定期的に再ビルドして比較する 165

Slide 166

Slide 166 text

ワークフローの実行記録を保持する 汚染された外部コードを、いつからいつまでどのワークフローが使ったかを答えられなけれ ば、失効の範囲が決まらない。 SHA で固定した Action には Dependabot alerts が出ないので、使った時期は実行記録で調べ る。 原則 侵害後の手順の事前設計 侵害例 ( 年 月) 影響を受けた期間の実行を、各組織が自分で特定する作業を要した tj-actions/changed-files 2025 3 : https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised 166

Slide 167

Slide 167 text

ワークフローの実行記録を保持する 対応 ジョブごとに使った Action とコミット SHA を、実行ログの保持期間より長く外部に保存する 監査ログのストリーミング、ワークフロー実行の API、Harden-Runner の記録などを使う 167

Slide 168

Slide 168 text

ログにシークレットを出さない マスキングは既知のシークレットだけに働き、構造化データに包まれたものはマスクされな い。 公開リポジトリのログは誰でも読める。 原則 盗まれる前提の資格情報 侵害例 (2022 年 6 月): API から取得できたビルドログの標本から、約 7 万 3000 件の資格情報が見つかった Travis CI https://www.aquasec.com/blog/travis-ci-security/ 168

Slide 169

Slide 169 text

ログにシークレットを出さない 対応 環境変数を出力しない 派生した値は ::add-mask:: で登録する デバッグログを本番ワークフローで有効にしない ログ本文の保持期間を短くする 169

Slide 170

Slide 170 text

第 4 章「届ける」

Slide 171

Slide 171 text

「届ける」の範囲 ビルドの成果物を CI が公開し、デプロイして、レジストリか本番環境に到達するまで を扱う。 CI 成果物 公開とデプロイ 公開 レジストリ デプロイ 本番環境 171

Slide 172

Slide 172 text

のサプライチェーン攻撃 2026 年 3 月、攻撃者は盗んだ公開の権限で、汚染版を次のようにリリースした。 Trivy Trivy の公開 月 19 日 18:22 UTC、2 月の侵害後も残 っていたアクセスで Trivy v0.69.4 が公開 された。3 月 22 日には Docker Hub に v0.69.5 と v0.69.6 の不正イメージが追加 された 3 出典 GitHub LiteLLM の公開 月 24 日 10:39 と 10:52 UTC、CI から 盗まれた PyPI の公開トークンで LiteLLM 1.82.7 と 1.82.8 が公開された 3 取り消し は v0.69.4 を取り下げてアドバイザ リを出し、PyPI は最初の報告から約 3 時間 で LiteLLM の両バージョンを削除した Trivy アドバイザリ: https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 Trend AI Security: https://www.trendaisecurity.com/en-us/resources-insights/trendai-security-blog/inside-litellm-supply-chain-compromise 172

Slide 173

Slide 173 text

「届ける」の項目 「届ける」でやるべきことは、次の 13 項目である。 重⼤度 ⾼ 68 パッケージの公開に短命な資格情報を使う 69 デプロイのロールを最⼩権限にする 70 本番デプロイのソースを保護ブランチとタグに限定する 71 公開とデプロイの権限を⼈に持たせない 重⼤度 中 72 ⾃組織のスコープと社内パッケージ名を公開レジストリで確 73 デプロイ対象をダイジェストで指定する 74 公開と本番デプロイに⼈の承認を必須にする 75 デプロイ時に来歴を検証する 76 SBOM と来歴を成果物に添えて公開する 77 ⾃分の成果物の公開とデプロイのイベントに通知を付ける 78 緊急時の本番操作を申請制の⼀時的な権限に限定する 79 段階的にロールアウトする 80 汚染された成果物を出してしまったときの⼿順を持つ 173

Slide 174

Slide 174 text

パッケージの公開に短命な資格情報を使う ( 、 )では、レジストリが CI の OIDC トークンを検証し、そのジョブに だけ有効な公開の資格情報を発行する。 既存の公開トークンを失効させれば、盗まれる長期トークンが公開の経路からなくなる。 Trusted Publishing npm PyPI 原則 盗まれる前提の資格情報 侵害例 ( Ultralytics 2024 された 年 12 月): Trusted Publishing に移行した後も失効していなかった API トークンで、8.3.45 と 8.3.46 が公開 https://blog.pypi.org/posts/2024-12-11-ultralytics-attack-analysis/ ( 年 月) から盗まれた の公開トークンで と が公開された LiteLLM 2026 3 : CI PyPI 1.82.7 1.82.8 https://securitylabs.datadoghq.com/articles/litellm-compromised-pypi-teampcp-supply-chain-campaign/ ( 年 月) ワークフローから持ち出された トークンで汚染版が公開された Nx s1ngularity 2025 8 : npm https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c 174

Slide 175

Slide 175 text

パッケージの公開に短命な資格情報を使う 対応 と PyPI の Trusted Publishing を設定し、公開ワークフローと environment を指定する 既存の公開トークンを失効させる npm 175

Slide 176

Slide 176 text

デプロイのロールを最小権限にする がデプロイに使うロールの権限を、対象の環境と操作に必要な範囲まで絞る。 ロールが広いほど、ワークフローの侵害がそのまま本番環境の侵害になる。 CI 原則 盗まれる前提の資格情報 176

Slide 177

Slide 177 text

デプロイのロールを最小権限にする 対応 環境ごとにデプロイのロールを分け、本番環境のロールは本番環境の対象だけを変更できるようにす る コンテナのデプロイでは、イメージの push 先のリポジトリと、更新する対象のサービスに絞る サーバーレスや静的サイトでは、対象の関数やバケットの更新だけを許す Terraform では plan と apply のロールを分け、plan のロールは読み取りのみにする state の保管先は、この 2 つのロールだけに読ませる 177

Slide 178

Slide 178 text

本番デプロイのソースを保護ブランチとタグに限定する デプロイのワークフローは、既定ではどのブランチからでも起動できる。 本番へ出せるソースを保護ブランチと不変のタグに限ると、レビューを通っていない変更は本 番へ出ない。 原則 出所と内容の検証 178

Slide 179

Slide 179 text

本番デプロイのソースを保護ブランチとタグに限定する 対応 では environment のデプロイ元ブランチの制限を使い、ワークフローに を書く クラウドの OIDC の信頼ポリシーにも、ブランチと環境の条件を付ける CD ツールのソース設定で、対象のブランチとタグを固定する GitHub Actions environment: 179

Slide 180

Slide 180 text

公開とデプロイの権限を人に持たせない 端末からの npm publish や terraform apply は、「取り込む」と「ビルドする」の統制をすべ て迂回する。 人のアカウントとロールに公開とデプロイの権限があれば、その資格情報が盗まれた時点でこ の経路が開く。 原則 盗まれる前提の資格情報 侵害例 ( 年 月) 個人アカウントのトークンで汚染版が手動で公開された Axios 2026 3 : https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan 180

Slide 181

Slide 181 text

公開とデプロイの権限を人に持たせない 対応 は Publishing access で「Require two-factor authentication and disallow tokens」を選 び、トークンによる公開を禁止する 2FA 付きの対話的な公開は残るので、maintainer の数を絞る PyPI に同じ設定はなく、既存トークンの失効で代替する クラウド側では、人のロールに本番変更の deny を付ける 社内プロキシレジストリへの書き込み権限も同じ基準で絞る npm 181

Slide 182

Slide 182 text

自組織のスコープと社内パッケージ名を公開レジストリで確保する の組織スコープを確保していなければ、誰かが先に @myorg の組織を作り、その配下に 公開できる。 スコープを確保すれば、利用者側に設定がなくても、その名前で公開される経路を塞げる。 npm 原則 出所と内容の検証 182

Slide 183

Slide 183 text

自組織のスコープと社内パッケージ名を公開レジストリで確保する 対応 で組織を作り、@myorg のスコープを確保する 社内パッケージ名は確保したスコープの配下に置き、スコープのない名前は使わない npm 183

Slide 184

Slide 184 text

デプロイ対象をダイジェストで指定する のような可変の参照でデプロイすると、実行のたびに違うものが出る。 ダイジェストで指定すれば、検証したものとデプロイされるものが一致する。 latest 原則 出所と内容の検証 184

Slide 185

Slide 185 text

デプロイ対象をダイジェストで指定する 対応 マニフェストと Helm chart で、イメージをダイジェスト指定にする ダイジェストの更新は PR で行い、レビューを経る 185

Slide 186

Slide 186 text

公開と本番デプロイに人の承認を必須にする マージがデプロイを意味しない経路(手動 dispatch、公開ワークフロー)では、実行の直前に 人の承認を挟む。 承認されるまで、本番用のシークレットをジョブへ渡さないようにする。 原則 出所と内容の検証 侵害例 (2025 年 8 月): 公開用の npm トークンが environment の承認の外にあり、注入されたワークフロー Nx s1ngularity から読めた https://github.com/nrwl/nx/security/advisories/GHSA-cxm3-wv7p-598c ( 年 月) シークレットが の承認の外にあり、追加されたワークフローから読めた GhostAction 2025 9 : environment https://blog.gitguardian.com/ghostaction-campaign-3-325-secrets-stolen/ 186

Slide 187

Slide 187 text

公開と本番デプロイに人の承認を必須にする 対応 では、required reviewers 付きの environment に本番用のシークレットを置く ワークフローには environment: を書く 起動した本人が承認できないよう、Prevent self-review を有効にする 管理者もバイパスできないよう、Allow administrators to bypass configured protection rules を外す 他の CD ツールでも、本番ステージに手動承認のゲートを置く GitHub Actions 187

Slide 188

Slide 188 text

デプロイ時に来歴を検証する 来歴は、デプロイ先が検証しなければ汚染版の受け入れを止めない。 署名の有無ではなく、期待するリポジトリとワークフローの ID で作られたかを見る。 原則 出所と内容の検証 188

Slide 189

Slide 189 text

デプロイ時に来歴を検証する 対応 や cosign verify を、デプロイのワークフローに入れる 期待する ID は --owner、--signer-workflow、--source-ref で指定する Kubernetes では Kyverno や Sigstore の policy-controller で強制する gh attestation verify 189

Slide 190

Slide 190 text

SBOM と来歴を成果物に添えて公開する 利用者は、SBOM で影響を受けるかを判定し、来歴で入手したものが公開元の CI から出たか を検証する。 どちらも公開元が添えていなければ、利用者の側では作れない。 原則 侵害後の手順の事前設計 190

Slide 191

Slide 191 text

SBOM と来歴を成果物に添えて公開する 対応 リリースに SBOM と attestation を添付する SBOM も attestation として署名し、成果物のダイジェストに紐づける actions/attest-sbom など npm は Trusted Publishing なら来歴が自動で付き、トークン公開なら --provenance を付ける 自動生成は、公開リポジトリから公開パッケージを出す場合に限る PyPI は pypa/gh-action-pypi-publish が attestation を自動で生成する 191

Slide 192

Slide 192 text

自分の成果物の公開とデプロイのイベントに通知を付ける 盗まれたトークンでの公開に、公開元が最初に気づく手がかりはレジストリの公開イベントで ある。 自分のパイプラインの実行と照合する。 原則 侵害後の手順の事前設計 侵害例 (2024 年 12 月): パイプラインの外から API トークンで 8.3.45 と 8.3.46 が公開された Ultralytics https://blog.pypi.org/posts/2024-12-11-ultralytics-attack-analysis/ (2026 年 3 月): CI から盗まれたトークンで 1.82.7 と 1.82.8 が公開された LiteLLM https://securitylabs.datadoghq.com/articles/litellm-compromised-pypi-teampcp-supply-chain-campaign/ 192

Slide 193

Slide 193 text

自分の成果物の公開とデプロイのイベントに通知を付ける 対応 の公開通知メール、ECR の EventBridge イベント、GHCR の package Webhook、 のデプロイ記録を受け取る 受け取ったイベントを、パイプラインの実行と照合する npm environment 193

Slide 194

Slide 194 text

緊急時の本番操作を申請制の一時的な権限に限定する 人の経路を閉じても、緊急時の経路を設計しなければ常設の管理者ロールが非常用として残 り、それが盗まれる対象になる。 原則 盗まれる前提の資格情報 194

Slide 195

Slide 195 text

緊急時の本番操作を申請制の一時的な権限に限定する 対応 の Privileged Identity Management(PIM)は、申請と承認を経て時間制限付 きのロールへ昇格させる AWS の場合は、IAM Identity Center と連携する AWS Samples のソリューション TEAM (Temporary Elevated Access Management)を使う TEAM は IAM Identity Center の機能ではなく、自分の AWS 環境にデプロイする 操作を記録し、終了後に自動で失効させる Microsoft Entra ID 195

Slide 196

Slide 196 text

段階的にロールアウトする 一度に全環境へデプロイすると、汚染や欠陥のある成果物が全体へ同時に届く。 一部の環境へ先行して出し、指標を見てから残りへ広げれば、問題を全体へ届く前に止められ る。 原則 侵害後の手順の事前設計 196

Slide 197

Slide 197 text

段階的にロールアウトする 対応 デプロイは Argo Rollouts や CodeDeploy のカナリアで、指標を見て広げる パッケージは next などの dist-tag で先に出し、latest へ移すのは確認後にする 197

Slide 198

Slide 198 text

汚染された成果物を出してしまったときの手順を持つ 公開元の対応は「止める」「戻す」「取り下げる」「通知する」の 4 つで、利用者は通知を受ける まで汚染版を使い続ける。 順序を平時に決めておかないと、取り下げと通知が遅れる。 原則 侵害後の手順の事前設計 侵害例 Trivy のサプライチェーン攻撃(2026 年 3 月): アドバイザリの発行と汚染版の取り下げで対応した https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23 198

Slide 199

Slide 199 text

汚染された成果物を出してしまったときの手順を持つ 対応 直前の成果物へ戻す手順を書き、定期的に実行して確認する レジストリでの deprecate と取り下げ、アドバイザリの発行、利用者への通知先を書いておく 利用者が汚染版を報告できる窓口(SECURITY.md)を公開する 199

Slide 200

Slide 200 text

おわりに

Slide 201

Slide 201 text

Trivy の連鎖(再掲) フォークから 細⼯した PR を送る PR のコードが、本家の 権限で実⾏される コードを仕込む コードを動かす 汚染版の Trivy が LiteLLM の CI に取り込まれる の CI で 汚染版が実⾏される コードを仕込む LiteLLM コードを動かす aqua-bot の PAT 資格情報を盗む PyPI の公開トークン 資格情報を盗む 盗んだ PAT で 汚染版の Trivy を公開 ⽬的を遂⾏する LiteLLM PyPI の汚染版を に公開 ⽬的を遂⾏する 201

Slide 202

Slide 202 text

連鎖で起きたこと 各段で起きたことを、被害者ごとに並べる。 被害者 Trivy Trivy Trivy Trivy LiteLLM LiteLLM LiteLLM LiteLLM 段階 仕込む 動かす 盗む 遂行 仕込む 動かす 盗む 遂行 起きたこと フォークから細工した PR が送られた PR のコードが本家の権限で実行された bot アカウントの PAT が持ち出された 残存した PAT でリリースワークフローを書き換え、汚染版を公開した 汚染版の Trivy が CI に取り込まれた CI で汚染版が実行された PyPI の公開トークンが持ち出された LiteLLM の汚染版が PyPI に公開された 202

Slide 203

Slide 203 text

連鎖の各段に対応する項目 連鎖の各段に、この資料の項目のどれが対応するかを示す。 被害者 段階 対応する項目 なし Trivy 仕込む 動かす Trivy 盗む Trivy LiteLLM 遂行 仕込む 動かす 盗む LiteLLM 遂行 Trivy LiteLLM LiteLLM を起動できる主体と条件を限定する(防止) 46 信頼していないコードを動かすジョブにシークレットを渡さない(防止)/45 長期のシークレットを CI から消 す(緩和) 23 タグとリリースを書き換えられなくする(防止)/39 リポジトリと連携先の失効手順を持つ(防止) 42 取得したスクリプトとバイナリの完全性を検証する(防止) なし 68 パッケージの公開に短命な資格情報を使う(防止)/58 ランナーの外向き通信を許可リストに制限する(緩和) 39 リポジトリと連携先の失効手順を持つ(防止)/80 汚染された成果物を出してしまったときの手順を持つ(緩 和) 25 CI 203

Slide 204

Slide 204 text

4 つの問い 原則 出所と内容の検証 実行されるコードの把握 盗まれる前提の資格情報 侵害後の手順の事前設計 問い それが本物で、意図したとおりのものだと、何で確かめた そのコードは、誰が動かすと決めた その資格情報は、盗まれたら何ができる 侵害されたら、最初に何をする 204

Slide 205

Slide 205 text

No content