
The Pwn Request Reckoning: What Still Works Against GitHub Actions
The Pwn Request Reckoning is a Wiz post updating the GitHub Actions pull_request_target threat model for 2026. It covers what GitHub's checkout protections, read-only cache tokens and event policies block, and what still works. Remaining paths: SHA-pinned pre-backport actions/checkout, custom checkout scripts, issue_comment and repository_dispatch triggers, script injection, artifact poisoning, self-hosted runner state and allow-unsafe-pr-checkout. It ends with grep and zizmor audit steps.
What it is
A Wiz post (Part 3 of its GitHub Actions research) that updates the pull_request_target threat model after GitHub's 2026 platform changes: checkout protections in actions/checkout, read-only Actions cache tokens, and event policies. Wiz says the changes make the default exploit harder but shift the vulnerability class rather than remove it. From December 2, pull_request_target stops running in public repositories unless an event policy allows it.
How it works
Each surviving path sits outside what GitHub's changes cover.
- SHA pinning. The protections live inside
actions/checkout, backported on July 20, 2026. Mutable tags such as@v4(resolving to v4.4.x) and@v4.4.0are protected.@v4.3.0, or a SHA pointing at it, runs legacy code with no checks. Wiz confirmed this by testing. - Custom checkout.
git clone,gh pr checkoutandgh repo clonefetch fork code with no protection. Third-party checkout actions use their own git logic and are also unprotected. - Other triggers. Checkout protections cover only
pull_request_targetandworkflow_run.issue_commentbot commands (/deploy,/test,/cherry-pick) stay exploitable on checkout v7 withoutallow-unsafe-pr-checkout.repository_dispatchis exploitable when payload data chooses the checkout ref. - Script injection.
${{ github.event.pull_request.title }}insiderun:is substituted before the shell runs. A title like"; touch /tmp/pwned; echo INJECTED #executes with the job's secrets. No checkout is involved. - Artifact poisoning. An unprivileged
pull_requestworkflow uploads attacker-built output, and a privilegedworkflow_rundownloads and executes it. Nothing marks artifacts from untrusted runs as tainted. - Self-hosted runners. On non-ephemeral runners,
~/.npm,~/.cache/pipand build directories persist outside the cache service, so they can poison later release jobs. - Approval setting. "Require approval for all external contributors" gates only
pull_request.pull_request_targetruns immediately.
Preconditions / Affected
- The December 2 default does not apply to private or internal repositories, or to
workflow_run. - Workflows that maintainers explicitly allow through an event policy (labelers, preview deploys, fork CI with secrets) are the ones that handle untrusted input with privileges.
- Anything using
allow-unsafe-pr-checkoutreopens the original vector.
What to do
Keep actions/checkout at or above the patched releases: v2.8.0, v3.7.0, v4.4.0, v5.1.0, v6.1.0 or v7.0.0+. For SHA-pinned refs, resolve each SHA to its release tag and compare. Wiz provides a script for this, but the page text does not include it.
Audit grep from the post, with each hit reviewed by hand (zizmor is the alternative):
# Flag risky triggers, unsafe checkouts, outdated checkout versions, artifact downloads,
# self-hosted runners and event data in expressions (env: usage is safe), then review each hit
grep -rnE 'pull_request_target|workflow_run|issue_comment|repository_dispatch|allow-unsafe-pr-checkout|self-hosted|download-artifact|gh (pr checkout|repo clone)|git (clone|fetch)|refs/pull|actions/checkout@v(1|2\.[0-7]|3\.[0-6]|4\.[0-3]|5\.0|6\.0)([^0-9]|$)|\$\{\{[[:space:]]*github\.(event\.|head_ref)' .github/workflows/Fixes the post recommends:
- Pass untrusted values through
env:and reference them as a double-quoted"$PR_TITLE". Unquoted, the shell still word-splits and globs. - For
issue_commentandrepository_dispatch, check the commenter's real permission withgetCollaboratorPermissionLevel(write or above), notauthor_association. Check out the exact head SHA and abort if the PR changed after the comment. Validate payload fields against an anchored allowlist regex. - Gate privileged
pull_request_targetjobs with an environment that requires reviewers, or with Workflow Execution Protections actor rules. - Use ephemeral runners for anything untrusted events can trigger, and separate runner groups for trusted and untrusted workflows.
- Before December 2, review policy insights for
pull_request_targetruns that would be blocked. Move workflows that don't need it topull_request. For the rest, write an event policy that allows specific workflow files only, and re-audit those files on every change.
Caveats
- GitHub's changelog says further hardening of other events "may be explored in future releases," so the
issue_commentandrepository_dispatchgaps have no committed fix. - The post is from Wiz, which also sells Wiz Code for this scanning. The attack claims come from its own testing.
Organisations named here
- allow-unsafe-pr-checkout
- artifact poisoning
- Wiz Research
Technologies named here
- Wiz Code
- Third-party checkout actions
- grep audit command over .github/workflows/
- Workflows that use the allow-unsafe-pr-checkout flag
- actions/checkout v7
- GitHub event policies and policy insights
- Workflow Execution Protections actor rules
- GitHub environments with required reviewers
- Non-ephemeral self-hosted runners
- getCollaboratorPermissionLevel
- Private and internal repositories
- GitHub Actions
- GitHub
- zizmor
- Wiz
Techniques named here
- Pwn request via pull_request_target
- Exploiting unprotected privileged triggers
- Artifact poisoning
- SHA pinning paradox
- Pwn Request
- Script injection
Related in DevSecOps
JFrog: The Governance Gap Between Your Policy and Your Pipeline

Dev Machine Guard Now Supports Windows - StepSecurity

Socket: TanStack npm Packages Compromised
