actions/checkout finally says no to pwn requests
Fork checkouts in privileged workflows now fail by default, and the September 23 Node 20 cutoff makes upgrading mandatory anyway.
The most boring line in every GitHub workflow is uses: actions/checkout. It's on the trending list this week for two reasons that happen to collide in the same month. The first is that the checkout action changed what it's willing to do by default, and that change reached every supported major version on July 20. The second is that GitHub just moved its Node 20 removal date to September 23, which means any action still declaring node20 has four weeks to live. If you've been ignoring the deprecation warnings in your logs since June, this is the month they stop being warnings.
What v7 refuses to do
A "pwn request" is the oldest footgun in Actions. A workflow triggered by pull_request_target runs with the base repository's secrets and a write-capable GITHUB_TOKEN. If that workflow then checks out the pull request's head and runs anything from it (npm install, a lint step, a test suite), a fork author has code execution inside your privileged context. Jaroslav Lobačevski at GitHub Security Lab wrote the canonical warning about this in August 2021. The docs page on securely using pull_request_target has said "don't do this" ever since. People kept doing it, because the action let them.
checkout v7, released June 18, doesn't let them. When the event is pull_request_target, or workflow_run chained off a pull_request* event, and the PR comes from a fork, the action fails if repository: resolves to the fork or ref: points at refs/pull/N/head, refs/pull/N/merge, or either of those commit SHAs. Same-repo PRs and plain pull_request triggers are untouched.
The escape hatch is an input called allow-unsafe-pr-checkout: true. GitHub says the name is deliberate: it's meant to jump out in code review and light up in static analysis. That's the right design. A vague trust-forks flag would've been copy-pasted from Stack Overflow within a week.
The part that catches teams off guard is the backport. On July 20 the same check landed in v2 through v6. If your workflow says @v4 and relies on the blocked pattern, it started failing five weeks ago without you touching a file. Only SHA-pinned workflows are still running the old behavior, and those are the ones GitHub wants you to upgrade by hand.
Three majors in eleven months
This is the third major version of checkout since August 2025, which is unusual for an action that went years between bumps. Each one moved a floor.
v5 (August 2025) switched the runtime from Node 20 to Node 24 and set the minimum runner at 2.327.1. Issue #2240 on the repo, filed the day it shipped, shows what that looked like on stale self-hosted fleets: 'using: node24' is not supported, use 'docker', 'node12', 'node16' or 'node20' instead. Every workflow on the affected runners died at step one until someone either pinned back to v4 or updated the runner binary.
v6 (November 2025) moved the persisted GITHUB_TOKEN out of .git/config and into a separate file under RUNNER_TEMP, wired in through includeIf.gitdir. That closed a leak William Woodruff wrote up in 2024 after a Palo Alto Networks team found dozens of projects tarring their checked-out repo into an artifact, token included. Container actions need runner 2.329.0 or later to see the relocated credentials.
v7 is the behavioral change above, plus a rewrite to ESM so the action can track current @actions/* packages.
Read together, the pattern is clear. GitHub spent 2021 through 2025 documenting how to use Actions safely and watched the advice fail to propagate. The March 2025 tj-actions/changed-files compromise hit a dependency used by over 23,000 repositories. The March 2026 Trivy incident force-pushed 76 of 77 trivy-action tags to a credential stealer. Neither of those was a pwn request; both were tag hijacks. But they made the case that defaults, not docs, are what move the number. GitHub's July 28 post on disrupting supply chain attacks lists the checkout change alongside workflow execution policies and read-only caches for untrusted triggers as one program: make the safe thing the default and make the unsafe thing loud.
What to change this week
Start by finding out whether you're exposed:
grep -rlE 'pull_request_target|workflow_run' .github/workflows/
For each hit, decide which of three buckets it belongs in.
If the workflow only needs to read the PR and doesn't need secrets or write access, switch the trigger to pull_request. This is most of them. Fork PRs get a read-only token and no secrets, and the checkout works as before.
If it needs both untrusted code and privileges (the classic case is "run the tests, then comment the results"), split it. A pull_request workflow runs the code and uploads a result artifact. A workflow_run workflow, triggered by the first one, downloads that artifact and posts the comment. It never checks out the fork. The second workflow's checkout is of your default branch, which v7 allows.
If you've convinced yourself the checked-out code is only ever read as data and never executed, add the flag:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha }}
allow-unsafe-pr-checkout: true
persist-credentials: false
Be honest with yourself about "never executed." A package.json with a postinstall script counts. So does a .pre-commit-config.yaml a linter picks up, or a Makefile a build step includes.
While you're in the file, set persist-credentials: false on every checkout that doesn't push. v6 made the token harder to leak by accident, but it's still readable by every later step on the runner, and a compromised third-party action in your job can read RUNNER_TEMP as easily as .git/config. Pin third-party actions to full commit SHAs; the Trivy attack only worked because tags are mutable.
Then deal with the runtime. Runners have defaulted to Node 24 since June 16. ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true keeps node20 actions alive until September 23 and then stops working, because Node 20 is being deleted from the runner image. Upgrade self-hosted runners to at least 2.329.0 now, and open issues on any action you depend on that still declares node20 in its action.yml.
What it doesn't fix
Be clear about the scope. The block only fires inside the checkout action. A run: step that calls gh pr checkout or git fetch origin pull/N/head in a pull_request_target job is just as exploitable and just as allowed. Checking out an unrelated third-party repository isn't covered. Neither are other privileged triggers like issue_comment, where a /deploy comment kicks off a workflow that then fetches the PR branch. GitHub says as much in the changelog.
So this is a real reduction in the most common pattern, and only that one. My read is that it's the single best default change Actions has shipped, five years late, and the backport to every supported major is the part that made it matter. The people who'll complain are the ones whose @v4 workflows broke in July. They were the reason for the change.
Sources & further reading
- actions/checkout — github.com
- Safer pull_request_target defaults for GitHub Actions checkout — github.blog
- GitHub Updates actions/checkout to Block Common Pwn Request Attack Patterns — thehackernews.com
- Deprecation of Node 20 on GitHub Actions runners — github.blog
- Releases - actions/checkout — github.com
- Breaking change with v5 release - 'using: node24' is not supported — github.com
- Persist creds to a separate file — github.com
- Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests — securitylab.github.com
Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.
Discussion 2
finally, no more accidentally pulling in malicious code from fork PRs. been burned by that before.
yeah, but now i need to check every workflow that was relying on the old behavior. at least this forces the conversation about what credentials actually need to be exposed where.