Incident write-up10–11 November 2026

Agent-driven mass GitHub token attack

An old GitHub token of mine was used to run a secret-stealing workflow in 158 repositories. Then I looked wider: the same attack hit thousands of accounts across GitHub in a single day.

Xander Steenbrugge · 11 November 2026

On 11 November my git push was rejected. The main branch of my project had two commits I never made, under my own name: "ci: refresh build cache" and "ci: restore". They cancel each other out. The diff was empty.

that was the tell.

Someone had my GitHub token, and had been using it for 22 hours.

~62,000decoy pull requests opened across GitHub in 26 hours
2,226different accounts, in just the third of those PRs I could list
93%of those PRs still open a day later: most victims have not noticed
1 in 5landed in a repository the victim does not own

Public repositories only. Source: GitHub's own search and API, 11 November 2026.

It wasn't just me

My first assumption was a targeted attack. It wasn't. GitHub's public search shows how many pull requests were opened with the attacker's exact decoy title, "docs: dependency audit notes": zero on each of the nine days before, 15,369 on 10 November, 46,685 on 11 November, and zero since.

About 62,000 public pull requests in 26 hours, starting 10 November at 12:45 UTC. In a single five-minute window, 804 different accounts opened one. Private repositories are not counted. My own 154 were a quarter of a percent.

Public pull requests titled "docs: dependency audit notes" per hour (UTC), from GitHub's PR search. The 16h gap on 10 November coincides with a GitHub outage; the 02–05h gap on 11 November is when the attacker moved servers.

I pulled about a third of them (20,636 PRs) through GitHub's API. That third alone came from 2,226 different accounts, into repositories of 3,743 different owners. 93% of those pull requests are still open, a day later. And one in five landed in a repository the victim does not own (an organisation, a client, an old collaboration), which is how one leaked token becomes many people's problem.

The decoy is only the visible part. GitHub's commit search finds about 14,300 workflow injections ("ci: refresh build cache") in public repositories over the same two days, and 1,680 commits of the install-script variant described below, in 782 repositories.

Other people have posted on their own decoy PRs what was used against them: a leaked GitHub CLI login, a third-party OAuth app token, a stolen key, a project's bot account. Mine was a classic personal access token. None of them names a common app or tool. So this is not one leak. It is a pooled list of stolen credentials, replayed by a script. Security firms have described near-identical waves since 2025 under names like GhostAction and Gheist, with different servers and commit messages each time. As of 11 November there is no write-up of this one and no statement from GitHub; the only public discussion I found is a thread on a Chinese developer forum and a Reddit post.

How it works

With one stolen token the kit lists every repository that token can administer and works through them alphabetically, about a minute each:

  1. DecoyA pull request from a branch called wfx-docs with one harmless file. It makes the activity look boring.
  2. InjectIf the repo has CI, about 1,500 lines are added to an existing workflow on the main branch. If not, a throwaway branch gets a new workflow called "Dependency Audit" plus four random characters.
  3. StealThe run copies every Actions secret and variable, reads the build machine's memory to recover masked secrets, grabs cloud login tokens, and scans the last 100 commits for anything key-shaped. All of it goes to a server over plain HTTP.
  4. EraseA second commit restores the workflow. The run is deleted. The throwaway branch is deleted.
There is a second variant that worries me more. Instead of CI it adds a postinstall script to package.json ("chore: add ci helper"). That runs the same stealer on any machine that installs the project, a developer's laptop included. It hit 782 public repositories, including the webdriverio project through its bot account. The worst case would be a poisoned release on npm; of the 94 affected projects that publish to npm and that I checked, none has shipped one since the attack.

Check your own account

Look for

The step the kit cannot hide is deleting its own workflow runs. Go to Settings → Security log and search action:workflows.delete_workflow_run. Deletions you did not make mean your token is on someone's list.

If you find any of this: revoke the token, and check all four places tokens live (classic tokens, fine-grained tokens, authorized OAuth apps, authorized GitHub Apps). Then rotate every Actions secret in every repository that token could administer. A repository with no trace left is not a repository where nothing was taken: in public repos the kit deliberately leaves nothing behind.

What I did wrong

The token was from 2022. It had every permission and no expiry date. I kept it in a plaintext notes file, and I pasted it into project folders that were later copied whole into Docker images. .gitignore kept it out of git. It did not keep it out of the images, because that is a different ignore file.

In 2022 I was a beginning programmer with zero experience in security. I would never make these mistakes today, but there it is.

It had admin rights on 158 repositories, most of them old collaborations I no longer work on. That is how my mistake became other people's problem. I am notifying each of them directly, and I am sorry.

Nothing alerted me. No email for a push to main, none for a deleted workflow run. I found out because a push bounced.

How the attacker got the token is still not proven. It was never used before this week, and it is not on my laptop. The candidates are a Docker image that carried it, or malware on some machine that once held a copy. I will update this when I know.

What I'd do today if I were you