Why this was a real vulnerability, not a theoretical one
pull_request_target exists so that a workflow can run with the base repository’s full secrets and write permissions even when triggered by a fork’s pull request, useful for things like commenting on the PR or triggering a deploy preview. The catch: many workflows using that trigger also checked out the fork’s own head commit, because that’s the code the PR is actually proposing to merge.
Put those two facts together, and you get the “pwn request” pattern: a workflow with real secrets, running arbitrary code from someone who doesn’t have write access to your repo. An attacker opens a PR from a fork, and any workflow step that executes something from the checked-out fork code (a build script, a test runner, a linter with a plugin system) runs with the base repo’s permissions.
actions/checkout v7.0.0 (released 2026-06-18) closes this by refusing to fetch the fork’s code in that specific context. The action now fails on patterns like these when it detects a pull_request_target or PR-flavored workflow_run event:
# actions/checkout v7 blocks all three of these inside
# pull_request_target and PR-flavored workflow_run:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}The fix landed as a major release first, then GitHub backported it to every older supported major version on 2026-07-20: the v6 line got v6.1.0, v5 got v5.1.0, v4 got v4.4.0, v3 got v3.7.0, and v2 got v2.8.0, each shipping the same enforcement that same day. Workflows pinned to a floating major tag like @v4 picked up the change automatically the next time that job ran.
This doesn't cover every pwn-request vector
Blocking the fork-checkout shortcut closes the most common path, but it isn’t
a complete fix for pull_request_target misuse. A workflow that manually runs
git fetch against the fork, or checks out an unrelated third-party
repository and executes something from it, sits outside what this specific
change catches. Review what a pull_request_target workflow actually
executes, not just how it checks out code.
Structural Comparison Matrix
| Operational Aspect | Before (unpatched) | After (v7.0.0 / backported) |
|---|---|---|
Fork PR ref in pull_request_target | Checked out normally, no warning | Action fails by default |
Fork PR ref in PR-flavored workflow_run | Checked out normally, no warning | Action fails by default |
Floating tag pin (@v4) | Vulnerable until GitHub patched it | Fixed automatically once backport landed |
| Exact version/SHA pin below the fix | Vulnerable, stays vulnerable indefinitely | Still vulnerable until you bump the pin yourself |
| Genuinely intended unsafe checkout | No opt-in required, no audit trail | Requires explicit allow-unsafe-pr-checkout: true |
Fixing a workflow that hits this
If a pull_request_target or workflow_run job in your repo starts failing at the checkout step after 2026-07-20, work through this in order.
-
Check what version you’re actually pinned to. Open the workflow file and look at the
uses:line for every checkout step in apull_request_targetorworkflow_runjob..github/workflows/preview.yml- uses: actions/checkout@v4.2.0 # exact pin, below the 4.4.0 fix with: ref: ${{ github.event.pull_request.head.sha }} -
Bump the pin to the fixed release for your major version: v7.0.0 if you’re on the v7 line, or the matching backport otherwise (v6 got 6.1.0, v5 got 5.1.0, v4 got 4.4.0, v3 got 3.7.0, v2 got 2.8.0).
.github/workflows/preview.yml- uses: actions/checkout@v4.4.0 # fixed with: ref: ${{ github.event.pull_request.head.sha }} -
Confirm the job still fails after the bump. If it does, that’s the intended behavior: your workflow genuinely was checking out fork code under
pull_request_target, and the failure is GitHub asking you to look at it again, not a bug. -
Redesign around the safer pattern if you can. Most
pull_request_targetuse cases (posting a comment, labeling a PR, triggering a status check) don’t need the fork’s code at all. Split the job: run the untrusted build/test steps under plainpull_request(no secrets, no write access), and use a separatepull_request_targetjob only for the trusted, secrets-requiring step that reads the PR’s metadata rather than its code. -
Only if you’ve reviewed it and it’s genuinely necessary, opt back in explicitly:
.github/workflows/preview.yml- uses: actions/checkout@v4.4.0 with: ref: ${{ github.event.pull_request.head.sha }} allow-unsafe-pr-checkout: true
Does this affect this site’s own deploy workflow?
This site’s CI, .github/workflows/ci.yml, pins actions/checkout@v7 already and only triggers on push to main and same-repo pull_request events, never pull_request_target or a fork-originated workflow_run. That specific attack surface doesn’t exist here. Worth saying plainly rather than implying a dogfooding story that isn’t real: this repo has nothing to fix for this particular change, because it was never exposed to the pattern it blocks.
Restricting which triggers and which people can even fire a workflow in the first place is a related, complementary control: see Set Up GitHub Actions Workflow Execution Protections for the allow-list layer that sits in front of the checkout step covered here.
If you’re deploying this exact kind of static site through GitHub Actions rather than Cloudflare’s built-in Git integration, Cloudflare Workers Deploys: Built-In Git vs. GitHub Actions covers that pipeline choice, including the actions/checkout step in a from-scratch deploy workflow.
Source
GitHub’s own changelog entry announcing this checkout change (2026-06-18, enforcement backported 2026-07-20), corroborated against the checkout action’s release history on GitHub (major release plus five backported patch versions, all published 2026-07-20). Browse more posts like this in the Dev Tools archive.







