No C2, No Malware, Just Your Own CI: Gheist Planted Secret-Dumping Workflows in 13,500 Branches With Stolen GitHub Tokens
On 25 October 2026 Wiz Research published Gheist, a campaign it has tracked since 15 September 2026 that performs mass abuse of compromised GitHub tokens. The method, reported by researchers Rami McCarthy and Hila Ramati, is brutally simple: for every repository a stolen token can reach, the tooling creates a branch named ci-sync- plus six random characters, commits a workflow file at .github/workflows/sync-check.yml with the message ci: sync config, lets the push trigger the workflow, downloads the resulting log through the GitHub API, and deletes the run seconds later. The workflow’s only job is to print the repository’s secrets into its own log. Wiz says it has recovered more than 13,500 planted branches from more than 850 compromised accounts, ranging from personal projects to private enterprise repositories. The campaign takes its name from the user agent, gheist, that the attacker’s tool sends with GitHub API requests.
Why the exfiltration works: three platform behaviors
The payload, quoted in the report, is two lines: the environment, then echo -n ‘${{ toJSON(secrets) }}’ | base32 -w 120. Three details make it effective, and each is worth stating precisely because each is a control defenders assume holds. First, GitHub masks secret values in logs only when they appear verbatim — a base32-encoded secret does not match, so it prints in full, and the operator even added an echo X | base32 separator line (printing LAFA====) as a parser marker. Second, permissions: {} in the workflow restricts the job’s GITHUB_TOKEN, which the attacker does not need, but does not restrict the secrets context — the thing being stolen was never behind the permission. Third, where a repository has deployment environments, the tool lists them in a job matrix with environment: ${{ matrix.environment }}, so environment-scoped secrets — often production credentials — are printed too. Because the secrets leave through GitHub’s own API, there is no attacker domain and no outbound connection to block. No C2 server exists to take down.
In enterprise organizations, Wiz observed the tool cycling through tokens of several users and automation accounts within minutes, cloning private repositories before planting the workflow — and hours later, cloud credentials from the same organizations being used to enumerate AWS Secrets Manager. Follow-on activity is real, not theoretical.
The control that worked — and its boundary
Since 28 July 2026, GitHub has held potentially malicious workflow runs in public repositories until a collaborator approves them. Wiz reports that every public Gheist run it checked, across all variants, sits at action_required: the control worked and the payloads never executed. But the hold does not apply to private repositories or GitHub Enterprise Server, where GitHub audit logs show runs on ci-sync-* branches completing and then being deleted by the gheist user agent. Wiz adds one detection gift for teams without GitHub log coverage: CloudTrail shows the organizations’ own workflows running on those branches and assuming AWS roles, which proves Actions executed without approval — check cloud audit logs for role assumptions whose subject includes a ci-sync- branch. As of 24 October, roughly two-thirds of implicated accounts were no longer available on GitHub, likely reflecting response activity after Wiz reported the campaign — but the researchers state plainly that the attack is ongoing and evolving.
Two variants, a self-hosted pivot, and a crowded crime scene
Two tool variants run in parallel, distinguishable by branch suffix (hexadecimal versus base36) — the second added the timeout, the environment matrix, and per-repository payload selection within hours of its first run on 28 September. On 19 October it gained a second job, sync-host, targeting repositories with a registered self-hosted runner: it writes three helper files (.gdmp_pat, .gdmp_patg, .gdmp_sweep), logs each stage under a gdmp-pivot header, and searches the runner for 31 token formats — explicitly including keys for AI providers such as Anthropic, Hugging Face, Groq and NVIDIA — plus private keys, .env files, Terraform state, shell history, every process’s environment via /proc, the Docker socket with the host root filesystem mounted, cloud instance metadata, runner credential files, and the Kubernetes service account token. Wiz describes this job as buggy: it looks for the wrong runner key file, its URL-credential regex is broken, and it does not collect GCP or Azure tokens. The AI-provider key hunting is the detail our readers should sit with — the same campaign family that harvested enterprise cloud credentials is explicitly sweeping build hosts for the keys that train and serve models, adjacent to the OpenCode Console provider-key exposure and the MCP config secrets sprawl we have documented this month.
The same compromised accounts are shared infrastructure. Wiz reports overlaps with lure-repository renaming (391 accounts, ~1,461 repositories renamed to cracked-software lures on 6 October), cryptomining on 25 accounts, PolinRider — DPRK-linked malware hiding JavaScript in build configs — on three victims, and Megalodon victims from May among 57 Gheist accounts. Traced credential sources fit the picture: infostealer logs and tokens exposed in earlier supply-chain compromises such as the March Trivy compromise (see our Trivy coverage). Wiz explicitly distinguishes Gheist from GhostAction, which uses different workflow files and exfiltrates to an external IP — the splintering of secret-dumping GitHub campaigns into distinct toolchains is itself the trend.
What to do
Wiz’s response list is concrete and we repeat it because it is actionable: hunt branches matching ^ci-sync-[0-9a-z]{6}$ whose only change is the sync-check file; hunt audit logs for workflow runs on ci-sync-* branches deleted within a minute and any gheist user-agent activity; on self-hosted runners look for the .gdmp_* files and gdmp-pivot strings. Rotate every repository, organization and environment secret available to an affected repository — deleting the run or branch does not undo exposure — revoke the pushing tokens, and check owners’ devices for infostealers. Treat a self-hosted runner that ran a ci-sync branch, with its instance role, as compromised. Harden by requiring reviewers on environments holding production secrets, scoping OIDC trust to protected branches or environments, preferring GitHub Apps or short-lived fine-grained tokens over personal access tokens, and running self-hosted runners as ephemeral hosts without Docker-socket or unrestricted metadata access. This is the same standing-access lesson as the bank credential-sharing and Claude Duet isolation stories: the tokens that persist are the tokens that get harvested.
Our verification was read-only. The campaign dates, scale figures, workflow content, base32 mechanism, approval-hold behavior, variant history, overlap figures and remediation steps above were read first-hand in full from the Wiz Research blog (25 October 2026) and corroborated against the Infosecurity Magazine report (26 October 2026), which independently confirms the research date, tracking window, branch count, account count, token sources and the July approval-hold boundary. The Hacker News and Dark Reading versions of this story sit behind access controls we did not pass and are not cited. We contacted no vendor, accessed no repository, ran no workflow, and handled no stolen credential. Brave Search was used for discovery only.
Sources:
- Wiz Research — Gheist: Attempted Scaled Abuse of Stolen GitHub Tokens Stumbles (25 October 2026)
- Infosecurity Magazine — Gheist Attackers Stumble in Attempt to Abuse GitHub Tokens (26 October 2026)
- al-ice.ai — GhostAction: the secret-dumping GitHub campaign that never stopped
- al-ice.ai — OpenCode Console breach and provider-key exposure
- al-ice.ai — Trivy supply-chain compromise