On October 25, Wiz Research published a campaign it had been tracking since September 15: an automated tool that takes a stolen GitHub token and plants a workflow in every repository that token can reach. The workflow prints the repository's secrets into its own workflow log. The tool then downloads that log through the GitHub API and deletes the run seconds later. Its requests carry the user agent `gheist`, which is where the name comes from.
The tally: more than 13,500 planted branches across more than 850 compromised accounts, ranging from personal projects to private repositories inside enterprise organizations. Roughly two-thirds of those accounts were no longer available on GitHub as of October 24, which Wiz reads as response activity after it reported the campaign.
The attack is boring, which is the point
For each repository, the tool creates a branch named `ci-sync-` followed by six random characters and commits `.github/workflows/sync-check.yml` there, with the message `ci: sync config`. The commit is unsigned and authored as the token's owner. The push triggers the workflow, and the workflow does the rest.
Three details make it work. GitHub masks secret values in logs only when they appear verbatim, so a base32-encoded secret matches nothing and prints in full. The `permissions: {}` guard restricts the job's `GITHUB_TOKEN` — which the attacker does not need — but leaves the `secrets` context reachable. And where a repository has deployment environments, the tool lists them in a job matrix with `environment: ${{ matrix.environment }}`, so environment-scoped secrets, the ones that usually hold production credentials, get printed too.
The detail worth sitting with is the exit path. The secrets leave through GitHub's own API. There is no attacker domain, no outbound connection, nothing for a network control to block.
GitHub's safety net worked exactly where it covers
Since July 28, 2026, GitHub has held potentially malicious workflow runs in public repositories until a collaborator approves them. That control worked. Every public Gheist run Wiz examined, across all variants including the self-hosted job, sat at `action_required`; the payloads never executed.
The hold does not apply to private repositories or to GitHub Enterprise Server. There, audit logs show runs on `ci-sync-*` branches completing and then being deleted by the `gheist` user agent. CloudTrail shows the affected organizations' own workflows running on those branches and assuming AWS roles, which is proof that Actions executed without approval. In enterprise environments Wiz also watched the tool cycle through the tokens of several users and automation accounts within minutes, clone private repositories before planting anything, and hours later use cloud credentials from the same organization to enumerate AWS Secrets Manager.
That is the real finding. The platform's most effective defence against this exact attack class was applied everywhere except the repositories holding the most valuable secrets.
The tooling evolved in public
Two variants ran in parallel. Tool A used a hexadecimal branch suffix and the plain payload. Tool B used base36, added `timeout-minutes: 6` and the environment matrix within hours of its first run on September 28, and tailored the payload per repository. On October 19 it inserted a base32 separator, `LAFA====`, between the dumps — presumably for the operator's log parser — and hours later pushed a second job, `sync-host`, with `runs-on: self-hosted`, apparently only to repositories with a registered self-hosted runner.
That job hunts: 31 token formats including keys for AI providers such as Anthropic, Hugging Face, Groq and NVIDIA; private keys, `.env` files, Terraform state and shell history; the environment of every process via `/proc/*/environ`; and, through the Docker socket, a container with the host's root filesystem mounted. It is also buggy — wrong runner key file, a broken regex for URL credentials, no GCP or Azure tokens. Not every attacker improvement is competence.
One account, many campaigns
The compromised accounts are shared property. On October 6, 391 of them renamed about 1,461 repositories into cracked-software lures; 166 of those accounts never appear in Gheist activity at all, so the pool of stolen tokens is larger than this campaign's footprint. On October 18, 25 Gheist accounts each created a repository called "Windows RDP Runner" within nine minutes, apparently to burn Actions as free compute. Three victims showed signs of PolinRider, malware tied to North Korea, rewriting their build configs. Wiz's traceable credential sources fit: infostealer logs, plus tokens leaked in earlier supply-chain compromises such as the Trivy incident in March.
What defenders should do now
Hunt for branches matching `ci-sync-` plus six characters whose only change is a new `.github/workflows/sync-check.yml` with the message `ci: sync config`. In audit logs, look for workflow runs on `ci-sync-*` deleted within a minute of completing. The best signal needs no GitHub logs at all: if your workflows use OIDC, check cloud audit logs for role assumptions whose subject contains a `ci-sync-` branch.
Rotate every repository, organization and environment secret that was reachable from an affected repository — deleting the run or the branch does not undo the exposure. Revoke the tokens that pushed the commits and check their owners' devices for infostealers. If a `ci-sync` branch ran on a self-hosted runner, treat the host and its instance role as compromised. And do not approve held `sync-check` runs.
Harden by requiring reviewers on environments with production secrets, scoping OIDC trust policies to protected branches and environments, replacing personal access tokens with GitHub Apps or short-lived fine-grained tokens, and running self-hosted runners as ephemeral hosts without a Docker socket or unrestricted metadata access.
One design decision — the approval hold covering public repositories only — determined where this campaign did damage. Decisions like that are invisible until someone builds a business on them.




