The deleted key was still in the history
On October 8, a campaign called GhostAction used two hijacked maintainer accounts to commit a workflow named security-audit.yml into 346 repositories, including Uber's athenadriver and the 18,000-star pyxel. By October 9, Socket counted more than 500 accounts and tens of thousands of repositories. The workflow runs git log -p --all and greps every commit ever made for Anthropic, OpenAI, OpenRouter, GitHub and AWS keys, including the ones deleted a commit later. In Bromure Agentic Coding, the agent only ever holds stand-ins, so the keys it writes into a repository's history are worth nothing to the person who finds them.
Deleting a key from a file takes one commit. The commit before it keeps the key for as long as the repository exists.
At 13:40 UTC on Thursday, October 8, the GitHub account of Takashi Kitao,
author of the game engine pyxel, started committing a file called
.github/workflows/security-audit.yml. Four minutes later it was in 27 of his
repositories. At 21:10 UTC, the account henrywoo, original author of Uber's
athenadriver, began the same sweep. By 21:26 it had reached 318 repositories,
including 279 forks and uber/athenadriver itself, where the account has
write access.
Socket published its
analysis on October 9
and attributed the commits to GhostAction, a credential-theft campaign first
seen in September 2025. By the end of that day, Socket's update counted more
than 500 GitHub accounts that had committed the workflow to "tens of thousands
of repositories" since October 7. The Hacker
News
and StepSecurity covered the same wave. Socket found that the workflow had
already run in affected repositories, and that it was still sitting on the
default branches of kitao/pyxel and uber/athenadriver when the post went
up.
What security-audit.yml does
The workflow is called "Security Audit." It runs on workflow_dispatch and on
every push to any branch or tag, with no filter. Its checkout step uses
fetch-depth: 0, which pulls every branch, every tag and the full history.
Then a single step does two jobs.
The first job goes after the repository's GitHub Actions secrets. Before
committing the workflow, the attacker read the repository's existing workflows
and wrote the names of the secrets they use into the payload. For pyxel, those
were CARGO_REGISTRY_TOKEN, PERSONAL_ACCESS_TOKEN, PYPI_PASSWORD and
PYPI_USERNAME.
The second job is new in this wave. The step greps the working tree for 13
credential patterns, then runs git log -p --all and greps up to 200,000
lines of history for the same patterns:
- AI services: Anthropic (
sk-ant-), OpenAI project keys (sk-proj-), OpenRouter (sk-or-) - Source control: GitHub classic tokens (
ghp_), fine-grained tokens (github_pat_), GitLab tokens (glpat-) - AWS: access key IDs, temporary key IDs, secret keys and session tokens. The step also keeps two lines of context around each key ID, so the secret key committed next to it comes along.
- SaaS and cloud: Google API keys, Slack tokens, SendGrid keys
Everything goes out in one curl POST over plain HTTP to 193.32.204.199.
Every run reports the repository's name, whether it found anything or not.
Socket points at the audience. Two of henrywoo's repositories are
pyllama and chatllama, machine-learning projects whose contributors and
forkers "likely hold these key types." An Anthropic or OpenAI key can be
resold as billable inference, and every prompt sent through it passes through
the buyer's hands.
The history keeps everything
Socket's remediation advice has a line that should worry anyone who has
cleaned up a leaked key by editing a file: "Rotate every credential found in
any commit, whether or not it is still in the current tree." A key removed in
the next commit is still in the commit before it. git log -p --all reads both.
Coding agents add to that history quickly. GitGuardian's State of Secrets Sprawl 2026 found that public commits co-authored by Claude Code leaked secrets at 3.2%, against a 1.5% baseline across public GitHub. The same report counted 1,275,105 leaked secrets tied to AI services in 2025, up 81% from the year before.
The pattern is familiar to anyone who has watched an agent make a failing
integration test pass. The agent has the key in its environment. It writes the
key into a config file, a test fixture or a .env that isn't ignored yet,
because that makes the test pass. It commits. Later, the developer or the
agent moves the key to an environment variable and commits again. The tree
looks clean. The history has the key, every fork has the history, and a
workflow with fetch-depth: 0 reads it.
The token that wrote the workflow
Socket did not determine how the two accounts were taken. The Hacker News
names the most likely source, a personal access token from infostealer logs
or credential dumps. Infostealer logs come from developer machines. On a
laptop, the GitHub token sits in ~/.config/gh/hosts.yml, in
~/.git-credentials and often in GH_TOKEN, readable by anything running as
the developer, including whatever a coding agent installs or runs.
One token is enough. With it, the attacker can list the repositories the
account can write to, read their workflows for secret names, and commit to
each. The henrywoo sweep covered 318 repositories in 16 minutes.
The same repository, written from a Bromure machine
In Bromure Agentic Coding, the agent runs inside a Linux VM on your Mac, and every HTTP request the VM makes crosses a proxy on the host. Your Anthropic key, your GitHub token and your AWS keys stay encrypted on the Mac. Now let GhostAction loose on a repository that a Bromure machine wrote, and on the developer who wrote it.
The history holds stand-ins
Inside the VM, every credential is a stand-in with the real thing's shape:
ANTHROPIC_API_KEY is sk-ant-api03-brm-…, an OpenRouter key is
sk-or-v1-brm-…, GH_TOKEN and ~/.git-credentials hold ghp_ plus 36
characters. Each one is derived from the real value and a per-install salt
with HKDF-SHA256, and can't be turned back into it. The proxy swaps the real
value in on the wire, and only for a request to the host the credential
belongs to.
So when the agent writes its key into config/test.py to make the test pass,
it writes what it has. When it commits, the commit holds sk-ant-api03-brm-….
When the cleanup commit comes later, the history keeps that same stand-in.
GhostAction's grep finds it. sk-ant- matches sk-ant-api03-brm-, and ghp_
matches a stand-in GitHub token. The POST to 193.32.204.199 carries them,
and the buyer tries them. Anthropic rejects the key and GitHub rejects the
token, because the only thing that turns them into working credentials is the
proxy on your Mac, for requests leaving your VM. Socket's advice to rotate
every key found in any commit has a short answer for this repository: none of
those keys ever worked outside your Mac, and the AI-provider stand-ins carry
-brm- in their names, so you can spot them in a history scan.
The token on the laptop is a stand-in too
The other half of the campaign is the account itself. Run your agents in a
Bromure machine, and the GitHub token they use sits on the Mac. Anything that
runs in the VM, whether a package's install script, a stealer or a prompt
injection, finds a ghp_ stand-in in ~/.git-credentials and GH_TOKEN. An
attacker who replays it from their own machine gets a 401 from GitHub.
The stealer also has to send what it took somewhere, and that request crosses
the proxy. A stand-in has one legitimate destination, the host it was minted
for. The proxy scans every outbound request, headers and body, for any
stand-in headed somewhere else. A ghp_ token on its way to a stealer's
server gets HTTP 451, nothing is forwarded, the VM pauses, and This
environment may have been compromised opens on the Mac with the token
preview, the credential's name, the host it belongs to and the host it was
sent to.
A workflow push waits for a click
Worms in this family don't always wait for a buyer. The
tensorlake payload last week
committed files into its victims' repositories straight from the developer's
machine. New Bromure machines use Prompt before write for every git
forge. A push is a write, so committing security-audit.yml to a repository
from the VM pauses on a macOS dialog, Allow write on "github.com" from
workspace "…"?, showing POST /owner/repo.git/git-receive-pack as it
appeared on the wire. The henrywoo sweep made 318 of those writes in 16
minutes. From a Bromure machine, the first one waits for you, and you didn't
ask your agent to add a security audit to 318 repositories.
Every use of the token is on record
The Security Timeline records every credential swap as a Credential
brokering row: which token, which host, which machine, at what time. When
you need to know whether your GitHub token pushed anything on October 8
between 21:10 and 21:26, the answer is on your Mac, in a record the agent
can't edit. With tracing on, bromure-cli trace leaks lists every request
where a token appeared on the wrong host.
Cleaning up after a key
The standard advice after a leaked key is to rotate it, rewrite history with
git filter-repo, force-push, and hope no fork or mirror copied the old
commits first. GhostAction shows why that last part fails. Socket counted 279
forks in one namespace, each carrying the history and, now, the workflow. A
key that reached a public commit is gone, whatever the current tree shows.
A Bromure machine changes what reaches the commit in the first place. The
agent can be as careless with keys as GitGuardian's numbers say agents are. It
can paste a key into a fixture, commit a .env and forget about it. It never
held a working key, so none of that history can hurt you.
The commit holds a stand-in
Agents in the VM only see stand-ins shaped like the real keys. A key
written into a file and committed is a -brm- string that works only
through the proxy on your Mac.
The harvest gets 401s
GhostAction's patterns match the stand-ins and ship them out. Anthropic, OpenAI and GitHub reject them everywhere outside your Mac.
A stolen token stops the machine
The GitHub token in the VM is a stand-in too. Sent anywhere but GitHub, it gets a 451, the VM pauses, and an alert names the token and the host.
Pushes wait for a click
Prompt before write holds every push from the VM on a macOS dialog showing the request, and the Security Timeline records each use of the token.
The next grep
This is GhostAction's third wave in thirteen months. The first, in September 2025, took 3,325 CI secrets. The second, from August 31 to September 30 this year, went after 2,577 secrets in 772 repositories. This one added the full history and three AI-provider patterns. The next wave will add more patterns, and the history it reads only grows.
Run your agents where the keys they touch are stand-ins, and the history they leave behind holds nothing a buyer can use. For more on how stand-ins work and what happens when one leaks, read The agent leaked its own key and The key never expired.