TL;DR
GitHub Actions is not a single vulnerability — it is a product designed around a set of defaults that, once assembled by an attacker, become a turnkey secret-extraction machine. From the spotbugs PAT theft in November 2024 to the Bitwarden CLI compromise on April 22, 2026, every major CI/CD supply chain incident traces back to the same four misfeatures: pull_request_target's scope bleed, mutable git tags, the shared fork object pool, and the poisonable action cache. The platform has documented all four risks since 2021 and shipped no structural guardrails for any of them.
Background
On April 22, 2026, Bitwarden — the password manager downloaded by over 20 million developers — shipped a poisoned version of its CLI to npm. Version 2026.4.0 arrived via the same GitHub Actions workflow used for legitimate releases, except the GitHub token authorizing that workflow had been harvested upstream by a Checkmarx supply chain compromise. Any developer who ran npm install @bitwarden/cli that day was a credential-theft victim before they typed a single command.
What makes the Bitwarden incident instructive is not its novelty — it is how unremarkably it fits a pattern that stretches back eighteen months and runs through at least a dozen major open source projects. Trace any of them back far enough and you end up reading a .github/workflows YAML file whose authors made entirely reasonable decisions that GitHub's defaults made catastrophic.
This post-mortem maps the full kill chain, identifies the four platform-level root causes that make it keep happening, and closes with what defenders can do today while GitHub fails to act.
Technical Analysis
Phase 1 — The Origin Point: spotbugs and the `pull_request_target` Trap (November 2024)
The chain begins quietly. The spotbugs static analysis project ran a pull_request_target workflow — a trigger GitHub introduced so that maintainers could add labels to PRs from forks, or post comments, without granting anonymous contributors write-scoped tokens. The trigger is elegant in concept: it runs in the context of the base repository, not the fork, so you can give it secrets.
The problem is the second, entirely optional step: checking out github.event.pull_request.head.sha. Once you check out the fork's code in a workflow that carries base-repo secrets, you have handed an anonymous internet user code execution inside your trust boundary.
A threat actor submitted a plausible-looking PR to spotbugs. The pull_request_target workflow fired, the fork code ran, and the attacker lifted a maintainer Personal Access Token (PAT). GitHub documented this exact risk in their security lab in 2021 and called the attack class "pwn requests." spotbugs still had the misconfiguration in November 2024.
Phase 2 — The Mutable Tag Problem: tj-actions/changed-files / CVE-2025-30066 (March 2025)
The stolen spotbugs PAT had write access to reviewdog, a widely-used GitHub Actions utility. Four months after the spotbugs compromise, the attacker used that access to push a malicious commit to reviewdog/action-setup and moved the v1 git tag to point at it. This is possible because GitHub Actions version tags are regular git refs — they can be force-pushed by anyone with repository write access, and they are re-resolved on every workflow run rather than locked at reference time.
The blast radius multiplied immediately. tj-actions/eslint-changed-files referenced reviewdog by tag. tj-actions/changed-files referenced that by tag. 23,000 downstream repositories referenced changed-files by tag. Every single one of them executed a memory scraper that dumped runner secrets — AWS credentials, GitHub tokens, PyPI publish keys, npm access tokens — into public build logs, base64-encoded to slow detection.
CISA issued an advisory (CVE-2025-30066). The original intended target was Coinbase. Unit 42 confirmed the attribution chain back to the spotbugs PAT.
Phase 3 — The Shared Object Pool: "Imposter Commits" and Cache Poisoning (December 2024 → ongoing)
Around the same time, the Ultralytics computer vision library was hit through the identical pull_request_target misconfig, but with a different second stage. Because the fork PR couldn't reach the publishing credentials directly, the attacker poisoned a GitHub Actions cache entry. When Ultralytics' legitimate release workflow later restored that cache, it executed the payload during wheel builds. Two versions of Ultralytics reached PyPI carrying a crypto miner.
The cache attack succeeds because of a third structural issue: GitHub Actions cache entries are keyed by branch and shared down to child runs. A pull_request_target job runs as the default branch, meaning a fork PR can write entries that the base repo's subsequent legitimate jobs will consume — with no UI or API indication that the entry originated from untrusted code.
Chainguard had documented a closely related mechanism — imposter commits — in 2022. GitHub stores a repository and all its forks in one shared git object pool. The runner resolves uses: owner/action@ref against anything present in that pool, including commits that exist only in forks and have never been reviewed or merged. A tag can legally point at a dangling object from a stranger's fork. This is how the malicious tj-actions commit executed even though it "didn't belong to any branch" in the canonical repository.
Phase 4 — AI-Assisted Industrialization: The prt-scan Campaign (March–April 2026)
By 2026 attackers stopped finding pull_request_target misconfigs manually. The prt-scan campaign, tracked by Wiz Research, ran across six weeks from March 11 through April 2026, opening over 500 malicious pull requests across hundreds of repositories using rotating throwaway accounts. The PRs used AI-generated, language-appropriate diffs designed to look like plausible contributions — a bug fix here, a test addition there — sufficient to trigger the vulnerable workflow without arousing immediate suspicion.
At least 50 repositories had secrets extracted before detection, including AWS keys, Cloudflare API tokens, and Netlify authentication credentials. The CSA characterized prt-scan as the first industrialized AI-assisted CI/CD supply chain campaign. The same technique was simultaneously used by a second, unattributed actor targeting the same trigger class.
The s1ngularity campaign (August 2025) demonstrated a parallel attack vector: the nx build system's pull_request_target workflow interpolated the PR title directly into a shell step via ${{ }} template syntax. Shell injection via PR title — code execution, npm publishing token in scope, and 5,000+ private repositories briefly made public as the attacker used harvested AI coding assistant credentials to enumerate and exfiltrate them.
Phase 5 — The Terminal Node: Checkmarx → Bitwarden CLI (April 22, 2026)
The April 22–23 triple-campaign (Checkmarx KICS, CanisterSprawl, xinference) documented by GitGuardian represents the current state of play: parallel, coordinated, multi-ecosystem campaigns where GitHub Actions compromise at one node propagates automatically downstream.
In the Bitwarden case, the Checkmarx KICS GitHub Actions compromise (attributed to TeamPCP) harvested GitHub authentication tokens that included write access to Bitwarden's publishing pipeline. The attacker used those tokens to inject a malicious GitHub Actions workflow, which then distributed @bitwarden/cli 2026.4.0 through the legitimate npm package. Bitwarden confirmed the incident affected the npm distribution path during a short window on April 22. Vault data and production systems were unaffected — but anyone who installed the CLI that day ran arbitrary attacker code with the same privileges as the CLI process.
The Four Root Causes (Not Bugs — Design Choices)
| Root Cause | Attack Surface | Documented Since | Guardrail Shipped |
|---|---|---|---|
| pull_request_target + fork checkout | Code execution inside base-repo trust boundary | 2021 (GitHub Security Lab) | None |
| Mutable git tags | Tag hijacking affects all downstream consumers instantly | Known pre-2021 | None |
| Shared fork object pool | Dangling commits in forks are fetchable as if canonical | 2022 (Chainguard) | None |
| Poisonable cache | Fork PRs write entries consumed by legitimate base-repo runs | 2024 (post-Ultralytics) | None |
None of these require a zero-day. None require elevated privileges. All four are documented on GitHub's own developer pages. The platform's response to each incident has been a blog post recommending that maintainers audit their YAML.
IOCs / Indicators
prt-scan campaign (March–April 2026):
- Attacker accounts: Rotating throwaway GitHub accounts created <72h before PR submission
- PR pattern: AI-generated diffs in correct language for target project, <150 LOC, targeting repos with
pull_request_targettrigger - Exfil: AWS credentials, Cloudflare tokens, Netlify tokens observed in harvested output
Checkmarx KICS / TeamPCP (April 22, 2026):
- Malicious Docker images:
checkmarx/kicsrepository (flagged by Docker Hub automated scan) - Payload: Obfuscated credential harvester targeting: GitHub tokens, AWS/Azure/GCP credentials, npm config, SSH keys, env vars
- C2: Compressed, encrypted exfil to TeamPCP infrastructure
- Attribution: TeamPCP (self-disclosed on X post-disclosure); second Checkmarx attack in two months (first: March 2026 targeting Trivy + LiteLLM)
CanisterSprawl:
- Initial package:
pgserve(malicious versions, npm) — April 21 - C2: Internet Computer Protocol (ICP) canister — decentralized, censorship-resistant
- Propagation: Self-replicating via stolen npm tokens; cross-ecosystem jump to PyPI if PyPI token found
- Related: Namastex.ai npm packages (same core methods, same ICP infrastructure)
Bitwarden CLI:
- Affected version:
@bitwarden/cli 2026.4.0(April 22, 2026 — short window) - SHA for affected npm tarball: Confirm via
npm view @bitwarden/[email protected] dist.integrity - Detection:
npm auditflags post-removal; package.json version lock to<2026.4.0or>=2026.4.1
Lyrie Take
The GitHub Actions supply chain is not a bug-tracking problem — it is an architectural debt problem running on the world's most critical software delivery infrastructure. The entire open source ecosystem's build, test, and publish pipelines are centralized on a single platform whose threat model was designed for private enterprise CI, not anonymous fork contributors with code execution on secret-carrying runners.
What makes this uniquely dangerous in the agentic era: autonomous coding agents (Copilot Workspace, Devin, Claude Code, Codex) now submit GitHub PRs autonomously, often without a human reviewing the workflow implications of a repository before interacting with it. An attacker can submit a malicious PR, an AI coding agent on the victim side can approve and merge it via the same automated pipeline, and the resulting pull_request_target execution happens before any human sees the diff. The attack surface is not shrinking — it is growing at the rate of AI adoption.
Lyrie's autonomous defense posture here is straightforward: machine-speed detection of workflow misconfigurations, real-time monitoring of tag mutations in dependency chains, and behavioral analysis of CI runner processes for anomalous outbound connections. When the platform's documentation is the only guardrail, you need a second layer operating faster than an attacker's PR merge window.
Defender Playbook
Immediate (do this week):
1. Audit pull_request_target usage. Run: grep -r "pull_request_target" .github/workflows/ — any result that also contains actions/checkout with ref: ${{ github.event.pull_request.head.sha }} is a live pwn-request vector. Replace with pull_request (no fork access) or add an explicit environment protection rule requiring manual approval.
2. Pin all third-party Actions to commit SHAs. Replace uses: owner/action@v1 with uses: owner/action@<full-40-char-SHA>. Automate with Dependabot for Actions or StepSecurity Harden-Runner.
3. Scope GITHUB_TOKEN permissions minimally. Add permissions: read-all at the workflow level and grant only what each job needs. This limits blast radius when a token is stolen.
4. Rotate any secrets exposed to public repo runners. Secrets in private repo runners have limited exposure from the tj-actions class of attack; public repo runner secrets should be treated as compromised if the repository used an affected action version between March 14–15, 2025.
Structural (this month):
5. Enable Artifact Attestations for all published packages. GitHub's Sigstore-backed attestations establish cryptographic provenance — a poisoned build that bypasses your workflow will not produce a valid attestation.
6. Implement cache isolation. Set cache: false on any job processing pull request input from forks, or scope cache keys to github.ref and never restore across trust boundaries.
7. Monitor for imposter commits. Use Chainguard's tooling or StepSecurity's harden-runner to verify that all action SHAs resolve to commits reachable from a canonical upstream branch — not dangling objects in the fork network.
8. Verify npm package integrity post-install. For security-critical packages like CLI tools, lock to explicit SHAs in your lockfile and run npm audit signatures to verify registry-level Sigstore signatures before execution.
Sources
1. Andrew Nesbitt, "GitHub Actions is the weakest link," April 28, 2026 — https://nesbitt.io/2026/04/28/github-actions-is-the-weakest-link.html
2. Wiz Research, "tj-actions/changed-files supply chain attack — CVE-2025-30066," March 2025 — https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066
3. Wiz Research, "prt-scan: Six accounts, one actor," April 2026 — https://www.wiz.io/blog/six-accounts-one-actor-inside-the-prt-scan-supply-chain-campaign
4. GitGuardian, "Three Supply Chain Campaigns Hit npm, PyPI, and Docker Hub in 48 Hours," April 23, 2026 — https://blog.gitguardian.com/three-supply-chain-campaigns-hit-npm-pypi-and-docker-hub-in-48-hours/
5. CISA Advisory, "Supply Chain Compromise of Third-Party tj-actions/changed-files," March 18, 2025 — https://www.cisa.gov/news-events/alerts/2025/03/18/
6. Chainguard, "Imposter Commits in GitHub Actions and CI/CD," 2022 — https://www.chainguard.dev/unchained/what-the-fork-imposter-commits-in-github-actions-and-ci-cd
7. SafeDep, "Bitwarden CLI Supply Chain Compromise," April 2026 — https://safedep.io/bitwarden-cli-supply-chain-compromise/
8. Unit 42 (Palo Alto), "GitHub Actions Supply Chain Attack," March 2025 — https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/
9. GitHub Security Lab, "Preventing pwn requests," 2021 — https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
Lyrie.ai Cyber Research Division — Senior Analyst Desk
Lyrie Verdict
Lyrie's autonomous defense layer flags this class of exposure the moment it surfaces — no signature update required.