Tenki’s startup program is live: up to $50K in credits and grants.Apply
Security

Cordyceps Exploits pull_request_target in 300+ Repos

Eddie Wang
Eddie Wangengineering

Share Article:

Novee Security scanned 30,000 open-source repositories last week and found that over 300 of them hand full CI/CD privileges to anyone with a free GitHub account. The vulnerability class, which Novee calls Cordyceps, after the parasitic fungus that hijacks its host, centers on a single GitHub Actions trigger: pull_request_target. Affected projects include Microsoft Azure Sentinel, Google's AI Agent Development Kit, Apache Doris, Cloudflare Workers SDK, and the Python Software Foundation's Black formatter. There's no CVE. There's no vendor patch. The fix requires every affected repository to change its workflow configuration individually.

How pull_request_target becomes a backdoor

GitHub Actions has two triggers for pull requests. The regular pull_request trigger runs with read-only access and no secrets. That's fine for most CI jobs. The pull_request_target trigger exists for a legitimate purpose: it lets maintainers run workflows that need write access (labeling PRs, posting comments, deploying previews) in the context of the base branch, with full access to repository secrets.

The problem is what happens when that privileged workflow touches untrusted input from the contributor's PR. If the workflow checks out the PR's head branch, runs scripts from it, or interpolates PR metadata (branch names, titles, comment bodies) into shell commands, the contributor's code runs with the maintainer's privileges. An anonymous outsider borrows the project's authority for a few minutes and uses it to steal credentials, push malicious code, or forge CI approvals.

As Elad Meged, Novee's founding engineer, put it: "No org membership or special privileges; a free account is enough to forge approvals, push code, or steal credentials."

# The dangerous pattern: pull_request_target with untrusted checkout
on:
  pull_request_target:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # This checks out the CONTRIBUTOR'S code
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      # ...and runs it with REPOSITORY SECRETS
      - run: npm test
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

That YAML is the entire attack surface. The workflow runs from the target branch (so it's trusted), but it explicitly checks out the contributor's code and executes it with secrets. Any fork contributor can put whatever they want in that npm test step.

What Novee found at Microsoft, Google, Apache, and Cloudflare

Close-up of a dark terminal with code output representing the privileged workflow execution context where pull_request_target runs YAML from the target branch with full secret access

Novee's scan of roughly 30,000 high-impact repositories flagged 654 projects and confirmed 300+ as fully exploitable. The most severe findings spanned Fortune 100 infrastructure.

Microsoft Azure Sentinel: A single comment on a pull request could run anonymous attacker code on Microsoft's CI and steal a non-expiring GitHub App key. That key grants persistent write access to the security content Microsoft ships to customer Sentinel workspaces via the Azure Marketplace. Microsoft's Security Response Center confirmed the behavior.

Google AI Agent Development Kit: A single pull request against the adk-samples repository (9,200+ stars) could execute code on Google's CI and gain roles/owner over the associated Google Cloud project. Google confirmed the issue.

Apache Doris: Two independent zero-click attack paths. Path one: a comment on any PR runs attacker code and exfiltrates hardcoded CI credentials. Path two: a fork PR steals a token with full write permissions across actions, contents, packages, and pages. Apache confirmed and patched.

Cloudflare Workers SDK: A PR with a crafted branch name executed arbitrary commands on Cloudflare's CI runners. No production secrets were reachable from this specific entry point, but Cloudflare credited Novee and applied broad hardening across their workflows.

Python Software Foundation (Black): A pull request from anyone could run attacker code on Black's build systems and steal the automation token. That token can approve PRs as the project's own bot, opening a path from code injection to the main branch. Black handles 130 million installs per month, so a compromised release would ripple through the entire Python ecosystem.

Why actions/checkout v7 doesn't close this gap

On June 18, GitHub shipped actions/checkout@v7 with a safe-checkout-by-default change. Under pull_request_target, v7 now refuses to check out the PR head branch by default. If a workflow explicitly sets ref: ${{ github.event.pull_request.head.sha }}, v7 will block it unless the author opts in to the unsafe behavior.

That's a real improvement. But checkout safety only controls what code is checked out. Cordyceps is about what code the workflow itself runs. The workflow YAML lives in the target branch, not the contributor's branch. The attack vectors that remain open after v7 include:

  • Command injection via PR metadata: Branch names, PR titles, and comment bodies interpolated into shell commands with ${{ github.event.pull_request.title }} or similar expressions. No checkout needed.
  • Code injection via actions/github-script: Attacker-controlled input interpolated into JavaScript that runs at workflow time with the workflow's token.
  • Cross-workflow privilege escalation: A low-privilege workflow's output (artifacts, env files, workflow outputs) flows into a high-privilege workflow. Neither workflow is vulnerable alone. The exploit exists in the composition.
  • Broken authorization logic: Workflows that check a property that doesn't exist on the triggering event type, so the guard silently passes.

Checkout v7 closes the most common entry point. It doesn't close the class. The workflow YAML in your repository is executable attack surface, and until you audit the full execution pattern, you're exposed.

Why scanners miss it

Standard SAST, DAST, and workflow-linting tools miss Cordyceps because they evaluate single files in isolation. Every individual piece of YAML in these workflows is syntactically valid. The vulnerability only emerges in composition: untrusted data crossing a trust boundary across multiple workflow steps, or across multiple workflows entirely.

Novee described this in their disclosure: "A deterministic scanner checking one workflow file sees valid YAML. An attacker sees a 4-step chain to permanent credentials." Finding these chains requires understanding what a workflow is supposed to do, reasoning across trust boundaries, and proving the chain end to end.

Remediation: what to do right now

Digital shield icon over a circuit board representing the remediation patterns needed to lock down privileged CI/CD workflow execution against Cordyceps-class attacks

There's no vendor patch coming. This is a configuration problem, and the fix is configuration-level. Here's a practical remediation checklist:

  1. Audit every workflow that uses pull_request_target. Search your repo for the trigger and trace every path where untrusted input touches a shell command, script, or artifact.
  2. Add a fork guard. The simplest fix is restricting the job to same-repo PRs:
jobs:
  build:
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
  1. Upgrade to actions/checkout@v7. It won't fix the execution pattern, but it closes the most common checkout vector. If you must check out PR code, v7 forces you to opt in explicitly, which is at least a speed bump.
  2. Never interpolate PR metadata into shell commands. Instead of ${{ github.event.pull_request.title }} in a run: step, pass it through an environment variable so the shell doesn't evaluate it.
  3. Apply least-privilege permissions. Set permissions: at the job level to restrict the GITHUB_TOKEN to only what the workflow actually needs.
  4. Treat workflow YAML as security-critical code. Review changes to .github/workflows/ files with the same rigor you'd apply to authentication middleware. Every PR that touches workflow configuration is a potential privilege escalation.

Catching workflow misconfigurations at the PR gate

Developer reviewing code on a laptop screen representing the PR review gate where workflow configuration changes must be caught before reaching the main branch

That last point deserves emphasis: reviewing workflow changes at the PR level. The Cordyceps pattern persists because these YAML files get treated as config, not code. Most teams rubber-stamp workflow changes or skip review entirely.

This is where automated code review becomes a security control, not just a quality tool. Tenki Code Reviewer reviews every PR against the full repository context, including workflow files. When a PR introduces or modifies a pull_request_target trigger, adds untrusted input interpolation into shell steps, or removes a fork guard, Tenki flags it with severity context before the change reaches the main branch. That's the gate where Cordyceps-class patterns need to be caught.

The agentic coding multiplier

Novee's research flagged another dimension of this problem. AI coding agents generate CI/CD configuration files at scale, and they reproduce the same insecure patterns over and over. An agent that copies a workflow template with an unguarded pull_request_target trigger doesn't just make the mistake once. It makes it across every repository it touches. The same misconfiguration compounds across millions of repos that never get a manual audit.

This is exactly the scenario where having a reviewer in the loop matters. Agents will keep generating these patterns until something stops them at the merge boundary.

Timeline and disclosure status

Novee published their research on June 23, 2026. Microsoft and Google both confirmed impact. Cloudflare, the Python Software Foundation, and the Apache Security Team applied fixes. Novee says there are millions of repositories potentially affected by the same pattern beyond the 300+ they confirmed.

There won't be a CVE for this. There won't be a single patch. Workflow misconfiguration is a distributed problem: each repository owner needs to audit and fix their own YAML. If your project accepts external PRs and uses pull_request_target, assume you're exposed until you've proven otherwise.

Tags

#cicd-security#supply-chain-security#pwn-request#cordyceps

Recommended for you

What's next in your stack.