Gheist
Gheist: stolen GitHub tokens dump repo secrets into Actions logs with no C2 server
Posted · Updated · Spectrum Delta Research Team
Summary
Wiz Research (Oct 25, A/3) designates Gheist, an automated campaign tracked since September 15, 2026 that performs mass abuse of stolen GitHub tokens: for every repository a token can reach, it pushes a branch named ci-sync- plus six random characters carrying a single new file .github/workflows/sync-check.yml (commit message 'ci: sync config', unsigned, authored as the token's owner). The triggered workflow prints the job environment and the repository's secrets — base32-encoded, which defeats GitHub's verbatim-only log masking — into its own Actions log; the tool then downloads the log through the GitHub API and deletes the run seconds later. There is no attacker server: the secrets leave through GitHub itself. Wiz recovered more than 13,500 planted branches from more than 850 compromised accounts, from personal projects to enterprise organizations' private repositories. GitHub's public-repository approval hold stopped every public run checked (action_required), but the hold does not apply to private repositories or GitHub Enterprise Server, where audit logs show runs completing and being deleted by the 'gheist' user agent and CloudTrail shows organizations' own workflows assuming AWS roles from ci-sync- branches. A later variant adds a sync-host job on self-hosted runners that searches the host for 31 token formats, private keys, .env files, Terraform state and cloud instance-role credentials.
Breakdown
Adversary
Gheist — unattributed automated campaign tracked by Wiz Research since September 15
Procedures (4)
Stolen token pushes ci-sync- branch with sync-check.yml
Valid accounts: stolen GitHub tokens implant workflows at repository scale
- T1078Valid Accounts
Logs
GitHub push and commit events · ci-sync- branch adds only sync-check.yml; commit 'ci: sync config' unsigned
Detectors
- Wiz Defend: Commit Created With Known Malicious Commit Message · commit with known malicious message (ci: sync config)
Click to expand remaining (3) proceduresHide remaining procedures
Workflow prints env and secrets base32 into Actions log
Unsecured credentials: secrets printed to Actions log as base32, defeating log masking
- T1552Unsecured Credentials
Logs
GitHub audit logs · ci-sync-* runs deleted within a minute; gheist user agent activity
Detectors
- Wiz Defend: Workflow Pushed With Secret Dump · pushed workflow printing env/secrets to job log (base32 encoded)
Log downloaded via API; run deleted seconds later
Indicator removal: log retrieved via GitHub API, run deleted seconds later
- T1070Indicator Removal
Logs
GitHub audit logs · ci-sync-* runs deleted within a minute; gheist user agent activity
Detectors
- Wiz Defend: New Workflow Completed And Deleted In Short Time Span · workflow run completed and deleted within a minute on ci-sync-* branch
sync-host job pivots self-hosted runner host
Escape to host: sync-host job searches runner host for tokens and cloud credentials
- T1611Escape to Host
Logs
Self-hosted runner job logs and host telemetry · gdmp-pivot markers; .gdmp_pat/.gdmp_patg/.gdmp_sweep files; secret-search stages
Detectors
No known detector
Indicators of compromise
Hunt only
(Wiz (https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles), A/3) — user agent 'gheist'; branch pattern ^ci-sync-[0-9a-z]{6}$ (Tool A suffix hexadecimal, Tool B suffix base36); workflow file .github/workflows/sync-check.yml; commit message 'ci: sync config'; secret-dump marker "echo -n '${{ toJSON(secrets) }}' | base32 -w 120"; separator marker LAFA==== (base32 of 'X', added Oct 19); self-hosted stage headers '===== gdmp-pivot: <stage> =====' with helper files .gdmp_pat, .gdmp_patg, .gdmp_sweep; workflow blob SHAs — plain f3da2b8b357dcda02fd455d314be2a95007516a6, timeout d09535857a741bbb976bdc084a9a021713133291, separator 1dc5af9cce950aad1eb1546ecce8565b117540c4, self-hosted job c665b778b2d822453293003e7ca0bbb2cb08e39d. Overlaps: PolinRider (DPRK-linked) victims re-pushed Gheist commits Oct 19-20 with a loader appended to vite.config.js behind whitespace padding, plus api.js run by package.json before every dev/build; Oct 6 lure-repo wave (391 accounts renamed ~1,461 repositories to cracked-software lures such as fl-studio-crack, solana-sniper-lite); Oct 18 free-compute attempt (25 accounts each created a 'Windows RDP Runner' repository within nine minutes); Megalodon overlap (57 first-phase accounts, May); Trivy compromise (March) as credential source. Assessed 2026-10-26, hunt-only — the branch/commit/markers are durable campaign signatures; roughly two-thirds of implicated accounts were already removed from GitHub as of Oct 24, likely response activity, so account-level IOCs perish while the tooling signature persists.
Detailed
Campaign signature: user agent 'gheist'; branch names ci-sync- plus six characters (Tool A hexadecimal suffix, Tool B base36 suffix; regex ^ci-sync-[0-9a-z]{6}$); workflow file .github/workflows/sync-check.yml; job 'sync'; commit message 'ci: sync config' (unsigned, authored as token owner); secret-dump commands 'env | base32 -w 120' and "echo -n '${{ toJSON(secrets) }}' | base32 -w 120"; separator 'echo X | base32 -w 120' printing LAFA==== (added Oct 19); self-hosted job 'sync-host' with runs-on: self-hosted; stage headers '===== gdmp-pivot: <stage> ====='; helper files .gdmp_pat, .gdmp_patg, .gdmp_sweep; timeout-minutes: 6 (Tool B). Workflow blob SHAs: plain f3da2b8b357dcda02fd455d314be2a95007516a6, timeout d09535857a741bbb976bdc084a9a021713133291, separator 1dc5af9cce950aad1eb1546ecce8565b117540c4, self-hosted job c665b778b2d822453293003e7ca0bbb2cb08e39d; environment-matrix variants differ per repository. Scale: 13,500+ planted branches, 850+ compromised accounts, tracked since Sept 15; ~2/3 of accounts removed as of Oct 24; campaign ongoing and evolving. Adjacent waves sharing the account pool: Oct 6 — 391 accounts renamed ~1,461 repositories to cracked-software lures (fl-studio-crack, solana-sniper-lite), wiping 610 ci-sync branches as a side effect, 166 of those accounts never in Gheist activity; Oct 18 — 25 accounts each created a 'Windows RDP Runner' repository within nine minutes (free-compute attempt); Megalodon overlap — 57 first-phase accounts also Megalodon victims in May; PolinRider overlap — three victims re-pushed Gheist commits Oct 19-20 with a loader appended to vite.config.js behind whitespace padding, two also gained api.js run by package.json before every dev and build; Trivy compromise (March) as credential source. All from the primary (Wiz (https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles), A/3).
Sources
- A/3Wiz Research: Gheist — Attempted Scaled Abuse of Stolen GitHub Tokens Stumbles (primary)
- B/3The Hacker News: Gheist Uses Stolen GitHub Tokens to Steal Private Repository Secrets via Actions Logs (corroboration)
Grades are source reliability (A–F) and information credibility (1–6).
Questions, detections or sightings of Gheist? Bring them to the community.
Discuss in the Tradecraft Slack