
If you saw a random pull request titled docs: dependency audit notes show up in one of your repos back on November 10, 2026, you weren't alone. Thousands of developers saw the exact same thing. It looked like a boring docs update, so almost nobody paid attention to it.
Two days later, the picture looked very different. That harmless-looking PR was the first step in wfx-loot, an automated botnet that tore through more than 66,000 GitHub repositories, pulled secrets out of 1,806+ build pipelines, and deleted its own logs in under 60 seconds.
Did somebody find a zero-day bug in GitHub Actions? Nope. GitHub's servers worked fine.
The attackers didn't need an exploit because they already had 4,837 working developer and bot tokens. When we talk to engineering teams about DevSecOps, this is the exact scenario we warn them about: old tokens sitting on laptops or forgotten inside CI configs. And if you think rotating your GitHub secrets after a breach like this fixes the problem, wait until you see what this script does to AWS and Azure accounts before it deletes itself.
Old Leaked Tokens Did All the Work
Every single repo hit in this wave was unlocked with a real, valid token. Some were classic Personal Access Tokens (PATs) that didn't have an expiration date. Others were plaintext gh CLI tokens scraped off developer laptops, along with third-party app tokens like the 173 probes tied to Fly.io's GitHub App.
What really stings is how many of these tokens were recycled from older breaches. Back in October 2026, the GhostAction campaign hit a ton of repositories. In over 100 repos this week, wfx-loot dropped its payload right inside leftover GhostAction workflow files because those teams never revoked their leaked keys the first time around.
Because those 4,837 tokens belonged to real devs, private repos were just as exposed as public ones. Whenever a token had write access to a repo, the bot went in.
By November 12, teams at Aikido, OX Security, Endor Labs, and Chainguard had tracked 8,093 altered public default branches. Big organizations got hit alongside solo devs. Fortune 100 companies, the U.S. GSA, and French government agencies all showed up in the data. Even webdriverio/webdriverio had a hijacked bot push bad commits straight past branch protection before a maintainer caught it and rolled it back 20 minutes later.
How the 60-Second Attack Actually Ran
The whole thing runs on a script that finishes its job in less than a minute.
It starts with a simple permission check. Before dropping any real malware, the bot creates a branch called wfx-docs and opens that empty docs: dependency audit notes pull request. There is no bad code in it at all; the bot just wants to see if the token is allowed to push. Over 10,400 of these test PRs landed in company repos that the token owner didn't even own, and 92% of them went unnoticed.
Once the bot knows a token has write access, it drops a 1,500-line shell script into the build pipeline. It has three different ways of doing this depending on how the repo is set up. Sometimes it tacks the script onto an existing GitHub Actions file using a commit message like ci: refresh build cache. In Node projects, it might hide a .ci-xxxxxx.sh script inside package.json as an npm postinstall hook (chore: add ci helper). Otherwise, it just creates a temporary .github/workflows/build-audit-xxxxxx.yml file on a throwaway chore/xxxxxxxx branch.
As soon as the runner starts the job, the script grabs every credential it can find. It dumps GitHub Actions secrets, GITHUB_TOKEN, and OIDC tokens, and even reads the live Runner.Worker process memory directly. If there are local keys on the runner for AWS, Azure, GCP, Kubernetes, Docker, Terraform, npm, or PyPI, it takes those too, right alongside AI keys for OpenAI, Anthropic, Cursor, and Copilot. On private repos, it stashes the stolen data inside Actions caches, job summaries, and build artifacts starting with wfx-loot-.
Stealing keys isn't the worst part, though. If the build runner has live cloud access, the script builds a backdoor so the attackers can get back in even after you rotate your GitHub tokens. In AWS, it rewrites IAM role trust policies to include "AWS": "*", which lets any outside AWS account assume that role. In Azure, it adds rogue federated credentials starting with wfx-ci-.
Then it cleans up. Right after sending the zipped secrets out through HTTP POST or Telegram bots, the script runs a ci: restore or ci: cleanup commit to put the repo back the way it was, and calls the GitHub API to delete the workflow run and logs. Five minutes later, your repo looks completely untouched. The only thing left behind is a skipped number in your Actions run counter.
How It Spilled Into Public Packages
Around 50 of the stolen developer accounts also had permission to publish packages to public registries. Because their release workflows ran automatically when commits landed, a few infected builds went live before maintainers could stop them. That is how bad versions of digitalelvis/neuronai-studio (v3.14.3 on Packagist) and github.com/onuragtas/docker-env (v1.0.278 and v1.0.280 on Go) ended up in the wild.
What You Need to Check in Your Own Stack
If you run CI/CD pipelines or work with an enterprise IT delivery partner, don't just rotate your GitHub secrets and call it a day. Rotating a repo secret won't kick an attacker out if they already rewrote your cloud IAM roles.
Start with your cloud accounts. Open AWS IAM and check every role trust policy touched on or after November 10, looking specifically for "AWS": "*". If you use Azure, look through your App Registrations and Managed Identities for any federated credentials starting with wfx-ci-.
Next, check your GitHub history for the footprints the script couldn't hide. Search your org for wfx-docs branches, chore/ branches, or open PRs named docs: dependency audit notes. While you are in there, look at the run numbers in your GitHub Actions tab. Run numbers go in strict order, so if Run #82 jumps straight to Run #84, someone deleted Run #83 and you need to pull your org audit logs right away.
Finally, stop letting developers use non-expiring tokens. Classic PATs and plaintext gh CLI tokens sitting on laptops are the reason this botnet worked in the first place. Move your pipelines over to short-lived OIDC tokens locked down to specific branches so a leaked token from a dev laptop can never touch production across your product's network.
You can't catch a 60-second script with manual code reviews. If you want a hand auditing your CI/CD setup, locking down AWS and Azure IAM roles, or wiring up AIOps and DevSecOps alerts to catch weird runner activity while it happens, reach out to our team and let's get your build pipelines locked down.

