cd /news/ai-safety/the-pwn-request-reckoning-what-still… · home › topics › ai-safety › article
[ARTICLE · art-158015] src=wiz.io ↗ pub= topic=ai-safety verified=true sentiment=↓ negative

The Pwn Request Reckoning: What Still Works Against GitHub Actions

GitHub's 2026 platform changes to GitHub Actions make the default-case pull_request_target exploit significantly harder but do not eliminate the vulnerability class, according to a security research post updating a three-part GitHub Actions threat model. GitHub released actions/checkout@v7.0.0 on June 18, 2026, blocking fork code checkouts by default in untrusted triggers, added read-only cache tokens on June 26, 2026, backported fork checkout protections on July 20, 2026 to actions/checkout v2.8.0, v3.7.0, v4.4.0, v5.1.0 and v6.1.0 (v1 excluded), and reached GA on cache-mode on September 10, 2026 and Workflow Execution Protections on September 17, 2026. GitHub is scheduled on December 2, 2026 to enforce a default event policy blocking pull_request_target in public repositories without an explicit event policy, a rule in evaluate mode since September 17 that does not apply to private or internal repositories or to workflow_run; the researchers found that SHA-pinning actions/checkout to releases before the July 20, 2026 backport, such as @08eba0b resolving to v4.3.0, bypasses the protections entirely.

by read9 min views1 publishedOct 26, 2026
The Pwn Request Reckoning: What Still Works Against GitHub Actions
Image: Wiz (auto-discovered)

For years, pull_request_target has been the canonical Pwn request vector: a workflow trigger that runs with repository secrets, yet can be triggered by anyone who opens a pull request from a fork. In 2026, GitHub responded with platform changes that make the default-case exploit significantly harder.. These changes represent real progress. Checkout protections, read-only cache tokens, and event policies close the paths that made exploitation trivial. But they don't eliminate the vulnerability class. They shift it. Attackers who previously exploited any pull_request_target workflow now need to find explicit opt-ins, outdated checkout versions, unprotected triggers, or script injection.

In Part 1 of our GitHub Actions research, we laid out the threat model and core defenses, and Part 2 covered AI-powered actions. This post updates that threat model for 2026: what GitHub's changes actually protect against, where the gaps remain, and how defenders need to adjust. The audit checklist at the end covers what to look for in your own workflows.

Recognizing the endemic risk posed by Pwn requests, GitHub spent 2026 systematically closing the gap through a phased rollout of platform-level protections and action updates.

Date Milestone & Action Taken
June 18, 2026 Release of actions/checkout@v7.0.0 , blocking fork code checkouts by default in untrusted triggers (pull_request_target, and workflow_run triggered by pull requests).
June 26, 2026 GitHub introduces read-only cache tokens at the platform level for untrusted triggers, preventing cache poisoning attacks.
July 20, 2026 Backported fork checkout protections across all active major versions of actions/checkout (v2.8.0, v3.7.0, v4.4.0, v5.1.0, and v6.1.0; v1 excluded). Workflows referencing mutable version tags (e.g., @v4) inherit these defenses automatically.
September 10, 2026 Fine-grained cache controls via cache-mode reached General Availability (GA).
September 17, 2026 Workflow Execution Protections reached GA, enabling fine-grained event policies and actor rules.
December 2, 2026 [Scheduled] GitHub begins enforcing the default event policy that blocks pull_request_target in public repositories without an explicit event policy. The rule has been in evaluate mode since September 17. Private and internal repositories are not affected.

While this sequence of platform updates significantly strengthens default security configurations for GitHub Actions, they don't remove all risk. The December 2 enforcement is a default, not a ban. Maintainers whose automation depends on pull_request_target (labelers, preview deploys, fork CI with secrets) will add an event policy that allows it. Those are exactly the workflows that handle untrusted input with elevated privileges. The default policy also doesn't apply to private and internal repositories or to workflow_run. The rest of this section covers how these surviving workflows can still be exploited.

One of the most disconcerting findings in our research is how standard supply chain hardening advice - specifically pinning actions to exact commit SHAs - can inadvertently bypass GitHub's platform defenses.

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. Our empirical testing across workflow triggers confirmed this discrepancy: referencing mutable tags like @v4 (resolving to v4.4.x) or pinned versions such as @v4.4.0 remain protected, whereas older versions like @v4.3.0 or SHA-pinned references to legacy releases (such as @08eba0b... resolving to v4.3.0) offer no protection.

To maintain protection, security teams using SHA pinning must ensure their refs are updated to match or exceed the minimum patched release versions: v2.8.0, v3.7.0, v4.4.0, v5.1.0, v6.1.0, or v7.0.0+.

A second gap concerns workflows that bypass actions/checkout altogether. GitHub's protections are implemented within the action's code - any alternative method of fetching code operates unrestricted. Direct CLI invocations sidestep protections entirely:

Third-party checkout actions implement their own git logic, so they don't inherit these protections. Any of them configured with a PR head ref will check out fork code unrestricted.

GitHub's checkout protections only cover pull_request_target and workflow_run:

Pwn requests triggered in other event types besides pull_request_target (such as issue_comment) will not be blocked by this change. Further hardening of additional events may be explored in future releases.

GitHub changelog

This creates an inconsistent security boundary: GitHub's cache controls explicitly treat issue_comment as a low-trust trigger, while checkout protections do not. Workflows using issue_comment for bot commands (/deploy, /test, /cherry-pick) remain fully exploitable with checkout v7 - no allow-unsafe-pr-checkout flag required:

Other privileged triggers have the same gap. repository_dispatch also runs with secrets, and it's exploitable when a workflow uses attacker-influenced payload data to choose a checkout ref.

Until GitHub extends checkout protections, these workflows have to enforce their own authorization, and it needs to hold from the moment the event fires until the code runs:

  • Verify who triggered it. For comment commands, check the commenter's actual repository permission (getCollaboratorPermissionLevel, write or above). Don't rely on author_association, which doesn't reflect current permissions. For repository_dispatch, limit who holds a token that can send the event, and don't treat the payload as proof of who sent it.

  • Pin what was approved. refs/pull/N/merge is mutable, so an attacker can push a malicious commit after a maintainer comments /deploy. Check out the exact head SHA, and abort if the PR was updated after the comment was posted.

  • Treat the event payload as untrusted input. Validate comment arguments and dispatch payload fields against a strict, anchored allowlist regex, and pass them to the shell through environment variables, never through ${{ }} interpolation.

A common misconception is that pull_request_target vulnerabilities require checking out fork code. In reality, attackers can achieve code execution without any checkout step, through script injection.

When workflow authors interpolate event context directly into shell commands using ${{ }} expressions, GitHub substitutes the value into the script before the shell runs, so attacker-controlled data becomes part of the command itself:

An attacker simply crafts a pull request with a malicious title:

"; touch /tmp/pwned; echo INJECTED # The quote closes the string, the semicolon ends the echo, and everything after it runs as a new command with the job's secrets and token. None of GitHub's 2026 changes touch this: there is no checkout to block, and the vulnerability lies in how the workflow handles untrusted input.

The fix is to never interpolate untrusted input into a script. Pass it through an environment variable instead, so the shell treats it as data:

The double quotes around $PR_TITLE matter. Without them, the shell still word-splits and glob-expands the value.

GitHub's read-only cache tokens protect the Actions cache service, but workflows can still exchange data through channels that remain unaffected.

Artifact Poisoning

The upload-artifact and download-artifact actions operate independently of cache tokens. The classic attack chain uses an unprivileged pull_request workflow to upload attacker-controlled build outputs, then a privileged workflow_run to download and execute them:

This is exactly the workflow_run scenario checkout v7 was built to block, and it doesn't help here. The privileged workflow never checks out fork code; it downloads it. Unlike cache entries, there's no platform equivalent that marks artifacts from untrusted runs as tainted, so any workflow that downloads and executes artifacts from a less-trusted run inherits the risk.

Self-Hosted Runner State

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. Attacker code running under an untrusted trigger can write to these locations and poison the next trusted workflow scheduled on the same machine, including release and publish jobs. Use ephemeral runners, which are wiped after each job, for any workflow that untrusted events can trigger, and keep untrusted and trusted workflows in separate runner groups.

Many security teams believe they have another safeguard in place: the "Require approval for all external contributors" setting under Settings -> Actions -> General. If enabled, fork PRs need maintainer approval before workflows run. Problem solved, right?

Not quite. That setting only gates pull_request workflows, which run the fork's own workflow YAML. For pull_request_target, GitHub considers the workflow definition trusted (it comes from the base repository), so the approval setting doesn't apply and the run starts immediately.

The irony cuts deep: pull_request_target is the more dangerous trigger (secrets access, write tokens), yet it's the one that skips the approval requirement. If a pull_request_target workflow needs a human gate, put the privileged job behind an environment with required reviewers, or restrict who can trigger it with Workflow Execution Protections actor rules.

Finally, developers who need to interact with fork code can explicitly disable checkout protections by enabling the allow-unsafe-pr-checkout flag:

While the parameter name is deliberately alarming, determined development teams or third-party workflows may still employ it to maintain legacy automation, effectively reopening the exact same execution vector as before. Security teams should pay special attention to any use of this flag.   To sum up, GitHub's 2026 security rollout deserves credit. Default event policies, checkout protections, and read-only cache tokens make the trivial Pwn request (fork, PR, profit) significantly harder. Starting December 2, pull_request_target will no longer run in public repositories unless an event policy explicitly allows it. Cache poisoning through the Actions cache is mitigated. These are real wins.

But the vulnerability class isn't eliminated. It shifted. What remains exploitable: explicit opt-ins via allow-unsafe-pr-checkout, SHA-pinned versions predating the July 2026 backport, custom checkout scripts that bypass action-level controls, script injection requiring no checkout at all, unprotected triggers like issue_comment and repository_dispatch, artifact poisoning chains from pull_request to workflow_run, persistent state on non-ephemeral self-hosted runners, and private or internal repositories, which the default policy doesn't cover. The attack surface shrinks, but what remains is harder to spot.

Start by auditing what you already run, then get ahead of the December 2 enforcement.

Wiz Code automatically scans GitHub Actions workflows for Pwn requests, script injection, version gaps, and risky configurations to cover this residual attack surface. Alternatively, use open-source tools like zizmor or even these basic grep searches to start the audit:

In addition, run this script to resolve each SHA to its release tag, then compare against the patched versions:

pull_request_target Enforcement Starting December 2, the default event policy will block pull_request_target in public repositories that have no explicit event policy. workflow_run is not affected, and neither are private or internal repositories. Before that date:

  • Check policy insights to see which pull_request_target runs would be blocked.
  • Decide per workflow whether it actually needs pull_request_target. If not, move it topull_request . If a privileged follow-up step is still needed, aworkflow_run workflow can handle it, but it must treat the PR run's artifacts as untrusted data, never execute them.
  • For workflows that legitimately need fork access, create an explicit event policy and use workflow file targeting to allow pull_request_target only for those files. Treat every file on that allowlist as high-risk: run it through the audit above before adding it, and re-audit whenever it changes.

State of SDLC Security 2026

── more in #ai-safety 4 stories · sorted by recency
── more on @github 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-pwn-request-reck…] indexed:0 read:9min 2026-10-26 · —