GitGuardian reported this week that the GhostAction campaign reached more than 30,000 repositories across about 3,000 users and organisations on 8 and 9 October, pushed there from roughly 1,900 compromised GitHub accounts. It tried to extract 3,174 secrets.
Tried is the operative word, and it is the word doing the most work in every headline about this campaign. Nobody has published how many it got.
The one wave with a measured outcome
There is an answer for the earlier wave, and GitGuardian published it. Between 31 August and 30 September, the same operator pushed its workflow into 772 public repositories belonging to 373 users and organisations, aimed at 2,577 secrets.
What happened next is the part worth reading twice. Of 3,669 workflow runs collected across 605 repositories, only 499 executed, in 32 repositories. Of those, 336 runs completed successfully, exfiltrating 26 secrets from 13 repositories.
Twenty-six. From a campaign that put malicious code into seven hundred and seventy-two repositories.
Why the gap is that wide
GitHub Actions does not run a workflow pushed by an arbitrary account the way a script runs when you type it. Most of the collected runs were held for approval and never executed. Many of the ones that did execute were triggered later, by a legitimate push from a real maintainer who had no idea the workflow was sitting there.
The October wave ran into something more direct. GitHub started preventing the malicious workflow from running on 9 October, which is the day after it began. GitGuardian records waves on 10 and 12 October that could not extract any secrets at all.
So the thirty-thousand figure describes how many repositories the operator reached, over roughly two days, before the platform shut the execution path. It is a measure of write access, not of loss.
What it actually is, and what it is not
There is no vulnerability here and no patch, which is the thing most worth saying plainly. The operator already had the ability to push to these repositories, using stolen personal access tokens and credentials from infostealer logs and old dumps, some of them compromised months or years earlier. Every commit was made under the victim's own identity.
The payload is a workflow file with a reassuring name — security-audit.yml in October, github_actions_security.yml and security-check.yml earlier — that reads the repository's secrets and posts them to an address with a single curl call. GitGuardian tracked three versions of the October payload: the first scanned the working tree and git history against eleven secret-detection patterns and went to about 1,800 repositories, the second added case-insensitive matching and context around AWS key identifiers, and the third scanned up to 200,000 lines of git log.
The targets, from the earlier wave's haul, were ordinary infrastructure credentials: 446 SSH keys and deployment credentials, 218 Azure credentials, 142 container registry logins, 112 database credentials, 106 AWS access keys.
The number that should worry people is the cleanup one
As of 5 October, GitGuardian found that only 124 repositories, about 16 percent, had been effectively cleaned in observed public history.
That is the figure with a real consequence attached. A malicious workflow left in a branch or a fork runs the next time something triggers it, and the twenty-six successful exfiltrations from the earlier wave happened largely because legitimate pushes set off workflows that had been sitting dormant. The campaign's yield is low per repository and the repositories stay loaded.
The counts also disagree, as they usually do. Socket reported 346 repositories hit in two automated sweeps lasting minutes, and more than 500 accounts committing the workflow since 7 October. GitGuardian does not reconcile the two, and the gap most likely reflects different detection methods and different windows. We have written before about a ransomware count that is mostly unverified claims and a victim figure that turned out to be a database's row count; this is the same shape of problem, with the added difficulty that the only honest answer for October is that the yield has not been published yet.
This is also the second campaign in a fortnight that turns on package and repository identity rather than on a flaw. We covered an npm trusted publishing flow that checked the package name and not the identity behind it earlier this month.
What to do
- Search every branch and every fork for security-audit.yml, github_actions_security.yml and security-check.yml, and treat any repository that has carried one since 31 August as compromised. Removing it from the default branch is not removing it.
- Rotate every secret the workflow could read, then revoke the GitHub credential that allowed the push. GitGuardian is explicit that rotation on its own is not enough, because the access that put the file there is still valid.
- Check commit history for workflow changes made under your own identity that you did not make. That is what this looks like from the inside.
- Review Actions run history for held or approved runs you did not expect, and for runs triggered by ordinary pushes against workflows you do not recognise.
- Audit CI images for cryptominers while you are in there. GitGuardian found thirteen GhostAction victim repositories also used for mining, across at least four separate campaigns, and says it is not convinced the same operator is behind both.
What is not established
- How many secrets the October wave actually took. No figure has been published, and GitHub's block on 9 October cut it short.
- How the GitHub credentials were obtained in the first place. GitGuardian does not establish the initial access method for either wave.
- Who is behind it, or whether one operator or several are sharing the same stolen credentials.
- Why GitGuardian's October figures and Socket's are so far apart.
- Whether the cryptomining activity found in the same repositories shares an operator with GhostAction. GitGuardian says it is not convinced it does.