GitHub Actions Checkout v7: Fixing the Pwn Request Vulnerability Before July 16

GitHub Actions Checkout v7: Fixing the Pwn Request Vulnerability Before July 16

If your CI pipeline uses pull_request_target and checks out the pull request's head commit, actions/checkout@v7 will now refuse to do it. That's not a bug โ€” it's GitHub closing one of the most common CI/CD attack patterns in the ecosystem, known as a "pwn request." The change shipped June 18, 2026, and on July 16, 2026 the same enforcement gets backported to every currently supported major version of the action (v3 through v6), so pinning to an older tag won't save you for long.

If you maintain workflows that touch fork pull requests, you need to know whether you're affected before that date.

Why pull_request_target workflows are dangerous

pull_request_target exists so that automation (auto-labeling, welcome comments, stale-PR bots) can run with access to the base repository's secrets and a writable GITHUB_TOKEN, even when triggered by a pull request from a fork. Unlike the pull_request event, the workflow definition itself is always pulled from the default branch โ€” not from the fork โ€” so in theory an attacker can't just rewrite your workflow file to do something malicious.

The vulnerability shows up when someone adds a checkout step that pulls in the fork's actual code:

 1# INSECURE โ€” do not use this pattern
 2on:
 3  pull_request_target:
 4
 5jobs:
 6  build:
 7    runs-on: ubuntu-latest
 8    steps:
 9      - uses: actions/checkout@v6
10        with:
11          ref: ${{ github.event.pull_request.head.sha }}
12      - run: make test   # attacker's Makefile now runs with your secrets

The workflow YAML is trusted, but the checked-out repository content is not. Once that untrusted code executes โ€” a Makefile, an npm install with a malicious postinstall script, a build tool's config file โ€” it runs with the full privileges of the job: your GITHUB_TOKEN, any secrets exposed to the workflow, and (for public repos) the default-branch dependency cache. This exact pattern has been behind several real supply-chain compromises, which is why GitHub's security team calls it a "pwn request."

What changed in actions/checkout v7

Starting with v7 (June 18, 2026), actions/checkout detects when it's running inside a pull_request_target workflow, or a workflow_run triggered by a pull request event, and refuses to fetch the fork's PR head. It fails the step outright if you try any of these common insecure patterns:

 1# All three of these now fail on actions/checkout@v7
 2- uses: actions/checkout@v7
 3  with:
 4    ref: refs/pull/${{ github.event.pull_request.number }}/merge
 5
 6- uses: actions/checkout@v7
 7  with:
 8    ref: ${{ github.event.pull_request.head.sha }}
 9
10- uses: actions/checkout@v7
11  with:
12    repository: ${{ github.event.pull_request.head.repo.full_name }}

The rollout timeline matters for planning:

DateWhat happens
June 18, 2026v7 released with enforcement on by default
July 16, 2026Enforcement backported to all supported major versions (v3โ€“v6)
OngoingFloating tags like @v4 pick up the change automatically; only SHA-pinned versions stay on old behavior

If your workflows pin actions/checkout to a specific commit SHA rather than a major-version tag, you won't get this protection (or the breakage) automatically โ€” which cuts both ways: you're not protected until you upgrade, but you also won't get surprised mid-sprint by a floating tag suddenly failing your CI.

How to check if you're affected

Search your workflows for the risky combination:

1grep -rl "pull_request_target" .github/workflows/

For each match, check whether the same job also checks out the PR head:

1grep -A5 "actions/checkout" .github/workflows/*.yml | grep -E "head.sha|pull/.*merge|head.repo"

If nothing matches, you're likely fine. If something does, you have a workflow that needs one of the fixes below before July 16.

Migration paths

1. Switch to pull_request if you don't need secrets. Most CI jobs โ€” running tests, linting, building โ€” don't actually need write access to the base repo or secret access. pull_request runs with a read-only, fork-scoped token and checks out fork code safely by default:

1on:
2  pull_request:
3
4jobs:
5  test:
6    runs-on: ubuntu-latest
7    steps:
8      - uses: actions/checkout@v7
9      - run: make test

This is the fix for the overwhelming majority of workflows that were using pull_request_target incorrectly just to "make checkout work."

2. Split trusted and untrusted work into two workflows. If you genuinely need pull_request_target (e.g., to comment on the PR with results, or to access a deployment secret), separate the untrusted build/test step from the privileged step. Run untrusted code in a pull_request-triggered workflow with no secrets, upload the results as an artifact, then consume that artifact in a second workflow_run-triggered job that has secrets but never executes fork code:

 1# .github/workflows/test.yml โ€” no secrets, safe to run untrusted code
 2on:
 3  pull_request:
 4jobs:
 5  test:
 6    runs-on: ubuntu-latest
 7    steps:
 8      - uses: actions/checkout@v7
 9      - run: npm test -- --json > results.json
10      - uses: actions/upload-artifact@v4
11        with:
12          name: test-results
13          path: results.json
 1# .github/workflows/comment.yml โ€” has secrets, never checks out fork code
 2on:
 3  workflow_run:
 4    workflows: ["test"]
 5    types: [completed]
 6jobs:
 7  comment:
 8    runs-on: ubuntu-latest
 9    steps:
10      - uses: actions/download-artifact@v4
11        with:
12          name: test-results
13          run-id: ${{ github.event.workflow_run.id }}
14          github-token: ${{ secrets.GITHUB_TOKEN }}
15      - run: |
16          # parse results.json as data only โ€” never execute it
17          echo "Posting results comment using the GITHUB_TOKEN"          

3. Explicitly opt out โ€” only if you've confirmed it's safe. If you've reviewed the workflow and are certain checking out fork code is intentional and controlled (for example, a maintainer-only repo where you trust every contributor, or a workflow that only reads files as data and never executes them), you can opt back in:

1- uses: actions/checkout@v7
2  with:
3    ref: ${{ github.event.pull_request.head.sha }}
4    allow-unsafe-pr-checkout: true

The flag name is deliberately loud. It's meant to be impossible to miss in a code review or a grep across your org's workflows, so treat any appearance of allow-unsafe-pr-checkout in a PR as something that needs a second reviewer.

Best practices

  • Default to pull_request for anything that doesn't need secrets. Reach for pull_request_target only when you have a specific, reviewed reason.
  • Never execute anything from checked-out fork code in a privileged context โ€” treat it as data (read files, parse JSON) rather than as code (run, make, npm install with lifecycle scripts, pip install -e).
  • Scope GITHUB_TOKEN permissions explicitly with a permissions: block at the workflow or job level, even after fixing the checkout issue โ€” least privilege is a second layer of defense, not a replacement for this fix.
  • Audit self-hosted runners separately: they don't get the ephemeral isolation of GitHub-hosted runners by default, so a pwn request there can persist beyond a single job.

Common mistakes to avoid

  • Assuming SHA-pinning your action version protects you long-term. It only delays the fix โ€” you're running without the security patch until you upgrade, and you'll eventually need to.
  • Adding allow-unsafe-pr-checkout reflexively to "make CI green again." That defeats the entire point of the change. Fix the workflow instead of silencing the guardrail.
  • Forgetting workflow_run workflows. The same fork-checkout risk applies there when the triggering workflow was a pull request event โ€” teams often audit pull_request_target and miss workflow_run.
  • Trusting pull_request_target because "it's just a label bot." Label/triage bots that later grew a "run tests and post results" step are exactly how this vulnerability class spreads โ€” audit workflows for scope creep, not just their original intent.

Troubleshooting

"My CI suddenly started failing on checkout after July 16 with no code changes." You're almost certainly hitting the backported enforcement on a floating major-version tag (e.g., @v4). Check your checkout step for ref: or repository: pointing at PR head data, and apply one of the migration paths above.

"I need this to keep working exactly as before, right now, while I plan the real fix." Add allow-unsafe-pr-checkout: true as a temporary bridge, but open a tracked issue immediately โ€” this flag is a visible admission of risk, not a fix.

FAQ

Does this affect pull_request workflows too? No. pull_request already checks out fork code safely with a restricted, read-only token by default. This change only targets pull_request_target and pull-request-triggered workflow_run workflows, where the token has broader privileges.

Will my workflow just silently keep working if I do nothing? Only if you're pinned to a specific commit SHA. Any major-version tag (@v3 through @v6) gets the enforcement backported by July 16, 2026, and will start failing checkout steps that use the insecure patterns.

Is this only a GitHub-hosted runner issue? No โ€” the risk is actually worse on self-hosted runners, since a compromised job there can potentially persist or pivot beyond the single workflow run, unlike GitHub's ephemeral hosted runners.

What if I use GitLab CI or another platform โ€” does this apply? The specific fix is GitHub-only, but the underlying vulnerability class (untrusted fork/branch code running in a privileged pipeline context) applies to any CI system that has an equivalent "run with elevated permissions on external contributions" trigger. The mitigation principle โ€” never execute untrusted code in a privileged context โ€” transfers directly.

Key takeaways

IssueFix
pull_request_target + fork PR checkoutSwitch to pull_request if secrets aren't needed
Need secrets and fork PR contentSplit into two workflows: untrusted build โ†’ artifact โ†’ privileged consumer
Confirmed-safe fork checkoutExplicit allow-unsafe-pr-checkout: true, reviewed deliberately
DeadlineBackport enforcement lands July 16, 2026 on all supported major versions

Further Reading