Home/ Blog/ Security news/ Article
Blog · Security news

A free GitHub account can push code as a trusted maintainer. Upgrading actions/checkout won't fix it.

Cordyceps lets anyone with a free GitHub account run code as a maintainer on 300+ repos. Why upgrading actions/checkout closes one door, not the others.

Isometric conveyor of identical blocks, one sprouting a tendril back through a gate

GitHub rolled a fix into the most-used action in its catalog this month, and most teams will apply it and move on. That is the mistake. The change shipped into actions/checkout on June 18 closes exactly one of the four ways an outside pull request can hijack a continuous-integration job, and it is not the most common one. The underlying problem, which researchers have named Cordyceps, is simpler and worse: a person with a free GitHub account and no connection to your project can make your automation run their code with your permissions.

Novee Security scanned around 30,000 high-impact open-source repositories and confirmed that more than 300 were fully exploitable this way, including projects run by Microsoft, Google, Apache, Cloudflare and the Python Software Foundation. The headline number undersells the reach. Each of those repositories is a distribution point, so one hijacked pipeline can push tainted output to everyone downstream of it.

What Cordyceps actually is

Cordyceps is not a single bug with a single patch. It is a pattern: untrusted input from a pull request crossing into a privileged automation job that was never built to trust it. Novee's writeup and reporting in The Hacker News break it into four shapes, and you need all four in your head, because closing one leaves the rest open.

  • Command injection. An attacker controls a branch name, pull-request title or comment body, and that text lands directly inside a shell command. It runs.

  • Broken authorization logic. The workflow does check who is allowed, but the check is buggy. A null value the code reads as approved is enough.

  • Artifact poisoning. A low-privilege job writes a file or an environment value, and a high-privilege job reads it and trusts it. Neither job is wrong on its own.

  • Cross-workflow escalation. workflow_run lets a trusted workflow pick up the elevated rights of another, so output from an untrusted run flows into a privileged one.

The triggers that make this possible are familiar to anyone who writes pipelines. pull_request_target and workflow_run both run with the base repository's secrets and a write-scoped token, and issue_comment fires on a comment from anyone at all. The exposure lives in the combination, not in any single line, which is why it has sat in plain sight for years.

Why "we upgraded actions/checkout" is not the fix

GitHub's change is real and worth taking. The patched action refuses to check out the head or merge commit of a fork's pull request when it runs under pull_request_target or workflow_run, the classic pwn-request setup, unless you explicitly set allow-unsafe-pr-checkout to true. That removes the single most copied footgun in CI.

It does nothing for the other three shapes. Socket flagged the same limit: the change blocks only fork pull-request head and merge checkouts, not other untrusted references. A branch-name command injection still fires. A poisoned artifact still flows from a low-privilege job into a high-privilege one. A broken authorization check still returns the wrong answer. A team that upgrades the action and closes the ticket has shut one door in a building with three more standing open, and it now has a false reading on its own exposure. That false confidence is its own risk.

The damage does not stop at the repository

The Azure Sentinel finding is the one to sit with. Novee reported that a single comment posted on a pull request was enough to execute an anonymous attacker's code on Microsoft's continuous-integration system and walk off with a GitHub App key that never expires. That key carried write access to security detection content delivered to customer workspaces through the Azure Marketplace.

Read that chain again as a defender. The target was not Microsoft's source tree. It was the pipeline that distributes detection rules to the people relying on them to catch attacks. A compromise of build automation becomes a compromise of the supply line, and the blast radius is every downstream consumer rather than the one repository. Google's AI Agent Development Kit told the same story, where a single pull request could reach the highest privilege role in the connected cloud project. Python's Black formatter, installed millions of times a month, exposed a bot token that could approve pull requests as the project itself, turning code injection into a clean path to the main branch.

We have seen the downstream-trust failure before, where npm packages passed review and then turned hostile. Cordyceps is the same failure one layer up, in the machinery that builds and ships the package rather than the package itself.

Why your scanners stayed quiet

Static and dynamic analysis tools read one file at a time. The most dangerous Cordyceps findings live in the relationship between two files: a workflow that is safe alone and a second workflow that is safe alone, unsafe only in how data passes between them. No single-file rule can see that, which is why repositories that pass every existing scan still failed Novee's review. Catching this means modeling the whole trigger graph and the trust boundary each edge crosses, not grepping for a bad string. It is the same blind spot that lets secrets baked into a project's defaults survive review: the danger is in the configuration, not the code the scanner was built to read.

What to do this week

None of this needs a vendor. The fixes are configuration you already control:

  • Upgrade actions/checkout to the patched version, but treat it as step one, not the whole job.

  • Find every workflow that uses pull_request_target, workflow_run or issue_comment, and ask whether it truly needs to run untrusted code with secrets attached. Most do not.

  • Set permissions: {} at the top of each workflow and grant the narrowest token scope per job. Cross-workflow escalation only bites when the privileged job holds a broad GITHUB_TOKEN.

  • Never drop github.event data, branch names, titles or comment bodies, straight into a shell step. Pass them through an environment variable instead.

  • Put a required-approval gate on any job that can reach production secrets, so a first-time contributor's pull request cannot touch them unattended.

For the hunt, pull the run history for those three triggers and flag any job started by a forked or first-time contributor that also read a secret or wrote to $GITHUB_ENV. That intersection, an untrusted source plus a privileged action, is the signature, and it is sitting in your Actions logs right now. The pattern is close kin to developer tooling quietly stealing credentials: the theft happens inside infrastructure your team already trusts.

This is going to get worse before it gets better

There is a reason the pattern is surfacing at scale now. The unsafe pull_request_target-plus-checkout idiom is everywhere in public code, so the AI coding assistants now writing pipelines have learned it as normal and reproduce it by default. The supply of vulnerable workflows is growing faster than anyone is auditing it. The teams that come out ahead will stop treating CI configuration as plumbing and start treating every workflow trigger as an untrusted input, which is what it always was.

Topics

Frequently asked questions

What is the Cordyceps CI/CD vulnerability?

Cordyceps is a class of CI/CD weaknesses in GitHub Actions where untrusted pull-request input runs inside a privileged workflow. Novee Security named the pattern and confirmed more than 300 exploitable repositories. A free GitHub account is enough to run code as a maintainer, steal build secrets, or push tainted output downstream.

Does upgrading actions/checkout fix Cordyceps?

No. The patched actions/checkout only blocks checkouts of a fork pull request's head or merge commit under pull_request_target and workflow_run. It does nothing for command injection through branch names, broken authorization checks, or artifact poisoning between workflows. Treat the upgrade as one step, not a complete fix.

Which projects were found vulnerable to Cordyceps?

Novee Security confirmed exploitable workflows at Microsoft's Azure Sentinel, Google's AI Agent Development Kit, Apache Doris, Cloudflare's Workers SDK, and the Python Software Foundation's Black formatter. Its scan of roughly 30,000 high-impact repositories flagged over 300 as fully exploitable. The affected vendors applied hardening after disclosure.

How do I check if my GitHub Actions workflows are exposed?

Find every workflow using pull_request_target, workflow_run or issue_comment, and check whether it runs untrusted code with access to secrets. Confirm no branch name, title or comment body reaches a shell step directly. Then review run history for jobs started by forked or first-time contributors that touched a secret.

Why did security scanners miss Cordyceps?

Most scanners analyze one file at a time. The most serious Cordyceps flaws exist in the relationship between two workflows, each safe alone but unsafe in how data passes between them. Detecting that requires modeling the full trigger graph and its trust boundaries, which single-file pattern matching cannot do.

Is there a CVE for Cordyceps?

No single CVE covers Cordyceps. It is a systemic configuration pattern across many independent repositories, not one product defect, so it was disclosed privately to each affected project rather than tracked under one identifier. The fix is workflow configuration, not a vendor patch you wait for.

Ready to meet the Guardians?

Deploys fast - agentless for monitoring and cloud, a lightweight agent for deep endpoint security. Just Suriq, standing watch.