What happened

Wiz Research has been following a campaign it calls Gheist since 15 September 2026, in which stolen GitHub tokens are abused at scale. With each token, the tool adds a workflow called sync-check to every repository that token can reach. That workflow tries to write the repository's secrets into its own run log, which the tool then pulls down through the GitHub API before deleting the run a few seconds later.

Wiz says it has recovered over 13,500 planted branches belonging to more than 850 compromised accounts. The victims run from personal projects to private repositories owned by enterprise organizations. The campaign takes its name from the user agent, gheist, that its API requests send.

A GitHub control introduced in July has blocked the attack in public repositories, according to Wiz. In private repositories and GitHub enterprise environments, however, Wiz observed the theft succeeding and further activity afterwards. As of 24 October, Wiz described the campaign as ongoing and still changing.

About two thirds of the accounts involved are no longer available on GitHub. Wiz says this probably reflects a response after it reported the activity to GitHub.

Background

On 28 July 2026, GitHub began holding some workflow runs in public repositories for approval before they start. GitHub said recent supply chain attacks had used compromised credentials to push malicious workflows that steal CI/CD credentials. A held run waits until a collaborator with write access approves it from an authenticated web session, and the protection is switched on automatically. It covers public repositories on github.com only, not GitHub Enterprise Server.

Wiz notes that Gheist is a different campaign from GhostAction, which relies on other workflow files.

How it works

For each repository, the tool makes a branch whose name is ci-sync- followed by six random characters. The only file it adds there is .github/workflows/sync-check.yml, committed under the message ci: sync config. The commit is unsigned, is authored as the owner of the stolen token, and the push itself starts the workflow.

The trick relies on how GitHub hides secrets in logs: values are masked only when they appear exactly, so a base32-encoded secret is printed in full. An empty permissions block does not limit access to the secrets context, Wiz says. Where a repository has deployment environments, the tool runs a job per environment so that environment-scoped secrets, frequently production credentials, are printed as well. Because the data is pulled out over GitHub's own API, defenders have no attacker server or outgoing connection they could block.

Wiz has seen two variants running side by side, told apart by their branch names. Tool A uses a hexadecimal suffix and the basic payload. Tool B uses a base36 suffix and, within hours of its first run on 28 September, added a six-minute timeout and the per-environment job.

Tool B later added a second job, sync-host, aimed at self-hosted runners and apparently sent only to repositories that have one registered. That job combs the home directory, workspace and system paths for 31 kinds of token, among them AI provider keys from Anthropic, Hugging Face, Groq and NVIDIA. It also uses the Docker socket to start a container with the host's root filesystem mounted and repeat the search there, and it collects AWS instance-role credentials. Wiz found mistakes in the job: it searches for an incorrect runner key file, its pattern for credentials in URLs does not work, and it never gathers GCP or Azure tokens.

Timeline

  1. GitHub starts holding potentially malicious workflow runs in public repositories for approval.[2]

  2. Wiz Research starts tracking Gheist.[1]

  3. Tool B makes its first run.[1]

  4. 391 accounts rename about 1,461 repositories into cracked-software lures.[1]

  5. 25 Gheist accounts create Windows RDP Runner repositories.[1]

  6. Tool B adds a separator and a job for self-hosted runners.[1]

  7. Wiz says the campaign is ongoing and evolving.[1]

Who is affected

  • Private repositories and GitHub Enterprise environments that a stolen token can reach, since the approval hold does not apply to them.
  • Self-hosted runner hosts, and their instance roles, where a ci-sync branch ran.

Impact

Every public Gheist run Wiz checked was stuck waiting for approval, so the malicious payloads never ran there. The hold does not cover private repositories or GitHub Enterprise Server. In those places, Wiz found audit logs showing ci-sync runs finishing and then being deleted, and CloudTrail records of the organizations' own workflows assuming AWS roles on the attacker's branches.

In enterprise organizations, the tool moved through the tokens of several users and automation accounts within minutes and cloned private repositories before planting the workflow. Hours later, cloud credentials belonging to the same organization were used to enumerate AWS Secrets Manager.

The same compromised accounts are being used by other actors as well, Wiz says. On 6 October, 391 accounts renamed about 1,461 repositories into cracked-software lures, and on 18 October 25 Gheist accounts each created a Windows RDP Runner repository. Where Wiz could trace where the credentials came from, they pointed to infostealer logs and to tokens leaked in earlier supply-chain incidents, the March Trivy compromise among them.

How to check

  1. Search your repositories for branches named ci-sync- plus six characters whose only change adds .github/workflows/sync-check.yml with the message ci: sync config.
  2. In GitHub audit logs, look for ci-sync runs removed less than a minute after they finished, and for any requests with the user agent gheist.
  3. Where workflows reach your cloud through OIDC, search the cloud provider's audit logs for role assumptions or token swaps that name a ci-sync- branch in the subject.
  4. Self-hosted runners that were hit may hold the files .gdmp_pat, .gdmp_patg or .gdmp_sweep, and their job logs may contain the string gdmp-pivot.

What to do

  1. Rotate every repository, organization and environment secret that an affected repository could use. Deleting the run or the branch does not undo the exposure.
  2. Revoke whichever tokens pushed the planted commits, and have the owners' machines checked for infostealer malware.
  3. Where a self-hosted runner executed a ci-sync branch, consider the machine and its instance role compromised.
  4. Do not approve held sync-check runs, and do not run code from ci-sync branches.
  5. Wiz also advises requiring reviewers on environments that hold production secrets and limiting OIDC trust policies to protected branches or environments.

Indicators of compromise

Shown defanged so they can't be clicked. Copy copies the real value. Every indicator appears in the cited source.

TypeIndicatorNoteCopy
User agentgheistuser agent on the tool's GitHub API requests[1]
File path.github/workflows/sync-check.ymlplanted workflow file, commit message ci: sync config[1]
File path.gdmp_pathelper file written on self-hosted runners[1]
File path.gdmp_patghelper file written on self-hosted runners[1]
File path.gdmp_sweephelper file written on self-hosted runners[1]
SHA-1f3da2b8b357dcda02fd455d314be2a95007516a6workflow blob SHA, plain variant[1]
SHA-1d09535857a741bbb976bdc084a9a021713133291workflow blob SHA, timeout variant[1]
SHA-11dc5af9cce950aad1eb1546ecce8565b117540c4workflow blob SHA, separator variant[1]
SHA-1c665b778b2d822453293003e7ca0bbb2cb08e39dworkflow blob SHA, self-hosted job variant[1]

Sources

Changelog

  1. First published.