Gheist: stolen GitHub tokens dump secrets into Actions logs
Gheist pushes a sync-check workflow that prints repository secrets in base32, then deletes the run. How to hunt it in audit logs and what to rotate first.
By Yaali. October 28, 2026, 6 min read, Supply chain, DevOps, Threat intel.
Wiz Research published details on October 25 of Gheist, an automated campaign that takes stolen GitHub tokens and commits a small workflow to every repository each token can write to. The workflow prints the repository's secrets into its own GitHub Actions log, the tool downloads that log through the API, and the run is deleted seconds later. Wiz has tracked it since September 15 and, as of October 24, had recovered more than 13,500 planted branches across more than 850 compromised accounts, from personal projects to enterprise private repositories.
If your organization uses private repositories or GitHub Enterprise Server (GHES), this applies to you even if your own code is untouched. GitHub's hold on suspicious workflow runs, in place since July 28, only covers public repositories on github.com. Every public Gheist run Wiz checked sat at action_required and never executed, but in private repositories the payload ran, and a deleted run does not mean the secrets stayed private.

How it works
Every workflow run on GitHub Actions can read the secrets context: repository secrets, organization secrets shared with that repository, and the secrets of any deployment environment the job names. GitHub tries to keep them out of logs by masking, which replaces a secret with *** when its exact value appears in the output. Masking matches the literal string only, so an encoded value is printed in full.
Gheist uses that gap. For each repository a stolen token can reach, the tool creates a branch named ci-sync- followed by six random characters, then commits one file, .github/workflows/sync-check.yml, with the message ci: sync config. The commit is unsigned and attributed to whoever owns the token. The push itself triggers the workflow on ubuntu-latest, and the job dumps the environment and toJSON(secrets) piped through base32 -w 120 into the log.
The workflow declares permissions: {}, which strips the short-lived GITHUB_TOKEN of its rights but does nothing to the secrets context. A second variant, first seen on September 28, adds timeout-minutes: 6 and, where the repository has deployment environments, a job matrix with environment: ${{ matrix.environment }} so environment-scoped secrets (often the production ones) are dumped too. Everything leaves through GitHub's own API, so there is no attacker domain to block. The API calls carry the user agent gheist, which gave the campaign its name.
Later builds push a second job, sync-host, with runs-on: self-hosted, and only to repositories that have a self-hosted runner registered. That job sweeps the runner host for 31 token formats, private keys, .env files, Terraform state and shell history. It also reads /proc/*/environ for every process, mounts the host filesystem through the Docker socket and requests AWS instance-role credentials. It writes .gdmp_pat, .gdmp_patg and .gdmp_sweep to disk and labels its log sections ===== gdmp-pivot: <stage> =====. Wiz found it buggy, but treat any host it ran on as compromised.
What attackers are doing
Wiz does not attribute Gheist to a named group. The tokens appear to come from infostealer logs and from credentials exposed in earlier supply chain incidents, such as the March Trivy compromise. Roughly two thirds of the implicated accounts were gone from GitHub by October 24, most likely through response activity.
In enterprise cases, the tool cycled through 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. Wiz also found CloudTrail records of organization workflows assuming AWS roles on ci-sync-* branches, which confirms the payload ran without approval.
The same accounts were reused for other abuse. On October 6, 391 accounts renamed about 1,461 repositories to cracked-software lures, wiping 610 ci-sync branches along the way. On October 19 and 20, three victims had Gheist commits re-pushed with a loader appended to vite.config.js, which Wiz links to the PolinRider activity it ties to North Korea. Gheist is separate from GhostAction, which uses different workflow files and sends secrets to 193.32.204[.]199.
What to do
1. Find planted branches that still exist. For each repository, gh api repos/ORG/REPO/git/matching-refs/heads/ci-sync- --jq '.[].ref' lists matching branches. A hit matches ^ci-sync-[0-9a-z]{6}$, contains one commit with the message ci: sync config, and changes only sync-check.yml. Wiz lists the blob SHAs for the fixed variants, for example f3da2b8b357dcda02fd455d314be2a95007516a6 for the plain payload. Do not approve a held sync-check run and do not check out the branch on a workstation.
2. Search the audit log for deleted runs. Branches can be gone, so the audit log is the better record. In an organization on GitHub Enterprise Cloud, pull the workflow events through the REST API, because some of them never show in the web interface:
gh api --paginate "/orgs/ORG/audit-log?phrase=action:workflows+created:>=2026-09-15&include=all&per_page=100" > workflows.json
jq '.[] | select((.user_agent // "") | test("gheist"; "i"))' workflows.json
Look for workflows.delete_workflow_run events that follow workflows.completed_workflow_run for the same run within a minute, and for any event whose user_agent is gheist. The workflows.prepared_workflow_job event records the list of secrets provided to each job, which gives you the exact rotation list per run. Also run the same query with action:git.push to find pushes from that user agent, but Git events are kept for only seven days on Enterprise Cloud. The organization audit log keeps the rest for 180 days.
3. Check your cloud logs. If workflows authenticate to AWS through OpenID Connect (OIDC, keyless federation from GitHub to the cloud), search CloudTrail for AssumeRoleWithWebIdentity events from token.actions.githubusercontent.com whose subject contains ref:refs/heads/ci-sync-. Any hit means an attacker branch ran with a cloud role, even if GitHub's own records are gone. Review Secrets Manager ListSecrets and GetSecretValue calls in the hours after.
4. Check self-hosted runners. Search hosts for .gdmp_pat, .gdmp_patg and .gdmp_sweep, and search retained job logs for gdmp-pivot.
5. Rotate in this order. Deleting the run or the branch removes nothing the attacker already downloaded. The sequence below is our recommendation, built on Wiz's response guidance:
- Revoke the token that pushed the commit first. Until it is gone, the tool can plant the branch again and dump the new values. Then check its owner's machine for an infostealer, since that is the likely source.
- Next, rotate environment secrets and cloud keys reachable from the repository, production first, because Wiz saw cloud credentials used within hours.
- Then organization secrets shared with the repository, since one value may cover dozens of other repositories.
- Then repository secrets.
- If
sync-hostran, rebuild the runner host and rotate its instance role, its runner registration and everything stored on it.

Close the paths it relied on
The campaign worked because a single personal access token could push to hundreds of repositories, and any branch could reach production secrets. Require reviewers on environments that hold production secrets, so a job on an unknown branch waits for a human. Limit OIDC trust policies in the cloud to protected branches or named environments instead of repo:ORG/*. Replace long-lived personal access tokens with GitHub Apps or short-lived fine-grained tokens. Run self-hosted runners as ephemeral machines, without the Docker socket and with metadata access restricted.
We review Actions setups, token scope and OIDC trust policies in platform and cloud engineering, and watch for this activity in security operations. To have someone check your audit log results, open the chat and Yaali, our AI agent, will pass the question to an engineer.
Sources: Wiz Research, RuntimeWire, GitHub changelog, July 28, 2026, GitHub Docs: audit log events for your organization.
Read next
- GhostAction is back, stealing secrets via GitHub workflows
- A trojanized Terraform provider is robbing cloud engineers
- tensorlake 0.5.144 on npm carried a Shai-Hulud worm
Back to the blog, or tell us about your system in the chat. Yaali, our AI agent, answers first and brings in an engineer.