The Incident
On 2026-04-02, security researcher Charlie Eriksen of Aikido Security publicly disclosed an automated GitHub Actions supply-chain campaign tracked as prt-scan. Wiz Research subsequently consolidated the activity as a single six-wave operation that began on 2026-03-11 — three weeks before public disclosure — and clustered six attacker accounts under one operator. Across the waves, the operator opened more than 500 malicious pull requests against public repositories whose workflows use the pull_request_target trigger. Across roughly 450 analyzed exploit attempts, the campaign achieved an under-10% success rate but compromised approximately 50 repositories’ AWS, Cloudflare, and Netlify credentials and produced two confirmed npm package compromises.
The mechanic was repetitive and statically detectable. The attacker forked a target repo, created a branch named prt-scan-{12-hex-chars}, injected a payload into a CI-relevant file (conftest.py, package.json, Makefile, or build.rs), and opened a PR titled ci: update build configuration to blend in. Inside the privileged pull_request_target runner, the payload extracted GITHUB_TOKEN from git config and emitted base64-encoded credential blocks to the workflow log for later harvesting. Wave six escalated to per-repository, language-aware payloads that mimicked each target’s coding conventions — agentic reconnaissance attached to an offensive supply-chain pipeline.
MITRE ATT&CK coverage: T1195.002 (Compromise Software Supply Chain), T1059 (Command and Scripting Interpreter), T1552.001 (Credentials In Files).
The Authority Path That Failed
The identity carrying execution authority at the moment of failure is the GitHub Actions runner of the base repository, executing under the privileged context that pull_request_target confers. The scope it held is the full GITHUB_TOKEN for the base repo plus every secret referenced by the workflow — AWS access keys, Cloudflare API tokens, Netlify deploy tokens, npm publish credentials. The scope it exercised in success cases was running attacker-authored code from the head of an untrusted fork PR — conftest.py invoked by pytest, package.json lifecycle scripts run by npm install, Makefile recipes, build.rs invoked by cargo — with that full secret context attached.
The trust anchor that failed first is the maintainer’s reading of pull_request_target. The trigger was designed to let workflows label or comment on fork PRs from the privileged base context; many workflows compounded that with an actions/checkout of ${{ github.event.pull_request.head.sha }} and then ran build, lint, or test steps that load attacker-controlled files. No credential needed to be stolen — the runner spawned the binaries on the attacker’s behalf. The auditable gap is in the workflow YAML itself: any combination of pull_request_target plus checkout-of-PR-head plus a step that reads or executes repo files is the canonical anti-pattern, and it is detectable before a single PR is filed.
SecurityV0 Perspective
This fits unproven_execution / ASI05. The base-repo runner is a non-interactive identity that the operator wired up for narrow purposes — labeling, commenting, status checks — and that the workflow definition then handed broad code-execution powers it was never explicitly granted. The non-human identity fallout (stolen GITHUB_TOKEN, AWS keys, Cloudflare and Netlify tokens, npm publish rights, two compromised npm packages) is the consequence; the root failure is that the runner executed code the operator never authorized.
The evidence pack SecurityV0 would produce inventories every workflow in the org that uses pull_request_target, identifies which of those workflows check out PR-head code, captures the secret references each workflow expands at runtime, and resolves the file paths the runner will execute (test entry points, build scripts, lint configs, package lifecycle hooks). Before any PR is filed, the pack answers the operator question: which workflows would let an external fork’s HEAD execute against base-repo secrets, and what is each one’s blast radius? After the fact, it answers the forensic question: which pull_request_target runs in the past N days received a fork-supplied workflow file, ran a checkout of fork HEAD, and emitted a base64-encoded secret blob into the run log?
What To Do
- Audit every workflow that uses
pull_request_target. For each, check whetheractions/checkoutis invoked withref: ${{ github.event.pull_request.head.sha }}(or any equivalent fork-HEAD reference) followed by a step that reads or executes repo files. That triple — privileged trigger, fork checkout, code execution — is the campaign’s entire prerequisite. Replace withpull_requestfor any workflow that actually needs to run PR code. - Strip secret access from
pull_request_targetruns by default. Pinpermissions:to the minimum needed (pull-requests: write,issues: write); do not exposeGITHUB_TOKENwrite scopes or environment-level secrets in PR-triggered workflows. If a labeling step needs a token, scope it to that step alone viaenv:rather than the workflow. - Require approval for fork-PR workflow runs. Enable “Require approval for all outside collaborators” in repository Actions settings so first-time fork PRs cannot trigger workflows automatically. Combined with branch protection on the workflow files themselves, this denies the campaign its quiet auto-fire.
- Monitor for the campaign’s signature. Alert on PR titles containing
ci: update build configurationand head-branch names matchingprt-scan-[0-9a-f]{12}against repositories withpull_request_targetworkflows. Even after the campaign retunes, the workflow-log emission of base64-encoded credential blocks is a higher-fidelity detection than scanning PR diffs. - Rotate every credential that lived in a
pull_request_target-exposed workflow. If the workflow ran in the affected window — repo by repo — assume the secret context was readable by any fork PR that landed during that period. RotateGITHUB_TOKENscopes, AWS access keys, Cloudflare API tokens, Netlify deploy tokens, and any npm publish tokens; re-issue from short-lived OIDC where possible.
Sources
- Wiz Research — Six accounts, one actor: inside the prt-scan supply chain campaign
- Aikido Security — Charlie Eriksen, Lead Security Researcher
- SafeDep — prt-scan: A 5-phase GitHub Actions credential theft campaign
- Cloud Security Alliance Lab Space — CSA Research Note: GitHub Actions prt-scan supply chain campaign
- Dark Reading — AI-Assisted Supply Chain Attack Targets GitHub
- Cybersecurity News — New GitHub Actions attack chain uses fake CI updates to exfiltrate secrets and tokens
- eBuilderSecurity — AI-driven supply chain attack compromises 500 GitHub repositories in six-week campaign
- GitHub Docs — Events that trigger workflows: pull_request_target
- GitHub Security Lab — Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests
- MITRE ATT&CK: T1195.002, T1059, T1552.001