If your company keeps code in private GitHub repositories, a live attack campaign may already have reached it. According to Hacker News, Wiz Research has been tracking a campaign it calls “Gheist” since September 15, 2026. Gheist uses stolen GitHub tokens to plant secret-stealing workflows in every repository each token can access. Wiz has recovered more than 13,500 planted branches from more than 850 compromised accounts. They range from personal side projects to private repositories at large companies.
As of October 24, the attack was still running and still changing.
How the attack works
Gheist follows the same short routine for each repository:
- It creates a branch named ci-sync- plus six random characters.
- It commits a file called .github/workflows/sync-check.yml with the message “ci: sync config.” The commit is unsigned and shows the token’s owner as the author.
- The push starts the workflow, which prints the environment variables and every repository secret into the run log, encoded in base32.
- The tool downloads that log through the GitHub API and deletes the run seconds later.
The campaign gets its name from the user agent on its API requests: “gheist.”
Why GitHub’s safeguards miss it
Three details make the trick work:
- Masking only catches exact matches. GitHub hides a secret in logs only when it appears exactly as stored. Once a secret is base32-encoded, it no longer matches, so it prints in full.
- Empty permissions don’t protect secrets. The setting permissions: {} limits the job’s GITHUB_TOKEN, but the attacker doesn’t need that token. The secrets themselves are still readable.
- Production secrets get pulled too. When a repository has deployment environments, the tool runs the job once for each one. That exposes secrets tied to specific environments, which are often production credentials.
What stands out here is that the stolen data leaves through GitHub’s own API. There’s no attacker domain and no outbound connection to block, so network egress controls see nothing.
The payload keeps getting worse
Wiz has seen two versions running in parallel. Tool A uses a hexadecimal branch suffix and the basic payload. Tool B uses a base36 suffix. Within hours of its first run on September 28, it added a six-minute timeout and the environment trick, and it now adjusts the payload for each repository.
On October 19, Tool B added a second job, sync-host, that targets self-hosted runners (machines a company runs itself to execute GitHub workflows). That job:
- Searches the machine for 31 token formats, including API keys for Anthropic, Hugging Face, Groq and NVIDIA
- Grabs private keys, .env files, Terraform state and shell history
- Reads the environment of every running process
- Uses the Docker socket to break out to the host machine and repeat the search there
- Takes AWS instance credentials, the Kubernetes service account token and runner credential files
Wiz says this job is buggy for now. It looks for the wrong runner key file, its URL-credential regex is broken, and it doesn’t actually collect GCP or Azure tokens. Don’t count on those bugs lasting.
Inside enterprise organizations, Wiz saw the tool switch between the tokens of several users and automation accounts within minutes. It cloned private repositories before planting the workflow. Hours later, cloud credentials from the same organization were used to list secrets in AWS Secrets Manager.
Public repos are covered. Private ones aren’t.
Since July 28, 2026, GitHub has held potentially malicious workflow runs in public repositories until a collaborator approves them. Every public Gheist run Wiz checked was stuck waiting for approval, so the payload never ran.
That hold doesn’t apply to private repositories or GitHub Enterprise Server. In those, audit logs show Gheist runs completing and then being deleted. Wiz has reported the activity to GitHub, and about two-thirds of the implicated accounts are no longer available, which likely means cleanup is underway.
Stolen access gets passed around
The same compromised accounts are showing up in other campaigns:
- Malware lures: On October 6, 391 accounts renamed about 1,461 repositories to fake cracked-software names. 166 of those accounts never showed up in Gheist activity, so the pool of stolen accounts is bigger than Gheist alone.
- Cryptomining: On October 18, 25 Gheist accounts created “Windows RDP Runner” repositories within nine minutes to get free compute from GitHub Actions.
- PolinRider: Some victims also appear to be infected with this DPRK-linked malware, which hides JavaScript in build config files.
What to do right now
- Search your organization for branches named ci-sync-* and files named sync-check.yml.
- Check GitHub audit logs for deleted workflow runs and the gheist user agent.
- Check CloudTrail for your own workflows running on ci-sync-* branches and assuming AWS roles. Wiz says this signal finds Gheist even without GitHub logs.
- Rotate every secret in any affected repository, including environment-level production credentials and AI provider API keys.
- Revoke and reissue personal access tokens. Fine-grained, short-lived tokens limit how far a single stolen token can reach.
- Use deployment branch rules so only protected branches can access environment secrets.
- Lock down self-hosted runners, especially their access to the Docker socket.
My take: one stolen token now means every repository and every secret that token can touch. The attackers are adding features week by week, so expect the gaps in the current payload to get fixed. Full technical details and indicators are in the original report.