← Back to all posts
Published on · by Renaud Deraison

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.

One stolen token, one workflow, every commit ever madeMaintainer's laptop~/.config/gh/hosts.yml~/.git-credentialsGH_TOKEN=ghp_… (real)infostealer log→ token for salelikely source, per THNsecurity-audit.yml in 318 repos, 16 minon: push (any branch), workflow_dispatchfetch-depth: 0git log -p --all | grep 13 patternsHistory of config/test.pya1f3 + KEY="sk-ant-api03-…" ← foundc92e - KEY="sk-ant-api03-…" + KEY=os.environ["…"]Clean tree, key still in the history.193.32.204.199POST (plain HTTP)REPO=…PYPI_PASSWORD=…sk-ant-api03-…AKIA_CTX_START…current keys anddeleted ones alikeEvery fork carries the same history. Socket counted 279 forks in the henrywoo namespace alone.
The GhostAction chain. A token from a developer machine commits the workflow, the workflow reads every commit ever made, and every key in that history, current or deleted, goes out in one POST.

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.

In Bromure: the history holds stand-ins, and the real keys never left the MacMachine VM (Ubuntu)GH_TOKEN=ghp_… (stand-in)ANTHROPIC_API_KEY=sk-ant-api03-brm-…The agent's commit+ KEY="sk-ant-api03-brm-…"stealer posts ghp_ stand-in outgit push security-audit.ymlno real secret in the VM to takeProxy, on the MacReal value swapped in only forthe credential's own hostStand-in to another host → 451,VM paused, alert on the Macgit-receive-pack → Prompt before writeSecurity Timeline: every swap,token, host, machine, timeGitHub historysk-ant-api03-brm-…GhostAction's grepmatches sk-ant- andships it outAnthropic: 401GitHub: 401the buyer gets stringsthat work nowhereKept on the Mac, outside the VMAnthropic, OpenAI and OpenRouter keys · the GitHub token · AWS keys · SSH private keys · traces · Security Timeline
The same chain against a Bromure machine. The history the workflow reads holds stand-ins, the token a stealer finds in the VM is a stand-in, and any request carrying one to the wrong host stops on the Mac.

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.