Deleting a key from your code doesn't delete it.
Somebody pasted a password into the code years ago and took it out the next day. This week a fake "Security Audit" on GitHub went looking for exactly that kind of leftover, and it found plenty.

A key that was ever committed to your code is still in the project's history, even if someone deleted it years ago, and the fake "Security Audit" workflow spreading on GitHub this week reads that whole history. Ask your developer to scan every branch's history for keys and rotate anything it finds, check for the workflow files named in the reports, make changes to workflow files go through review, and clear out old access tokens and people who have left.
"We took that key out years ago"
Here's a conversation worth having on Monday.
You ask your developer, or the agency that built your app, whether any passwords or API keys have ever been typed straight into the code. Most of the time the honest answer is something like: "Once or twice, early on, but we took them out."
That answer used to sound fine. This week it stopped being enough.
On October 8, two well-known open-source developers had their GitHub accounts used by someone else. In two bursts lasting minutes, those accounts added a file called "Security Audit" to 345 code repositories, according to StepSecurity's write-up, including one that belongs to Uber. The file wasn't an audit. It collected every key it could find and sent them to a server the attacker controls. Socket, another security firm, says it has since counted more than 500 GitHub accounts committing the same file to tens of thousands of repositories since October 7. The firms' counts differ and keep moving, so treat that as a sense of size rather than a precise number.
What's new this time, and the reason this note exists, is where it looked. It didn't stop at the keys sitting in the code today. It went back through the project's entire history, every change ever made on every branch, and pulled out keys that people had deleted long ago.
What the fake audit actually does
In plain terms, here's what StepSecurity found when it took the file apart.
GitHub lets a project run small automated jobs, called workflows, whenever code changes. Teams use them to run tests, build the app and publish new versions. Those jobs often need keys to do their work, so GitHub keeps them in a protected store and hands them to the job when it runs.
The attacker's workflow was set to run on any change to any branch. When it ran, it did three things. It sent off the protected keys the project had set up for its jobs. It searched the current code for thirteen kinds of keys, including Amazon Web Services keys, API keys for OpenAI, Anthropic and OpenRouter, GitHub and GitLab tokens, Slack tokens and Google keys. And then it ran the same search across the full history.
It was added directly to the main branch under the real developer's name, with the commit message "Add security audit workflow." No pull request, nobody asked to review it. On the Uber project, StepSecurity says the attacker's server confirmed it had the data four seconds after the job started.
How did the attacker get into those accounts? Nobody has said for certain. StepSecurity's best guess is a leaked personal access token, the long-lived kind of password developers create so their tools can talk to GitHub, picked up from the malware logs and password dumps that get traded around. As of its write-up, no poisoned software releases had been published with the stolen keys. That's good news with an expiry date: the keys work until somebody replaces them.
How the work moves
What the fake audit does, as StepSecurity describes it
- A trusted account is used
Most likely a leaked personal access token belonging to a real developer.
- The file lands on the main branch
"Add security audit workflow," under the developer's own name, with no review.
- The change itself triggers it
It runs on any change to any branch.
- Keys are gathered
The project's protected job keys, the current code, and the full history.
- Everything goes out at once
One request to a bare IP address; on one project, confirmed four seconds after the job started.
Deleting a key hides it. Only replacing it makes it useless.
Why deleted doesn't mean gone
This is the part that catches business owners out, so it's worth slowing down.
The tool almost every team uses to track code, Git, is built to remember. Every change is saved, so you can see who changed what and go back if something breaks. When a developer removes a key from a file, Git doesn't erase the old version. It records a new version without the key, and the old one stays in the history, one command away for anyone who can read the project.
So "we took it out" means the key is no longer in today's code. It doesn't mean the key is no longer in the project. If that key still works, it's as exposed as the day it was added. StepSecurity puts it bluntly: a secret committed once in 2019 and deleted the next day is harvested just the same.
Two more things make this worse for private projects. Socket points out that private repositories are where committed keys actually turn up, because people relax when they think nobody else can see the code. And copies count. A fork, a mirror or an old backup of the project carries the same history with it.
The fix isn't to scrub history and hope. Rewriting history is fiddly, and copies may already exist. The fix is to make the old key worthless: issue a new one, move everything over to it, and switch the old one off. We've written about doing that without breaking things.
Decision guide
What we tend to assume, and what's actually true
| Consideration | The comfortable assumption | What this campaign showed |
|---|---|---|
| Old keys | We deleted it, so it's gone. | It's still in the history until the key itself is replaced. |
| Private code | Nobody outside can see it. | Anyone with a working token for it can, and private projects are where keys turn up. |
| Changes | Everything goes through review. | Not if someone can push straight to the main branch. |
| A file named Security Audit | Someone added a scanner. | Check who added it, when, and why. |
| Our job keys | They're locked away in GitHub. | Any job that runs can use the ones it asks for. |
The setting that stopped it
One detail in the report is worth more than the rest.
On October 7, a compromised account tried to re-run the fake audit on a project it had already infected, by pushing an empty change and a small edit to the readme. Both runs stalled. That project had been set up so its automated jobs needed someone's approval before they ran, and nobody approved them. StepSecurity calls it the single control it saw stop this campaign's theft that week.
You don't need to know how to switch that on, but you should know it exists and ask about it. Along with it, three questions are worth putting to whoever looks after your code:
- Can anyone change our workflow files without a second person looking? The folder that holds them, .github/workflows, controls what runs with your keys. Changes there should go through review, and the main branch should be protected so nobody, admins included where possible, can push straight to it.
- Who has write access, and what tokens are out there? List the people and the tokens. Remove anyone who has left, including old agencies and freelancers. Swap long-lived personal tokens for ones that are limited to one project and expire.
- What can our jobs reach? A job that only runs tests shouldn't hold the key that publishes your app or reaches your customer database. The less each one holds, the less a fake one can take.
None of this is exotic. It's the same housekeeping as removing access when someone leaves, applied to the place your code lives.
If you find it
First, look. Your developer can search your organization's code on GitHub for the file names in the reports, security-audit.yml and github_actions_security.yml, and for the attacker's address, 193.32.204.199. StepSecurity's post lists the exact searches. The reports say to check back to August 31, and to check every branch and fork, not just the main one.
If it's there, StepSecurity's advice is to treat it as a confirmed breach, and the steps run roughly like this:
- Replace everything it could have reached. That means every key set up for your automated jobs, and every key ever committed anywhere in the history, whether or not it's still in the code.
- Shut the door it came through. Find the account or token that added the file and revoke it. If a token leaked from someone's computer, have that computer checked before they sign back in.
- Remove the file from every branch, and look through past job runs for the names and address above.
- Hold off on releases until it's clean, especially if any of those keys can publish your app or package.
If your team read our note on the poisoned JavaScript package, this is a cousin of that problem with a different entry point: not a package you installed, but a file someone else committed under a trusted name.
A message you can send today
If you'd rather not translate all this yourself, here's a short message you can paste to your developer or agency as it stands:
Have you seen the GitHub "Security Audit" workflow campaign from this week? Could you check three things for us? First, search our repositories, including other branches and forks, for security-audit.yml, github_actions_security.yml and 193.32.204.199. Second, run a secret scan over the full Git history, not just the current code, and send me a list of any keys that were ever committed, with a plan to replace the ones that still work. Third, tell me who can push to our main branch or change workflow files without a review, and which personal access tokens exist for our projects. I'm not expecting bad news. I'd just like to know.
A good developer will be glad you asked, and most of it is an afternoon's work. If the answer to the second question is a long list, that's not a disaster. It's a list, and every key on it can be replaced.
When we build or take over a web app, knowing where the keys live and who can change what runs with them is part of the job, not a cleanup for later. If you'd like help working through yours, our web application work covers it, and you can tell us what you're running.
Related work: Sutton Place.
We build alongside the people doing the work.
Bring us the problem you’re thinking about.