
pwn request対策でpull_request_targetは12月2日から許可制に。残る経路と洗い出し方
どうも!オペ部の西村祐二です!
GitHub Actions では 2026 年に、外部の PR のコードが秘密情報を持つワークフローで動いてしまう「pwn request」への対策が続けて入りました。主な対象は pull_request_target です。PR に反応するトリガーで、GitHub のドキュメントでは、フォークからの PR に対するラベル付け、トリアージ、認証付きでのステータスチェックの投稿が用途として挙げられています。こうした書き込みを行えるように、ベースリポジトリの権限で動きます。
actions/checkout は、v7 と、v1 を除くサポート中のメジャーバージョン(v2〜v6)へのバックポートで変わりました。pull_request_target などの特権のあるトリガーでは、フォーク PR のコード取得がデフォルトで拒否されます。さらに、イベントポリシーを設定していないパブリックリポジトリでは、2026 年 12 月 2 日から pull_request_target が許可制になり、イベントポリシーで許可しないと動かなくなります。
ただし、Wiz が 2026 年 10 月 26 日に公開した「The Pwn Request Reckoning」では、これらの対策が塞いだのは actions/checkout でフォーク PR のコードを取得する代表的な経路であり、まだ成立する経路が残っていると整理されています。この記事では、GitHub の changelog とドキュメント、Wiz の記事をもとに、対策が届く範囲と届かない範囲を分け、自分のワークフローの確かめ方をまとめます。
先に結論:確認と対策
| 確認すること | 対策 |
|---|---|
pull_request_target を使うワークフローが、秘密情報を本当に必要としているか |
不要なら pull_request に移す |
actions/checkout の古いバージョンを SHA やマイナー・パッチのタグで固定していないか、v1 を使っていないか |
v7 以上か、バックポート済みのバージョン(v4.4.0 など、後述)以上に更新する |
git fetch や gh pr checkout で PR のコードを取得していないか |
特権のあるワークフローでは PR のコードを取得・実行しない |
issue_comment や repository_dispatch で PR のコードを動かしていないか |
コメントした人の権限を確かめ、コメント時点のコミットに固定する |
${{ github.event.pull_request.title }} などを run: に直接書いていないか |
環境変数を経由して渡す |
workflow_run で PR 側のアーティファクトを実行していないか |
アーティファクトはデータとして読むだけにする |
| 未信頼のイベントで動くワークフローに、ジョブ間で状態が残るセルフホストランナーを使っていないか | エフェメラルなランナーにし、ランナーグループを分ける |
パブリックリポジトリで pull_request_target に依存しているワークフローがあるか(プライベートとインターナルのリポジトリにはデフォルトルールが適用されない) |
12 月 2 日までに影響を確かめ、必要なものだけイベントポリシーで許可する |
pwn request:外部の PR のコードが秘密情報のあるジョブで動く問題
pwn request は、外部の人が出した PR の内容が、秘密情報や書き込み権限を持つワークフローの中で実行されてしまう問題の呼び名です。
pull_request トリガーのワークフローは、フォークからの PR では秘密情報を受け取らず、GITHUB_TOKEN も読み取り専用で動きます。一方で pull_request_target は、ベースリポジトリのデフォルトブランチの文脈で動き、秘密情報と書き込み権限を持てます。このワークフローの中で PR 側のコードを取得してビルドやテストを行うと、PR を出した人のコードが秘密情報のある環境で実行されます。
GitHub のドキュメントも、追加の秘密情報が不要なら pull_request を使い、pull_request_target では未信頼のコードを明示的に取得しないよう案内しています。
2026 年に GitHub が入れた対策
GitHub の changelog で確認できる対策は次のとおりです。2026 年 10 月 30 日に確認した時点では、これより後に日付や範囲を変える changelog は出ていません。
| 日付 | 内容 | 出典 |
|---|---|---|
| 2026-06-18 | actions/checkout v7 が、pull_request_target と PR 起点の workflow_run でフォーク PR のコード取得をデフォルトで拒否する |
GitHub Changelog |
| 2026-07-20 | 同じ拒否を、v1 を除くサポート中のバージョンへバックポート | 同上(2026 年 7 月 15 日の編集者注で、当初の 7 月 16 日から変更) |
| 2026-09-17 | Workflow Execution Protections が一般提供(GA)。パブリックリポジトリでは pull_request_target を止めるデフォルトルールが、まず評価モード(止めずに、止まる予定の実行を記録するモード)で入る |
GitHub Changelog |
| 2026-12-02 | GA の前から pull_request_target のイベントポリシーを設定していなかったパブリックリポジトリで、デフォルトルールの強制が始まる(2026 年 10 月 21 日の編集者注で更新された日付) |
同上 |
actions/checkout の拒否が届く範囲
changelog によると、v7 はフォークからの PR で取得先が次のいずれかになるとき、取得を拒否します。
- フォークのリポジトリ
refs/pull/<番号>/headやrefs/pull/<番号>/merge- フォークの head / merge に解決されるコミット SHA
workflow_run は、元のイベントが pull_request* のときだけ対象になります。
バックポートについては、@v4 のようにメジャータグで参照していれば自動で対象になります。SHA やマイナー(@v4.3)、パッチ(@v4.3.1)のタグで固定しているワークフローは更新が必要です。拒否を外したい場合は、actions/checkout の with: に allow-unsafe-pr-checkout: true を指定します。
actions/checkout のリリース一覧を 2026 年 10 月 30 日に確認したところ、2026 年 7 月 20 日に v2.8.0 / v3.7.0 / v4.4.0 / v5.1.0 / v6.1.0 が公開されていました。5 つのリリースノートには、いずれも「[BREAKING] backport allow-unsafe-pr-checkout to v4」(v4 の場合)のような記載があります。git ls-remote --tags https://github.com/actions/checkout で見ると、メジャータグの v2〜v6 はそれぞれこれらのバージョンと同じコミットを指し、v1 タグは v1.2.0 と同じコミットのままでした。v7 系では、同じ日に v7.0.1 が出ています。Wiz の記事も、バックポートが入った最低バージョンを v2.8.0 / v3.7.0 / v4.4.0 / v5.1.0 / v6.1.0 としています。
デフォルトルールが届く範囲
Workflow Execution Protections のデフォルトルールは、イベントポリシーが設定されていないパブリックリポジトリで pull_request_target を止めます。プライベートとインターナルのリポジトリには適用されず、すでに設定してあるイベントポリシーを置き換えることもありません。12 月 2 日に強制が始まるのは、GA の前から pull_request_target のイベントポリシーを設定していなかったリポジトリです。pull_request_target が必要な場合は、イベントポリシーで明示的に許可できます。その際、ワークフローファイル単位で対象を絞れます。
それでも残る経路
Wiz は、上の対策の後でも成立する経路を整理しています。以下の 4 つは、GitHub のドキュメントでも同じ説明を確認できたものです。
次の図は、Wiz の記事と GitHub のドキュメントをもとに自作した概念図で、公式の構成図ではありません。特権のあるトリガーごとに、actions/checkout の修正で止まる経路と止まらない経路を分けています。後述のセルフホストランナーの残留物は、図から省いています。
1. actions/checkout 以外で取得する
v7 の拒否は、actions/checkout の中で行われる検査です。6 月 18 日の checkout changelog は、run: の中で git や gh CLI を使って PR のコードを取得する場合と、無関係な第三者のリポジトリを取得する場合は拒否の対象外だと明記しています。ドキュメントはこれに加えて、ダウンロードしたアーティファクトも対象外だとしています。古いバージョンを SHA で固定した actions/checkout と、allow-unsafe-pr-checkout: true を付けた取得も拒否されません。
2. 対象外のトリガーで PR のコードを動かす
checkout が拒否する対象は pull_request_target と PR 起点の workflow_run だけで、デフォルトルールの対象は pull_request_target だけです。checkout の changelog には、issue_comment のような他のイベントでの pwn request は止まらないと書かれています。ドキュメントも、フォーク PR のコードを取得して実行する issue_comment のワークフローは同じように脆弱だとしています。Wiz は /deploy のようなボットコマンドと repository_dispatch を例に挙げています。
3. PR の文字列がシェルの構文になる(script injection)
コードを取得しなくても成立する経路です。${{ github.event.pull_request.title }} のような式は、シェルスクリプトが実行される前に、文字列のまま埋め込まれます。PR のタイトルやブランチ名は PR を出した人が自由に決められるので、run: に直接書くと、その文字列がコマンドの一部として解釈されます。この経路は checkout の拒否やデフォルトルールとは関係がないので、2026 年の対策の後も残ります。
4. アーティファクトとセルフホストランナーに残るもの
秘密情報を持たない pull_request のワークフローで、PR 側のコードをビルドしたとします。その成果物を、特権のある workflow_run のワークフローがダウンロードして実行すると、PR 側のコードが秘密情報や書き込み権限のある環境で動きます。GitHub のドキュメントは、他のワークフローがアップロードしたアーティファクトを、workflow_run のワークフローでは未信頼のデータとして扱うよう案内しています。
Wiz は、ジョブのたびに作り直さないセルフホストランナーの問題も挙げています。Wiz によると、~/.npm や ~/.cache/pip がジョブ間に残るため、未信頼のジョブから、リリースや公開を行うジョブの依存を汚染できます。GitHub のドキュメントも、パブリックリポジトリでは原則としてセルフホストランナーを使わないよう案内しています。
よくある誤解:承認設定では pull_request_target は止まらない
外部コントリビューターのワークフロー実行に承認を求める設定(Require approval for all external contributors)があります。Wiz は、この設定で pull_request_target のワークフローも承認待ちになるという考えを、よくある誤解として挙げています。GitHub のリポジトリ設定のドキュメントにも、pull_request_target のワークフローはベースブランチの文脈で動くため、承認設定に関係なく常に実行されると書かれています。
自分のワークフローが影響を受けるか確かめる
ここで紹介するコマンドは Wiz の記事に載っているものです。git init しただけのローカルリポジトリにダミーのワークフローファイルを 6 本置き、2026 年 10 月 30 日に実行しました。環境は macOS 26.6.1(BSD grep 2.6.0、bash 5.3.20、git 2.56.0)です。スクリーンショットは同じコマンドを Linux のコンテナ(GNU grep 3.11)で実行して撮影したもので、grep の結果は macOS と同じでした。ダミーのファイルは GitHub には push しておらず、ワークフローとしては動かしていません。
特権のあるトリガーと危険な書き方を洗い出す
Wiz の記事には、.github/workflows/ を対象にした次の grep が載っています。危険なトリガー、actions/checkout 以外の取得、古い actions/checkout のタグ固定、アーティファクトのダウンロード、セルフホストランナー、${{ github.event. や github.head_ref の展開を一度に洗い出します。
grep -rnE 'pull_request_target|workflow_run|issue_comment|repository_dispatch|allow-unsafe-pr-checkout|self-hosted|download-artifact|gh (pr checkout|repo clone)|git (clone|fetch)|refs/pull|actions/checkout@v(1|2\.[0-7]|3\.[0-6]|4\.[0-3]|5\.0|6\.0)([^0-9]|$)|\$\{\{[[:space:]]*github\.(event\.|head_ref)' .github/workflows/
ダミーのワークフロー 6 本に対して実行すると、19 行がヒットしました。トリガー、gh pr checkout や git fetch、self-hosted、download-artifact、allow-unsafe-pr-checkout を含む行に加えて、@v1、@v2.7.0、@v3.6.0、@v4.3.1、@v5.0.1、@v6.0.3 のタグ固定を拾いました。@v4、@v4.4.0、@v7、@v2.8.0、@v6.1.0 は拾いませんでした。次の画像は、そのうち issue_comment と PR タイトルを扱う 2 本のファイルに絞った結果です。

ヒットしたら、そのワークフローが PR 側のコードや文字列を実行していないかを目で確かめます。画像のとおり、env: の値として書いた TITLE: ${{ github.event.pull_request.title }} の行も、run: に直接書いた行と同じようにヒットします。Wiz の記事でも env: の値として書くのは安全な書き方とされているので、それ以外の行を中心に見ます(理由は後述の「未信頼の文字列は環境変数で渡す」で説明します)。また、コメント行に書いた pull_request_target もヒットしました。Wiz は静的検査ツールとして、オープンソースの zizmor も挙げています(今回は zizmor を実行していません)。
なお、この grep は SHA で固定した actions/checkout を拾いません。SHA 固定は次のスクリプトで確かめます。
SHA で固定した actions/checkout のバージョンを確かめる
SHA で固定している場合、その SHA がどのリリースかを確かめる必要があります。Wiz の記事にある次のスクリプトは、ワークフロー内の actions/checkout@<40桁の SHA> を actions/checkout のリリースタグに対応づけます。結果のタグが v7.0.0 以上か、バックポート済みのバージョン以上かを見ます。このスクリプトは git ls-remote で GitHub からタグ一覧を取得するので、ネットワークが必要です。
tags=$(git ls-remote --tags https://github.com/actions/checkout)
grep -rhoE "actions/checkout@[0-9a-f]{40}" .github/workflows/ | cut -d@ -f2 | sort -u |
while read -r sha; do
tag=$(echo "$tags" | grep "^$sha" | sed 's#.*refs/tags/##; s#\^{}$##' | sort -V | tail -1)
echo "$sha ${tag:-UNKNOWN - not a release tag, check manually}"
done
ダミーのワークフローに、v4.3.0、v4.4.0、v7.0.1、v1.2.0、v6.0.3 のコミットの SHA と、存在しない SHA を 1 つ書いて実行した結果です。

v4 と v4.4.0 のように同じコミットを指すタグが複数ある SHA では、sort -V で後ろに来る v4.4.0 が表示されました。この結果なら、v4.3.0、v1.2.0、v6.0.3 の 3 件が更新の対象です。UNKNOWN になった SHA は、リリースタグ以外のコミットか、actions/checkout 以外の SHA の可能性があるので、個別に確かめます。
12 月 2 日に止まるワークフローを確かめる
9 月 17 日の changelog は、デフォルトルールの評価モードの結果を Insights で確認し、強制が始まると失敗する実行を事前に把握するよう案内しています。ワークフロー実行の制御のドキュメントによると、リポジトリの Settings を開くと、左サイドバーの Actions > Policies の直下に Policy insights のページがあります。なお、同じドキュメントでは、自分で作るポリシーの評価モードは GitHub Enterprise Cloud だけの機能とされていますが、これは自分で作るポリシーについての記載で、デフォルトルールの評価結果がどう見えるかは書かれていません。
2026 年 10 月 30 日時点では Policy insights の画面を開いていないため、GitHub Free プランのパブリックリポジトリでデフォルトルールの評価結果を見られるかは確かめていません。参考までに、同じ条件の検証用パブリックリポジトリで REST API のポリシー一覧(GET /repos/{owner}/{repo}/actions/policies)を取得したところ、結果は空で、デフォルトルールは一覧に出ませんでした。デフォルトルールがこの API に出ない仕様なのか、このリポジトリが対象外なのかは切り分けておらず、今後の更新で API の返す内容が変わる可能性もあります。API の一覧だけでは影響の有無を判断できないので、Policy insights の画面で確かめるのが確実です。画面で評価結果を見られない場合は、上の grep で pull_request_target を使うワークフローを洗い出しておきます。
守り方:トリガー・checkout・入力・ランナーを見直す
トリガーを選び直す
秘密情報が不要なワークフローは pull_request に移すのが基本です。PR の結果を使って特権が必要な処理(ラベル付けやコメント投稿など)をしたい場合は、pull_request で PR を処理し、後処理を workflow_run に分けます。このとき、PR 側のアーティファクトは実行せず、データとして読むだけにします。
actions/checkout を更新し、例外を監査する
actions/checkout は v7 以上か、バックポート済みのバージョン以上にします。allow-unsafe-pr-checkout を使っている箇所は、取得したコードを実行していないかを確かめ、変更のたびに見直します。GitHub のドキュメントも、取得したコードが決して実行されないことを確認した場合だけ使うよう案内しています。
未信頼の文字列は環境変数で渡す
GitHub のセキュリティ強化のドキュメントは、式の値を中間の環境変数に入れてからスクリプトで使う方法を推奨しています。次は、ドキュメントの例から step の部分を抜き出し、読みやすく字下げし直したものです。
- name: Check PR title
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
if [[ "$TITLE" =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
exit 0
else
echo "PR title did not start with 'octocat'"
exit 1
fi
値はシェルの変数で渡るので、タイトルの中身はコマンドとして解釈されません。シェル側では "$TITLE" のようにダブルクォートで囲みます。
コメント起点のワークフローに条件を付ける
Wiz は、issue_comment や repository_dispatch について次の対策を推奨しています。
- コメントした人の実際の権限(write 以上)を確かめる。
author_associationは現在の権限を反映しないので頼らない - コメント時点の head のコミットに固定し、その後に PR が更新されていたら中止する
- コメントの引数や
repository_dispatchのペイロードは、アンカー付きの許可リストの正規表現で検証し、${{ }}で埋め込まず環境変数で渡す
特権ジョブとランナーを分ける
Wiz は、特権のあるジョブをレビュー必須の environment や、Workflow Execution Protections のアクタールール(実行した人を条件に止めるルール)で制限するよう推奨しています。また、未信頼のイベントで動くワークフローをエフェメラルなランナー(ジョブごとに作り直すランナー)で動かし、信頼できるワークフローとはランナーグループを分けることも挙げています。
12 月 2 日までにイベントポリシーを決める
パブリックリポジトリで pull_request_target を使い続けるなら、影響を確かめたうえで、必要なワークフローだけをイベントポリシーで許可します。使っていないならデフォルトルールのままで構いません。プライベートとインターナルのリポジトリでは許可制にならないので、actions/checkout の更新とトリガーの見直しがより重要になります。
検知の手がかり
- フォーク PR を起点にした
pull_request_target、workflow_run、issue_commentの実行ログで、どのコミットを取得して何を実行したかを確認する
まとめ
- 2026 年の GitHub の対策により、
pull_request_targetの中でactions/checkoutを使ってフォーク PR のコードを取得する経路は、actions/checkoutをメジャータグか修正済みバージョン以上で参照していれば塞がれるようになりました。イベントポリシーを設定していないパブリックリポジトリでは、2026 年 12 月 2 日からpull_request_targetが許可制になります。 - それでも、
gitやghでの取得、古い SHA 固定やallow-unsafe-pr-checkout、issue_commentやrepository_dispatch、script injection、workflow_runのアーティファクト、セルフホストランナーの残留物は対策の外にあります。また、プライベートとインターナルのリポジトリにはデフォルトルールが適用されません。 - まずは
.github/workflows/の特権トリガーとactions/checkoutのバージョンを洗い出し、秘密情報が不要なものはpull_requestに移します。 - パブリックリポジトリでは、12 月 2 日までに
pull_request_targetに依存するワークフローを Policy insights(見られない場合は grep の結果)で確かめ、必要なものだけイベントポリシーで許可します。
誰かの参考になれば幸いです。
参考リンク:
- The Pwn Request Reckoning: What Still Works Against GitHub Actions(Wiz)
- Safer pull_request_target defaults for GitHub Actions checkout(GitHub Changelog)
- actions/checkout のリリース一覧(GitHub)
- Workflow execution protections in GitHub Actions generally available(GitHub Changelog)
- Securely using pull_request_target(GitHub Docs)
- Secure use reference(GitHub Docs)
- Managing GitHub Actions settings for a repository(GitHub Docs)
- Control workflow execution(GitHub Docs)






