Back
Blog
Insights
GhostAction: The Token Was Valid. The Machine Was Not.

Frank Lyonnet
Between 8 and 12 October, someone pushed a file called security-audit.yml into more than 30,000 GitHub repositories, belonging to about 3,000 users and organizations, most of them on 8 and 9 October. GitGuardian's analysis traces it to roughly 1,900 compromised GitHub accounts. The payload went first to about 160 repositories that had already been hit in earlier waves. Twenty minutes later, it started on 28,000 new ones.
The workflow runs on every push. It searches the repository and its entire git history with eleven regular expressions (AWS access keys, GitHub and GitLab personal access tokens, AI provider keys) and posts whatever it finds, over plain HTTP, to a single address. GitGuardian counted 3,174 secrets targeted. GitHub began blocking the workflow on 9 October, so the waves of 10 and 12 October extracted nothing. The first two days were enough.
The campaign is called GhostAction, and I think it is the clearest demonstration so far of a problem the industry keeps describing as something else.
Nothing was broken into
GitHub was not exploited. No vulnerability was used, no permission was bypassed, no approval was forged. Every one of those pushes was made with a valid credential belonging to the owner of the repository: personal access tokens and other credentials, reportedly taken from infostealer logs and credential dumps. GitHub saw exactly what it is built to accept. An authenticated push, from an account with write access.
I wrote earlier this week that almost every modern credential is a bearer credential: whoever presents it gets in, from any machine, anywhere. GhostAction is that sentence at the scale of 1,900 accounts and 30,000 repositories. The token was valid. The machine presenting it was not one its owner had ever used.
The detail that should worry you most
Some of the October victims had already been hit in earlier waves, in September 2025 or August 2026. GitGuardian's reading is that the attackers are likely relying, in part, on credentials stolen months or years ago and never rotated. Of the 772 repositories hit between August and September, only 124, about 16 percent, had been cleaned in their public history by 5 October.
GitGuardian draws the right lesson from it: rotating the secrets that were exfiltrated is not enough. You have to find and revoke the GitHub credential that pushed the workflow in the first place. Rotate the stolen secrets but leave that credential in place, and the next wave comes back through the same door.
Three things about this campaign are specific to 2026, and they change what a defense has to look like.
The shopping list now includes AI keys. Next to cloud credentials and registry tokens, the payload hunts for AI provider keys. They are spendable the moment they are taken, and a repository that integrates a model almost always holds one.
The response window is gone. Twenty-eight thousand repositories in two days, most of them before GitHub blocked the workflow, is not a campaign a security team answers one repository at a time. Detection after the fact is archaeology. The control has to sit at the moment the stolen credential is used.
The source is the developer's machine. Stolen tokens, infostealer logs and dumps are traded and reused across many actors. The credential that pushed
security-audit.ymlalmost certainly started its life on a laptop that was trusted the day before.
The industry knows where this goes: credentials bound to the device that holds them. Passkeys and device-bound session credentials are steps in that direction. A GitHub personal access token is not bound to anything. Until it is, the binding has to happen somewhere else.
Where my company EDAMAME breaks the chain
GhostAction has four stages: the credential is stolen from a developer's machine, presented to GitHub from the attacker's infrastructure, used to run a workflow that collects secrets, and left in place for the next wave. EDAMAME acts at each of them, and the most important one is the second.
1. At the moment of use: GitHub Enterprise conditional access
EDAMAME Hub integrates natively with GitHub Enterprise Cloud. It writes your organization's GitHub IP allow list from the devices that pass your policy, and removes a device the moment it stops passing. GitHub enforces that list on every authenticated request — web, API and git, personal access tokens and SSH keys included, public repositories included. Only anonymous access to public content is left open.
A token stolen from one of your developers and presented from an attacker's server is therefore refused, however valid it is. In GhostAction's terms, the push of security-audit.yml into your organization never happens. And it does not matter how the token was stolen, which is the property you want against a supply of credentials you will never fully see.
An IP address is a statement about where a device sits, so the precision of that control depends on the network. Over IPv4, devices that share one NAT address share their entry, and the allow list trusts the network a compliant device sits behind. Over IPv6, every device has its own global address, and EDAMAME writes each one as a single-address entry: the allow list becomes a list of devices, and trusting an IP is trusting a device.
GitHub is moving there as I write. Its documentation now says IPv6 support is being rolled out gradually across its services, and asks organizations to add their IPv6 addresses to the allow list before access is interrupted. EDAMAME Hub already writes one IPv6 entry per device, next to the IPv4 one. While the migration is in progress, part of the traffic still arrives over IPv4, which is one more reason for the second stage.
2. Where the token lives: the developer's machine
An infostealer does not take one secret. It sweeps: browser sessions, SSH keys, cloud credentials, git and registry tokens. EDAMAME Security grades a single process touching three or more kinds of credential as critical. That is the line between a tool picking the key it needs and a harvest, and our release gate replays real harvest shapes against it on macOS, Linux and Windows every release.
The two stages are connected. A machine that fails policy, because something on it is harvesting credentials or because its posture degraded, loses its place on the GitHub allow list. The access decision follows the state of the machine, continuously, without anyone opening the GitHub console.
3. Where the workflow runs: runners and build hosts
On self-hosted runners and build hosts where EDAMAME Posture runs, every job is observed, including one an attacker injected. A job that posts to an endpoint the runner pool has never used fails, and the destination is named. On GitHub-hosted runners, the EDAMAME Posture GitHub Action learns the egress of the pipelines that call it and fails the job on anything new. That is the defense for a compromised step inside your own workflow, the shape of the 2025 tj-actions incident. A workflow file written from scratch by an attacker will not call it, which is precisely why the first stage carries the weight against GhostAction.
4. After the fact: GitHub Audit, and the credential nobody revoked
The step GitGuardian says rotation alone misses is finding the credential that pushed the workflow. EDAMAME GitHub Audit is a one-click, read-only review of your GitHub organization's audit log, available as soon as GitHub is connected, before a single device is enrolled. It ties every push to the token that made it: the token's fingerprint, whether it is a classic or a fine-grained personal access token, and the user it belongs to, with the IP address and country the push came from when your organization has IP disclosure enabled.
A push of a new workflow file, made with a classic token, from a country the developer has never worked from, is the credential to revoke. It is also the answer to the repeat-victim problem: if you can see which token was used, you do not have to guess which one to rotate.
The lesson is not "rotate your secrets"
Everyone knows to rotate secrets. GhostAction's operators came back to repositories they had already hit because they expected the credentials to still work. The lesson is narrower and harder. A credential that works from anywhere will eventually be used from somewhere you did not choose. Bind access to the machines you trust, verify those machines continuously, and a stolen token goes back to being what it was before it reached GitHub: a string.
If your organization runs on GitHub Enterprise, you can see today which tokens touched it, from where, by running GitHub Audit. It needs no device enrollment: https://www.edamame.tech/github-audit
And the setup of conditional access for GitHub is documented here: https://github.com/edamametechnologies/integrations/wiki/Setting-Up-GitHub-for-Access-Control-Integration

Frank Lyonnet
Share this post



