GhostAction put the exfiltration inside the build
The attackers did not break a product. They pushed a file called `security-audit.yml` into other people's repositories and let CI do the stealing — with the repository's own secrets, the credentials in the working tree, and the ones in deleted commits.
GhostAction has been running since September 2025. Its October 2026 wave used two compromised maintainer accounts — one belonging to the author of an 18,400-star game engine — to push a workflow into more than 340 repositories, across 817 repositories and 327 GitHub users in total, exfiltrating 3,325 secrets. The workflow was named security-audit.yml or github_actions_security.yml, and it posted what it collected to a hard-coded IP address over plain HTTP.
The entry point was almost certainly a leaked personal access token from infostealer logs or a credential dump. No vulnerability was exploited. The attacker held a maintainer's credential and used it for exactly what it was issued to do: commit to repositories.
The mechanism
Three things about this campaign are worth separating out, because they fail differently.
The history walk is the part people underestimate. Scanning the working tree finds the secrets you know about. Walking every commit finds the ones someone committed, noticed, and deleted — which were never rotated, because deleting the line felt like fixing it. The injected workflow targeted 2,577 secrets across SSH private keys, Azure credentials, container registry credentials, database credentials, AWS access keys, FTP credentials, Google Cloud and Firebase credentials, GitHub tokens, Telegram, Slack and Discord bot tokens, and keys for Cloudflare, npm, PyPI and the AI providers. A repository's git history is a credential archive with no expiry policy.
The phishing wave is a response to defence. After GitHub moved to stop the workflow running, the actor registered my-github[.]com — pointed at the same IP used to receive the stolen data — and sent a message impersonating a GitHub security alert. The page was a reverse proxy: it harvested the password and the 2FA code as the victim typed them, then injected JavaScript that authorized an OAuth app with permission to modify workflows, exfiltrating the resulting token on TCP/8443. The point of the OAuth app is that it is a reusable API credential with exactly the permission GhostAction needs — enumerate repositories, write under .github/workflows/. The password and the second factor were a means to that end, not the prize.
The second stage outlives the cleanup. A file at public/fonts/fa-solid-400.woff2 — a fake font, the same filename used in North Korea's Fake Font campaign in January 2026 — carried a stealer for AWS keys, PyPI and npm tokens, Git logins, Kubernetes cluster credentials, SSH private keys and AI provider credentials for Anthropic Claude and Google Gemini. A VS Code task auto-runs it, and it installs a cron job that fetches and runs attacker-supplied code every five minutes. Removing the workflow does not touch any of that.
What we would have seen
A workflow file is text in a repository, and what it does is legible before it runs. The CI workflow integrity engine reads the change rather than trusting the author, which is the only approach that survives a stolen maintainer credential.
CI-WF-SECRET-HARVESTfires on a job that reads the whole secrets context — and only escalates when a second dangerous property is present, because reading secrets and making a network call is also what a legitimate deploy job does. Secrets pluscurlalone is a finding, not a critical.CI-WF-CREDENTIAL-HUNTis the distinctive one here: a workflow that sweeps the working tree or the git history for credential patterns. There is no benign version of a build step that greps every commit for AWS keys.CI-WF-IP-EXFIL-ENDPOINTcatches the destination — a bare IP address rather than a hostname, which is what you use when you do not want a domain to take down.CI-WF-REMOTE-EXECandCI-WF-OBFUSCATED-PAYLOADcatch the fetch-and-run shape and the encoding used to hide it.CI-WF-CAMPAIGN-NAMEis deliberately narrow: a workflow that presents itself as a security audit while doing none of the things a security audit does. The namesecurity-audit.ymlwas chosen so that a reviewer skimming a commit list would approve of it.CI-WF-UNPINNED-ACTIONandCI-WF-UNTRUSTED-CHECKOUTcover the original vector —pull_request_targetwith a floating tag, which hands a third party write access to your CI without anyone stealing anything.SECRET_LEAKEDrecords what actually left, so rotation gets a list.RULE-T2-014joins the tampered workflow to a secret from that repository appearing leaked or in use, which is the difference between a bad commit and a breach.RULE-BEH-IDP-GITHUB-002catchesoauth_application.createin the organization audit log — the phishing wave's end state. Five audit-log engines feed these rules: GitHub, Entra, M365, Okta and Google Workspace.
What to do
- 01
Diff your workflow files against a known-good commit
Not review them — diff them. The campaign's whole premise is that the commit came from an account you trust, so the question is not whether the author is legitimate but whether the file changed. Any add or modification under
.github/workflows/that you cannot tie to a reviewed pull request is in scope. - 02
Rotate from git history, not from your secrets manager
The workflow read every commit. Your secrets manager knows about the credentials you manage; the history holds the ones someone pasted and removed. Scan the full history for the same credential patterns and rotate what you find, whether or not you believe it was still valid.
- 03
Treat stolen publishing tokens as the real exposure
npm, PyPI and container registry tokens are the ones that turn this incident into someone else's incident. Revoke and reissue them first, and require provenance on publish so a stolen token alone cannot ship a release.
- 04
Audit OAuth app authorizations in the organization
List the OAuth apps with workflow or contents write permission and the accounts that authorized them. The phishing wave's product is an app grant, and it persists after the password is changed and the 2FA device is replaced.
- 05
Pin third-party actions by commit SHA
A floating tag means the code you run is whatever the tag points at today. Pin to a SHA, and restrict which identities can modify workflow files at all — the cheapest control here is that most contributors do not need write access to the build.
We removed the workflow within an hour. Are we clear?
How is this different from a compromised dependency?
Would detection have required reading our source code?
Why single out the campaign's name for the workflow?
security-audit.yml is designed to pass a glance at a commit list and to look like something a security team added. A rule that flags a workflow presenting itself as a security control while harvesting secrets costs almost nothing in false positives, because genuine security workflows do not sweep git history for AWS keys and post them to an IP address.Run it in your own account
One Docker Compose stack, your datastores, read-only credentials into your clouds. The Community edition is free to run in production.