Skip to main content

Cordyceps: How a Free GitHub Account Hijacked CI/CD at Microsoft, Google, and Cloudflare

9 min readDora NodaDora Noda
Share
On this page

A researcher with no organization membership, no special privileges, and nothing more than a free GitHub account could leave a pull request comment on Microsoft's Azure Sentinel repository and walk away with a non-expiring GitHub App key. The same class of bug let an anonymous PR against Google's AI Agent Development Kit escalate into roles/owner on a live Google Cloud project. Cloudflare's Workers SDK, Apache Doris, and the Python Software Foundation's Black formatter all had their own version of the same flaw.

Penetration-testing firm Novee Security calls the pattern Cordyceps, and it isn't one bug — it's a systemic class of CI/CD misconfiguration the firm found by scanning roughly 30,000 high-impact GitHub repositories. It flagged 654 of them and confirmed more than 300 fully exploitable, at organizations that included Microsoft, Google, Apache, Cloudflare, and the Python Software Foundation. Every one of those chains started the same way: a workflow trusted input from a pull request as if it came from a maintainer, and ran it with a maintainer's permissions.

What Cordyceps actually is

Novee's research groups the exploitable pattern into four recurring shapes, all rooted in the same mistake — a workflow treats attacker-controlled input as trusted:

  • Command injection. Attacker-controlled text — a branch name, a PR title, a comment body — gets interpolated directly into a shell command inside a workflow step, instead of being passed in as a quoted environment variable.
  • Code injection. The same untrusted input gets interpolated into inline JavaScript inside an actions/github-script step and evaluated at runtime, which is command injection's scripting-language sibling and just as exploitable.
  • Broken authorization. A workflow has an authorization check, but the check silently fails — it reads a property that doesn't exist on the event type actually triggering the workflow, so the gate never fires and every request passes.
  • Cross-workflow privilege escalation. A low-privilege workflow (one that safely checks out fork PR code) passes data — via artifacts, step outputs, or environment files — into a second, high-privilege workflow that trusts it without re-validating it.

The trigger underneath most of these is pull_request_target (or workflow_run, its cousin). Unlike a plain pull_request event, pull_request_target runs in the context of the base repository — full GITHUB_TOKEN scope, real secrets, the works — even when it's reacting to a fork. GitHub's own documentation has warned for years that checking out untrusted fork content inside one of these workflows hands an attacker the workflow's full privileges. Security researchers call the resulting exploit a "pwn request." Cordyceps is what happens when that warning gets ignored at scale, including by some of the org accounts writing the warning.

Five confirmed compromises, five different blast radii

Novee didn't stop at scanning — it validated exploitability end-to-end against real repositories, then worked with each vendor on fixes before publishing on June 23, 2026. The five disclosed cases show how differently the same root cause pays off depending on what a repo's CI actually has access to:

Organization / ProjectTriggerWhat an anonymous attacker got
Microsoft — Azure SentinelA PR commentAnonymous code execution on Microsoft's CI, plus theft of a non-expiring GitHub App key — persistent write access to security content Microsoft ships to customers
Google — AI Agent Development KitA single pull requestCode execution that escalated to roles/owner on the associated Google Cloud project
Apache DorisTwo independent chainsZero-click credential exfiltration via a comment trigger, plus a separate fork-PR path that stole a write-permission token
Cloudflare — Workers SDKA crafted branch nameArbitrary command execution on Cloudflare's CI runners
Python Software Foundation — BlackA single pull requestTheft of the automation token used to post, approve, and manipulate pull requests

Two of those — Microsoft and Apache Doris — required literally zero clicks from a maintainer once the PR or comment existed. The rest needed a PR to sit in the queue, not to be merged or even reviewed. Downstream, Novee's assessment lists the consequences that fall out of stolen CI credentials at this scale: publishing malicious packages to npm, PyPI, crates.io, container registries, and Helm; pushing directly to protected branches; forging CI checks to bypass merge gates; and pivoting into cloud credentials across AWS, GCP, and Netlify. A compromised build pipeline isn't just a compromised repo — it's a foothold into every artifact and every cloud account that pipeline is trusted to touch.

GitHub's fix: actions/checkout stops fetching fork code by default

Five days before Novee's disclosure, GitHub shipped a structural mitigation rather than waiting for individual repos to catch every misconfigured workflow. actions/checkout v7, released June 18, 2026, refuses to fetch fork pull request code inside pull_request_target and workflow_run workflows when it detects the specific patterns that make a pwn request possible — ref: refs/pull/<PR>/merge, a fork PR's head SHA, or an explicit fork repository name.

The rollout has teeth on a timeline:

  • v7 is generally available now — any workflow pinned to it gets the protection immediately.
  • July 20, 2026 — the same protection backports to every supported major version tag (@v4, @v3, etc., everything except v1), so workflows that never touched their checkout config still get covered automatically.
  • Workflows with a legitimate reason to check out fork code at elevated privilege have to opt out explicitly, by adding allow-unsafe-pr-checkout — turning a silent footgun into a decision someone has to type out and justify.

It's a meaningful floor-raise. It's also not a full fix: it closes the specific "checkout fetches attacker code" step, not the command-injection or broken-authorization patterns Novee found in workflows that never call checkout on fork content at all. A workflow that interpolates a PR title straight into a shell command is exploitable with no fork checkout in sight.

The audit a git-push platform's own build pipeline needs to run

None of this is abstract for a platform whose entire product is "push a git repo, we build and run it." A self-hosted PaaS's CI — the workflows that build the CLI, the Cluster API providers, the release pipeline — is exactly the shape Cordyceps targets: repos with real deploy secrets, public enough to take PRs from strangers, run by workflows nobody has re-read since the day they were written. This checklist is general — it applies to any GitHub Actions pipeline, not just bex's — but it's the concrete audit this class of vulnerability demands before assuming "we use GitHub Actions like everyone else" is a safe default:

  1. Grep every workflow for pull_request_target and workflow_run. Each hit needs a documented reason. If the workflow doesn't need base-repo secrets or write access, switch it to plain pull_request, which runs in the fork's own low-privilege context.
  2. Check every workflow has a top-level permissions: block, scoped down. GitHub's default is broad read/write access unless a workflow narrows it explicitly. permissions: contents: read (adding only what a specific job needs) turns a successful injection into a much smaller blast radius than an unscoped GITHUB_TOKEN.
  3. Search for string interpolation of github.event.* fields into run: shell steps or github-script bodies. Branch names, PR titles, and comment bodies are all attacker-controlled text. Pass them through env: and reference the environment variable instead of splicing the expression directly into the command.
  4. Trace what crosses from a fork-triggered workflow into a privileged one — via actions/upload-artifact/download-artifact, step outputs, or files written to $GITHUB_ENV. A privileged workflow consuming that data has to re-validate it, not trust that the upstream job already did.
  5. Pin actions/checkout to v7 (or confirm the July 20 backport applies) and audit every allow-unsafe-pr-checkout usage — that flag is the one place a team gets to declare "yes, we intentionally checked out untrusted code here," and it should be rare enough that every instance gets a name attached to it in review.

Nobody on this list — not Microsoft, not Google, not Cloudflare — treats CI security as an afterthought. They still shipped this. That's the actual lesson: this isn't a "hire better engineers" problem, it's a "the trust boundary is invisible in the YAML until someone specifically audits for it" problem, and it doesn't self-correct with scale or headcount.

Why this gets worse before it gets better

Novee's researchers flagged a specific accelerant: AI coding agents write CI/CD YAML fast, and they reproduce whatever pattern they were trained on — including the insecure ones — with no provenance signal attached to flag that a given pull_request_target block came from a template rather than a considered decision. A misconfiguration that used to take one engineer one mistake to introduce can now get stamped into dozens of repos in an afternoon by an agent generating boilerplate CI config on request, each copy looking exactly as confident and exactly as wrong as the last.

GitHub's own 2026 security roadmap is responding in kind — beyond the checkout v7 change, it lists a workflow lockfile that pins every direct and transitive Action dependency to a commit SHA, and "Workflow Execution Protections," centralized rulesets that control which events can trigger a workflow and who can trigger them at all. Both are aimed at the same target: making the trust boundary a policy the platform enforces, not a convention every workflow author has to remember correctly, every time, forever.

That's the same principle a self-hosted PaaS's own build pipeline should hold itself to. A tenant's git push triggers a build running on infrastructure the platform operates end-to-end — no third-party runner fleet, no CI vendor's own control plane sitting in the trust chain. That ownership doesn't grant immunity from Cordyceps-shaped bugs; it just means the audit checklist above is a platform team's job to run against its own release workflows, not a vendor's promise to take on faith.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, built and deployed through a pipeline the platform runs end-to-end. Star the repo on GitHub or deploy your first app today.


Sources:

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex