actions/checkout v7入門——pull_request_targetの危険なcheckoutを止める
GitHub Actionsのactions/checkout v7を題材に、pull_request_targetで未信頼コードを特権付きで実行しないCI設計を、Next.jsとFirestoreの監査例で解説します。

はじめに
GitHub ActionsでPR自動化を作っていて、pull_request_targetの中でfork PRのhead SHAをcheckoutしていませんか?このイベントはbase repositoryのGITHUB_TOKENやsecretsを使えるため、レビュー前のコードを特権付きrunnerで実行すると事故につながります。
2026年6月18日、GitHubは actions/checkout v7 を一般提供し、pull_request_targetや一部のworkflow_runでfork PRのコードを危険な形でcheckoutするパターンをデフォルトで拒否するようにしました。GitHubはこの種の事故を「pwn request」と呼び、サプライチェーン攻撃の原因になってきたと説明しています。
本記事では、架空の請求管理SaaS「BillPort」を題材に、Next.js + Firestore + GitHub Actions + TypeScriptの現場で、PR自動化を止めずに安全側へ寄せる手順を整理します。
何が変わったのか
pull_request_targetは、PR元ではなくbase repository側のworkflow定義で動くイベントです。ラベル付けやコメント投稿のようにPRへ書き戻したい場面では便利ですが、base repositoryのGITHUB_TOKEN、secrets、default branchのcacheなどにアクセスできるため、fork PRの未信頼コードをそのまま実行すると危険です。
actions/checkout v7では、fork PRから来たコードに対して、次のような入力を使う危険パターンが拒否されます。
| 入力例 | なぜ危険か |
|---|---|
ref: ${{ github.event.pull_request.head.sha }} | fork側の未レビューcommitを特権付きjobに取り込む |
ref: refs/pull/${{ github.event.pull_request.number }}/merge | PRのmerge refを特権付きcontextで実行する |
repository: ${{ github.event.pull_request.head.repo.full_name }} | fork repositoryそのものをcheckoutする |
GitHubの発表では、2026年7月16日にサポート中のmajor versionへもこの保護をbackportするとされています。actions/checkout@v4のような浮動major tagは自動的に影響を受けますが、SHAやminorへ固定しているworkflowは自分たちで更新が必要です。
ハンズオン1: 危険なworkflowを探す
まず、BillPortの.github/workflowsから危険候補を洗い出します。workflow_runやissue_commentからPRコードを手動で取りに行く処理も見ます。
rg "pull_request_target|workflow_run|issue_comment|pull_request.head|refs/pull|allow-unsafe-pr-checkout" .github/workflows
見つかったworkflowは、次の表で分類します。
| 分類 | 対応 |
|---|---|
| PRメタデータだけを読む | pull_request_targetのままでもよいがcheckoutしない |
| base branchのコードだけ使う | actions/checkout@v7で通常checkoutする |
| fork PRのコードを実行する | pull_requestへ移す |
| secretsも必要 | 環境を分離し、明示的なレビュー後に実行する |
actions/checkout v7は万能ではありません。runブロック内でgit fetchやgh pr checkoutを使ってPR headを取得する処理は、checkout actionの保護範囲外としてレビューします。
ハンズオン2: PR検証とPR操作を分ける
BillPortでは、Next.jsのビルドとFirestore Emulatorを使うテストはpull_requestで実行し、PRへのコメント投稿だけをpull_request_targetへ残します。
name: PR Quality Gate
on:
pull_request:
branches: [main]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
- run: yarn install --frozen-lockfile
- run: yarn build
PRへコメントするworkflowは、base repositoryのworkflowだけをcheckoutし、PR headはcheckoutしません。
name: PR Metadata Comment
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: echo "PRメタデータだけを扱い、PR headはcheckoutしない"
この分離により、未レビューのコードを実行するjobにはsecretsや書き込み権限を渡さず、書き込み権限が必要なjobでは未レビューコードを実行しない、という設計になります。
ハンズオン3: Firestoreへ監査結果を保存する
CIの安全性は一度直して終わりではありません。AIエージェントやbotがworkflowを追加する運用では、危険な入力が戻ってこないかを継続的に見ます。BillPortでは、監査結果をFirestoreのciWorkflowAuditsへ保存します。
// src/app/api/internal/ci-workflow-audits/route.ts
import { initializeApp, getApps } from "firebase-admin/app";
import { FieldValue, getFirestore } from "firebase-admin/firestore";
import { NextResponse } from "next/server";
if (getApps().length === 0) initializeApp();
const db = getFirestore();
type WorkflowFinding = {
file: string;
risk: "pull_request_target" | "unsafe_pr_ref" | "manual_git_fetch";
line: string;
};
export async function POST(request: Request) {
const { sha, findings } = (await request.json()) as {
sha: string;
findings: WorkflowFinding[];
};
if (!sha || !Array.isArray(findings)) {
return NextResponse.json({ error: "invalid payload" }, { status: 400 });
}
await db.collection("ciWorkflowAudits").add({
sha,
findings,
findingCount: findings.length,
createdAt: FieldValue.serverTimestamp(),
});
return NextResponse.json({ ok: true });
}
監査jobは、まずテキスト検出で十分です。
name: Audit workflow checkout safety
on:
schedule:
- cron: "0 23 * * *" # 08:00 JST
workflow_dispatch:
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Detect risky workflow patterns
run: |
rg -n "pull_request_target|pull_request.head|refs/pull|gh pr checkout|git fetch" .github/workflows || true
- name: Fail when unsafe opt-out is present
run: |
if rg "allow-unsafe-pr-checkout:\\s*true" .github/workflows; then
echo "allow-unsafe-pr-checkout requires security review"
exit 1
fi
allow-unsafe-pr-checkout: trueは実在するactions/checkout v7の入力ですが、名前の通り危険を理解した上で使うための明示的なopt-outです。使う場合は、runner network、キャッシュ、成果物の扱いまでレビュー対象にします。
運用で見るべきポイント
actions/checkout v7への更新は、単なるバージョン上げではありません。CIを次の3層に分けるきっかけにすると効果が出ます。
| 層 | 例 | 権限 |
|---|---|---|
| 未信頼コード検証 | pull_requestでyarn build、型チェック、テスト | contents: read中心 |
| PR操作 | ラベル付け、コメント、変更ファイル一覧の確認 | pull-requests: writeなど必要最小限 |
| デプロイ・外部連携 | Firebase Hosting、Firestore本番、外部API | protected environmentと承認を必須にする |
Next.js + Firestoreのプロジェクトでは、PR検証で本番Firestoreに接続しない設計も重要です。Firestore RulesやEmulatorを使うテストはPR側で実行し、本番データを使う確認はworkflow_dispatchとprotected environmentに分離します。
参考リンク
- Safer pull_request_target defaults for GitHub Actions checkout
- actions/checkout README
- actions/checkout action.yml
- GitHub Actions secure use reference
まとめ
actions/checkout v7は、pull_request_targetの典型的な危険checkoutをデフォルトで止める重要な更新です。ただし、保護されるのはcheckout actionで検出できる範囲に限られます。
実務では、PRのコードを検証するworkflowと、PRへ書き戻すworkflowを分けることが基本です。さらにFirestoreへ監査結果を残しておくと、AIエージェントやbotがworkflowを増やすチームでも、危険な設定が戻ってきたタイミングを追跡できます。まずは.github/workflowsを棚卸しし、actions/checkout@v7へ上げるところから始めましょう。