AIが書いたGitHub Actions、マージ前に確認する3点:権限・SHA固定・pull_request_target
AI実用ラボ
AIが書いたGitHub Actions、マージ前に確認する3点:権限・SHA固定・pull_request_target
【中級者向け】YAMLの基本的な書き方とGitHubのPull Request操作が分かる人向け。GitHub Actionsの権限モデルを詳しく知らなくても、本文中で必要な用語をその都度説明する。
Claude CodeやCodexのようなAIエージェントに「テストを自動実行するworkflowを作って」と頼むと、数十秒で動くYAMLが出てくる。実際にpushしてグリーンチェックが付けば、そのままマージしてしまいがちだ。だが、そのYAMLに`permissions`の指定がなかったり、外部の`uses:`アクションがタグ名のまま貼り付けられていたり、`pull_request_target`をよく分からないまま使っていたりすると、「動く」ことと「安全である」ことは別問題になる。
この記事では、GitHub公式ドキュメントの一次情報にもとづいて、AIが生成したworkflowファイルをマージする前に確認すべき3つのポイント(トークン権限・アクションの固定方法・危険なトリガーの扱い)を、そのまま使えるYAML例とコマンドつきで整理する。
**TL;DR** - `permissions:`を書かないと、GITHUB_TOKENに想定より広い権限が渡ることがある。ジョブ単位で最小権限を明示する。 - 外部アクションは`@v4`のようなタグではなく、コミットSHAで固定すると改ざんに強くなる。GitHub API(`api.github.com`)を叩けば、読者自身の手で最新のSHAを確認できる。 - `pull_request_target`と`workflow_run`は、フォークのコードを誤ってチェックアウト・実行すると、リポジトリの書き込み権限とシークレットごと乗っ取られる「pwn request」という攻撃パターンにつながる。

Claude Codeに「テスト用のGitHub Actionsを作って」と頼んだら一発で動くYAMLが出てきたので、そのままマージしちゃいました…まずかったですか?

動くこと自体は問題ありません。ただ、権限が広すぎたり、外部アクションがタグ指定のままだったり、pull_request_targetの扱いが甘かったりすると、動くのに安全ではない状態になっていることがあります。この記事で3つのチェックポイントを順番に見ていきましょう。
この記事でできること
- AIが生成したGitHub Actionsのworkflowファイルを、マージ前に自分でレビューできるようになる
- `permissions`キーの最小権限設定を、公式のYAML例そのままで書けるようになる
- 外部アクションをコミットSHAへ固定する手順と、そのSHAをGitHub APIで自分の手で確認する方法が分かる
- `pull_request_target`が危険になる具体的な条件と、安全な代替トリガーの選び方が分かる
対象読者
- Claude Code、Codex、GitHub CopilotなどのAIエージェントにCI/CD workflowを書かせている、または書かせようとしている人
- GitHub Actionsの基本的な書き方(`on:` `jobs:` `steps:`)は分かるが、権限設計やサプライチェーンリスクは詳しく調べたことがない人
- 個人リポジトリだけでなく、OSSや組織リポジトリでActionsを運用する人
確認環境・確認日
- 確認日: 2026-09-27
- 一次情報: GitHub Docs「Secure use reference」「Use GITHUB_TOKEN for authentication in workflows」「Securely using pull_request_target」(すべて2026-09-27時点で取得、ページフッターに"GitHub Inc. © 2026"の表記があることを確認)
- バージョン確認: `actions/checkout`の最新タグをGitHub API(`api.github.com`)で直接確認(2026-09-27時点で`v7.0.1`、公開日`2026-07-20`)。本記事のコマンド例は読者が実行した時点の最新バージョンに置き換えて使うこと。
背景:なぜ「動く」workflowがそのまま危ないのか
GitHub Actionsのjobは、既定で`GITHUB_TOKEN`という一時的な認証トークンを受け取る。このトークンは`github.token`コンテキスト経由でどのstepからもアクセスでき、`permissions`キーで制限しない限り、リポジトリに対して比較的広い操作ができてしまう。公式ドキュメントは「GITHUB_TOKENには必要最小限の権限だけを与えるべきで、既定の権限をリポジトリ内容の読み取りのみに設定するのがよい実践だ」と明記している。
AIエージェントに「テストを実行するworkflowを作って」とだけ頼むと、動作すること自体を優先した最小構成のYAMLが返ってくることが多く、`permissions`の指定が省略されがちだ。省略されていても多くの場合は動いてしまうため、レビューで気づかない限り、そのまま本番のリポジトリに残り続ける。
さらに、workflowの中で使う外部アクション(`uses: owner/action@タグ`のような行)は、そのアクションのリポジトリが乗っ取られたり、悪意あるコードを混入されたりすると、workflowを実行するたびに攻撃者のコードがGITHUB_TOKENと同じ権限で動く。公式ドキュメントは「単一のアクションが侵害されただけで、リポジトリの全シークレットへのアクセスと書き込み権限を攻撃者に渡しかねない」と説明しており、サードパーティアクションを使うこと自体に一定のリスクがあることを前提にしている。

権限を絞らなくても普通に動いているので、今のままで十分な気がしてしまいます…

動いているのは、今のリポジトリ設定がたまたま守ってくれているだけかもしれません。GITHUB_TOKENの既定の権限は環境によって変わるので、workflow側で明示しておかないと、設定を変えた瞬間に想定より広い権限が渡ることがあります。
AIが書いたYAMLをレビューする/しないで何が変わるか
| 観点 | レビューせずマージ | 3点チェック後にマージ |
| GITHUB_TOKENの権限 | 既定のまま(範囲が広くなりがち)、または権限指定なし | ジョブごとに`contents: read`など必要な権限だけを明示 |
| 外部アクションの参照方法 | `@v4`などのタグのまま(移動・削除されうる) | コミットSHAで固定、または検証済み作者のタグのみ許容 |
| `pull_request_target`の扱い | AIの提案をそのまま採用、フォークコードを実行するリスクに未対応 | 必要性を確認し、フォークのコードを実行しない設計に直す |
| 侵害時の被害範囲 | リポジトリ書き込み・シークレット全体に及びうる | 権限が絞られているため被害範囲を限定できる |
必要なもの
- レビュー対象のworkflowファイル(`.github/workflows/*.yml`)
- GitHubリポジトリへの書き込み権限、またはPull Requestでの提案権限
- `curl`が使える環境(外部アクションのコミットSHAを自分で確認するため。GitHub API呼び出しには認証不要)
- (任意)組織リポジトリの管理者権限。SHA固定を組織ポリシーとして強制する場合に必要
全体像:3つのチェックポイント
AIが生成したworkflowをレビューする流れは、次の3つのチェックを順に当てるだけでよい。
- **権限チェック**:`permissions`が最小権限になっているか
- **固定チェック**:外部アクションがコミットSHAで固定されているか
- **トリガーチェック**:`pull_request_target`や`workflow_run`がフォークのコードを実行していないか
この3つは独立しているので、既存のworkflowに後から追加する形でも、AIに直させる形でも適用できる。以降、それぞれのチェックを具体的なYAML例で見ていく。

チェック1: GITHUB_TOKENの権限を最小化する
まず、workflow全体またはジョブ単位で`permissions`キーを明示する。公式チュートリアルに掲載されている例をそのまま使うと、次のようになる。
name: Open new issue
on: workflow_dispatch
jobs:
open-issue:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- run: |
gh issue --repo ${{ github.repository }} \
create --title "Issue title" --body "Issue body"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ポイントは、`gh` CLIやREST APIを呼ぶだけのジョブに対して、必要な権限(この例では`contents: read`と`issues: write`)だけを列挙している点だ。AIが生成したworkflowに`permissions`が無ければ、まずそのジョブが実際に何をしているか(コードを読むだけか、Issueを作るのか、PRにコメントするのか)を洗い出し、対応する権限だけを足す。
REST APIを直接叩く場合も考え方は同じで、公式チュートリアルには次の例が載っている。
name: Create issue on commit
on: [push]
jobs:
create_issue:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Create issue using REST API
run: |
curl --request POST \
--url https://api.github.com/repos/${{ github.repository }}/issues \
--header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
--header 'content-type: application/json' \
--data '{
"title": "Automated issue for commit: ${{ github.sha }}",
"body": "This issue was automatically created by the GitHub Action workflow **${{ github.workflow }}**."
}' \
--fail
`permissions`を明示せずにこのworkflowを動かすと、既定の権限設定(リポジトリまたは組織の設定次第で「読み取りのみ」か「読み書き」)に依存してしまう。AIが出したYAMLに`permissions`が無い場合は「動いたから問題ない」ではなく、「今のリポジトリ設定にたまたま守られている」だけの可能性を疑うべきだ。
チェック2: 外部アクションをコミットSHAで固定する
次に、workflowの中で`uses:`に外部アクションを指定している行を確認する。`actions/checkout@v4`のようにタグ名で指定している場合、そのタグが指す中身は将来変わりうる。公式ドキュメントは「タグはリポジトリへのアクセス権を持つ攻撃者によって移動・削除されることがあり、作者を信頼していてもこのリスクは残る」と説明している。
より安全なのは、コミットSHA(40文字の完全なハッシュ値)で固定する方法だ。公式ドキュメントいわく、「コミットSHAでの固定は、現時点でアクションをイミュータブル(不変)なリリースとして扱う唯一の方法」であり、攻撃者がバックドアを仕込むにはSHA-1の衝突を作る必要があるため難易度が上がる。
固定に使うSHAは、想像やAIの回答をそのまま信じるのではなく、自分の手でGitHub APIから取得できる。次のコマンドで、任意のアクションの最新リリースとそのコミットSHAが分かる。
# 最新リリースのタグ名を調べる(例: actions/checkout)
curl -s "https://api.github.com/repos/actions/checkout/releases/latest" \
| grep -E '"tag_name"|"published_at"'
# そのタグが指すコミットSHAを調べる(<タグ名>を上の結果に置き換える)
curl -s "https://api.github.com/repos/actions/checkout/git/ref/tags/<タグ名>" \
| grep '"sha"'
このコマンドを2026-09-27に実際に実行したところ、`actions/checkout`の最新タグは`v7.0.1`(公開日2026-07-20)で、対応するコミットSHAは`3d3c42e5aac5ba805825da76410c181273ba90b1`だった。これを使うと、workflow内の指定は次のようになる。
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
行末のコメントにタグ名を残しておくと、後から見返したときにどのバージョン相当かが分かりやすい。**ここで示したSHAは2026-09-27時点の値であり、読者が実行する時点の最新版とは異なる。** 必ず自分でコマンドを実行し、その時点の値を使うこと。
SHAで固定する前に、「そのコミットが本当にアクション本体のリポジトリのものか、フォークのものではないか」を確認するのも公式の推奨事項だ。`git/ref/tags/`のAPIレスポンスに含まれるコミットURL(`object.url`)が、目的のリポジトリ(例では`actions/checkout`)を指しているかを見ればよい。

外部アクションをSHAで固定しました。これでもう安心ですよね?

権限とSHA固定は、それぞれ別のリスクに効く対策です。もう一つ、pull_request_targetというトリガーの使い方を誤ると、フォークから送られたPull Requestだけで書き込み権限とシークレットを奪われる経路が残っています。次はそこを見ていきましょう。
チェック3: pull_request_target / workflow_run の危険な組み合わせを避ける
3つ目は、AIが提案しがちな便利なトリガーの見落としだ。`pull_request`イベントは、フォークから送られたPull Requestであってもマージコミットからworkflowファイル自体を実行するため、GitHubは既定でこのイベントに読み取り専用のGITHUB_TOKENしか渡さず、他のシークレットへのアクセスも制限している。
一方`pull_request_target`は、workflowファイル自体をベースリポジトリのデフォルトブランチから実行する。そのため「信頼されたコードしか動かない」という前提のもとで、書き込み可能なGITHUB_TOKENとシークレットへのアクセスが渡される。ここで問題になるのは、`actions/checkout`にPull Requestのhead(フォーク側の変更内容)を指定して、そのコードをテストやビルドで実行してしまうケースだ。公式ドキュメントはこれを「pwn request」と呼び、次のような形を危険なパターンとして挙げている。
# 危険な例:pull_request_targetでフォークのコードをチェックアウトし、
# 直後のステップでそのコードを実行してしまう
on: pull_request_target
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: make test # フォークのMakefileが攻撃者の権限昇格に使われうる
`actions/checkout`のstep自体は無害だが、直後に`make test`のようなコマンドでフォーク由来のファイル(Makefile、package.json、設定ファイルなど)を実行すると、攻撃者はPull Requestを送るだけで、ベースリポジトリのシークレットとGITHUB_TOKENを使ったコマンド実行を狙えてしまう。
対策として公式が挙げているのは次の3点だ。
- 必要なければpull_request_targetを使わない。通常のテストはpull_requestで行い、権限が必要な後続処理を分ける場合はworkflow_runを検討する。ただし、切り替えるだけでは安全にならず、後続処理で信頼できないコードや成果物を実行しない設計が必要だ。
- pull_request_targetではフォーク由来のコードを実行しない。ラベル付けやコメント投稿など、信頼できる処理と必要最小限の権限で完結する用途に限定する。
- allow-unsafe-pr-checkout: trueは、フォークPRのhead参照をチェックアウトする制限を解除する設定であり、コードを安全に実行できるようにする機能ではない。公式の条件は「取得したコードを決して実行しないことを確認した場合」に限る。取得後のビルド・テスト・インストールなどでそのコードを実行してはならない。レビューでは、この設定の用途と実行に至る経路がないことを確認する。

直したつもりですが、ちゃんと効いているか自分で確認する方法はありますか?

はい、新しいツールを入れなくても、YAMLとGitHub APIの出力を突き合わせるだけで確認できます。3つのチェック項目に沿って見ていきましょう。
動作確認:設定が効いているか確かめる方法
設定を直したら、次の3つを確認する。
- **YAMLを読み返す**:`permissions`ブロックが各ジョブ(またはworkflow全体)に存在し、書き込み系の権限(`contents: write`など)が本当に必要な範囲だけに絞られているかを目で確認する
- **SHA固定を確認する**:`uses:`行がすべて40文字のコミットSHA、または信頼できる作者のタグになっているかを確認する。判断に迷うアクションは、前述のcurlコマンドで実際のコミットSHAとリポジトリ所有者を再確認する
- **トリガーとcheckoutの組み合わせを確認する**:`pull_request_target`または`workflow_run`を使っているジョブで、`actions/checkout`に`ref:`でPull Requestのhead(`github.event.pull_request.head.sha`など)を指定していないか、指定している場合はその後のstepでフォーク由来のファイルを実行していないかを確認する
いずれも新しくツールを導入する必要はなく、YAMLとGitHub APIの出力を読み合わせるだけで確認できる。
応用・実践パターン
パターン1: 複数の外部アクションを一括で洗い出すスクリプト
workflowファイルの中でタグ指定のままになっているアクションを機械的に洗い出す、簡単なシェルスクリプトの例。`.github/workflows/`以下の全YAMLから`uses:`行を抜き出し、SHA固定済みかどうかを判定する。
#!/usr/bin/env bash
set -euo pipefail
# .github/workflows以下のuses:行から owner/repo@ref を抽出し、
# refが40桁の16進数(=コミットSHA)かどうかを表示する
grep -rhoE 'uses:\s*[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[A-Za-z0-9._-]+' .github/workflows \
| sed -E 's/uses:\s*//' \
| while IFS='@' read -r action ref; do
if [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then
echo "OK(SHA固定済み): $action@$ref"
else
echo "要確認(タグ指定): $action@$ref"
fi
done
「要確認」と出たアクションだけ、前述のcurlコマンドで最新のコミットSHAを調べて置き換えれば、workflow全体を一つずつ手で見比べる必要がなくなる。
パターン2: リポジトリの性質に応じた使い分け
| リポジトリの性質 | 推奨する対応 |
| 個人の小規模リポジトリ | まずは`permissions`の最小化とSHA固定の2点だけでも実施する。`pull_request_target`は基本的に使わない |
| 社外からのPull Requestを受け付けるOSS | `pull_request_target`を使う場合は、フォークのコードを一切実行しないジョブ(ラベル付け・自動返信など)に限定する |
| 組織で複数リポジトリを管理している | 組織設定でSHA固定を必須にするポリシーを検討する。個々のリポジトリでのレビュー漏れを構造的に防げる |
パターン3: 固定方法どうしの比較
| 方法 | 改ざんへの強さ | 運用の手間 | 向いている場面 |
| タグ指定(`@v4`など) | 弱い(タグは移動・削除されうる) | 低い | 検証済み作者のアクションを、更新の速さ優先で使う場合 |
| コミットSHA固定 | 強い(SHA-1衝突が必要) | やや高い(更新のたびにSHAを調べ直す) | サードパーティアクション全般、セキュリティを優先する場合 |
| SHA固定 + Dependabot | 強い | 低い(Dependabotが更新を検知) | 継続運用するリポジトリ全般(下記の注意点も参照) |

SHA固定してしまえば、あとは放置していても大丈夫ですか?

そこは一つ注意が必要です。SHA固定にはDependabotのセキュリティアラートが働きにくくなるというトレードオフがあります。更新確認の仕組みは別に用意しておきましょう。
運用時の注意点
SHA固定には一つ知っておくべきトレードオフがある。公式ドキュメントによれば、Dependabotは「セマンティックバージョニング(タグ)を使っているアクションに対してのみ脆弱性アラートを作成し、SHAで固定されたアクションに対してはアラートを作成しない」。つまり、SHA固定だけを行って放置すると、そのアクションに新しい脆弱性が見つかっても気づく手段が減ってしまう。SHA固定を導入するときは、更新のタイミングを別途決めておく(定期的に前述のcurlコマンドで最新版を確認する、リリースノートを確認するなど)ことが必要になる。
AIが生成したworkflowにありがちな3つの落とし穴
| 落とし穴 | 何が起きるか | 直し方 |
| `permissions`が省略されている | GITHUB_TOKENの権限がリポジトリ・組織の既定設定に依存し、想定より広くなることがある | ジョブごとに必要な権限だけを`permissions`で明示する(チェック1) |
| 外部アクションがタグ指定のまま | タグの移動・削除により、意図しないコードが実行されるリスクが残る | コミットSHAで固定し、SHAはGitHub APIで自分で確認する(チェック2) |
| `pull_request_target`でフォークのコードを実行している | Pull Requestを送るだけで、ベースリポジトリのシークレットとGITHUB_TOKENを使われる「pwn request」につながる | フォークのコードを実行しない設計に直すか、`workflow_run`など他のトリガーに切り替える(チェック3) |
注意点・制限事項
- YAML例はGitHub公式ドキュメントに基づく。自分のリポジトリへ適用する際は、トリガー、権限、読み込むコードの出所を確認し、本文の動作確認手順で設定と期待する結果を照合する。
- `actions/checkout`のバージョンやコミットSHAは時間とともに変わる。本文中のSHA(`3d3c42e5aac5ba805825da76410c181273ba90b1`)は2026-09-27時点の`v7.0.1`に対応する値であり、読者が確認する時点の最新版に置き換えて使うこと
- 組織単位でSHA固定を必須にするポリシー機能は、プランや管理者権限によって設定できる範囲が異なる。自分のリポジトリ・組織で実際に選べる設定は、GitHubの管理画面で確認すること
- `allow-unsafe-pr-checkout`のような個別のオプション名は、使用している`actions/checkout`のバージョンによって対応状況が異なる可能性がある。使う前にそのバージョンのドキュメントを確認すること
早見表
| 項目 | 内容 |
| 最小権限の書き方 | ジョブ内に`permissions:`を追加し、必要な権限(例: `contents: read` `issues: write`)だけを列挙 |
| 最新リリースの確認 | `curl`で`api.github.com/repos/<owner>/<repo>/releases/latest`を取得(チェック2のコマンド例を参照) |
| タグのコミットSHA確認 | `curl`で`api.github.com/repos/<owner>/<repo>/git/ref/tags/<タグ名>`を取得 |
| SHA固定の書き方 | `uses: owner/repo@`の後ろに40桁のコミットSHAを置き、末尾コメントでタグ名を残す |
| 危険なトリガー | `pull_request_target`、`workflow_run`(フォークのコード・成果物を実行する場合) |
| 権限を分ける設計 | 通常のテストはpull_request。workflow_runで後続処理を分ける場合も、信頼できないコード・成果物を実行しない |
| フォークPRの取得制限を解除する設定 | allow-unsafe-pr-checkout: true。取得したコードを決して実行しないと確認した場合に限る。安全な実行を許可する設定ではない |
| SHA固定の副作用 | Dependabotのセキュリティアラートがタグ指定のアクションほど機能しなくなる。更新確認を別途行う |

権限・SHA固定・pull_request_targetの3点、どれも既存のworkflowに後から足せそうですね。

その通りです。AIが生成したYAMLを丸ごと書き直す必要はなく、レビューの観点として3つ覚えておくだけで、既存のworkflowにも新しく作るworkflowにも使えます。
まとめ
- AIエージェントが生成したGitHub Actionsのworkflowは、「動く」ことと「安全である」ことが別問題であることを前提にレビューする
- `permissions`の最小化、外部アクションのSHA固定、`pull_request_target`/`workflow_run`の安全な扱いという3点は、既存のworkflowにも後から追加できる
- 外部アクションを固定するSHAは、想像や生成AIの回答をそのまま使わず、GitHub API(`api.github.com`)を自分の手で叩いて確認できる
- SHA固定にはDependabotのアラートが働きにくくなるというトレードオフがあるため、更新確認の仕組みをあわせて用意する
- `pull_request_target`は便利だが、フォークのコードを実行すると「pwn request」につながる。必要性を確認し、代替トリガーや実行範囲の限定を検討する
参考にした公式情報
- GitHub Docs「Secure use reference」 https://docs.github.com/en/actions/reference/security/secure-use (確認日2026-09-27)
- GitHub Docs「Use GITHUB_TOKEN for authentication in workflows」 https://docs.github.com/en/actions/tutorials/authenticate-with-github_token (確認日2026-09-27)
- GitHub Docs「Securely using pull_request_target」 https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target (確認日2026-09-27)
- GitHub REST API `repos/{owner}/{repo}/releases/latest` および `repos/{owner}/{repo}/git/ref/tags/{tag}` (2026-09-27に直接実行して確認)
