GitHub’s 2026 changes stop most pull_request_target exploits but many gaps remain
The claim
The authors claim that GitHub’s 2026 platform updates largely mitigate pull_request_target attacks but still allow exploitation via explicit opt-ins, SHA-pinned old checkout versions, custom checkout scripts, script injection, unprotected triggers, artifact poisoning, and persistent runner state.
Published as: “The Pwn Request Reckoning: What Still Works Against GitHub Actions”
So what
Defenders must audit workflows for explicit opt-ins, outdated actions, and unsafe input handling despite GitHub’s platform hardening.
The five-minute version
The blog surveys GitHub’s 2026 security rollout that adds default event policies, read-only cache tokens, and back-ported checkout protections for actions/checkout. While these changes block the classic pull_request_target exploit in most public repos, the authors demonstrate several residual attack paths: explicit opt-ins, SHA-pinned legacy checkout versions, custom checkout scripts, script injection via ${{ }} expressions, unprotected event types, artifact poisoning, and stateful self-hosted runners. Their evidence consists of empirical tests on real GitHub workflows and a detailed audit checklist, showing the mitigations are effective but incomplete.
What they found 5
- Read-only cache tokens prevent cache poisoning for untrusted triggers
“June 26, 2026 GitHub introduces read-only cache tokens at the platform level for untrusted triggers, preventing cache poisoning attacks.”
- Default event policy blocks pull_request_target in public repos without explicit allowance
“Starting December 2, the default event policy will block pull_request_target in public repositories that have no explicit event policy.”
- SHA-pinning to commits before the July 20 2026 backport bypasses checkout protections
“Because GitHub's backported protections rely on logic embedded within actions/checkout itself, pinning to SHA values prior to the July 20, 2026 backport release means the workflow runs legacy code without any safety checks.”
- Custom checkout scripts or third-party actions can fetch fork code unrestricted
“GitHub's protections are implemented within the action's code - any alternative method of fetching code operates unrestricted.”
- Non-ephemeral self-hosted runners retain state between jobs, allowing persistent poisoning
“On non-ephemeral self-hosted runners, filesystem state persists between jobs. Local package manager caches (~/.npm, ~/.cache/pip), build directories, and installed tools live on the runner's disk, outside the Actions cache service, so read-only cache tokens don't cover them.”
What undercuts it the paper’s own results
- The default event policy does not apply to private and internal repositories or to workflow_run.
- GitHub’s checkout protections only cover pull_request_target and workflow_run, leaving other events like issue_comment unprotected.
Stated limits 3
- The default event policy does not affect private or internal repositories.
- Checkout protections are limited to pull_request_target and workflow_run events.
- Developers can re-enable the allow-unsafe-pr-checkout flag, reopening the original vector.
What’s new here 4
- Identification of SHA-pinned legacy checkout versions that bypass new protections
- Demonstration that custom checkout scripts and third-party actions evade checkout safeguards
- Highlighting script injection via ${{ }} expressions as a checkout-independent vector
- Discovery of artifact poisoning and persistent state attacks on self-hosted runners
Artifacts & methods 7
- actions/checkoutsystem
- allow-unsafe-pr-checkout flagsystem
- Wiz Code scanner
- zizmor
- custom git clone / gh pr checkout commandsprimitive
- upload-artifact / download-artifact actionsprimitive
- self-hosted runnerssystem
