Skip to content

Gheist abuses stolen GitHub tokens to leak CI secrets

Wiz details Gheist: stolen GitHub tokens plant a workflow that base32-encodes repo secrets into Actions logs, defeating log masking — 13,500+ branches across 850+ accounts.

Published 3 min read

Wiz has disclosed an automated campaign, which it calls Gheist, that uses stolen GitHub tokens to print a repository's secrets into its own GitHub Actions logs, then downloads the logs and deletes the run. Wiz has tracked it since September 15, 2026 and recovered more than 13,500 planted branches from more than 850 compromised accounts, including private repositories of enterprise organizations. The name comes from the gheist user agent the attackers' tool sends with its GitHub API requests.

How it works

For each repository a stolen token can reach, the tool creates a branch matching ci-sync- plus six random characters and commits a single file, .github/workflows/sync-check.yml, with the message ci: sync config. Pushing it starts a workflow that prints the job's environment and every readable secret — base32-encoded, which defeats GitHub's log masking because masking only catches secrets that appear verbatim. Some versions run a job per deployment environment, pulling environment-scoped production credentials.

The tool then fetches the log through GitHub's API and deletes the run seconds later. No attacker server or outbound connection is involved, so there is nothing to block at the network layer, and deleting the branch or run does not undo the exposure.

A platform safeguard blunted the public-repo case: since July 28, GitHub holds suspicious workflow runs until a write-access collaborator approves them. Wiz found every public Gheist run still waiting for approval, so those payloads never executed. But the hold "currently applies to public repositories on github.com only" — private repos and GitHub Enterprise Server are not covered, and that is where Wiz observed successful exfiltration.

Detection artifacts

Indicators from the Wiz report, verbatim:

User agent:      gheist
Branch pattern:  ^ci-sync-[0-9a-z]{6}$
Commit message:  ci: sync config  (adds only .github/workflows/sync-check.yml)
Strings:         toJSON(secrets) }}' | base32 -w 120
                 ===== gdmp-pivot:
Self-hosted:     .gdmp_pat  .gdmp_patg  .gdmp_sweep
Workflow blobs (SHA):
  f3da2b8b357dcda02fd455d314be2a95007516a6  (plain)
  d09535857a741bbb976bdc084a9a021713133291  (timeout)
  1dc5af9cce950aad1eb1546ecce8565b117540c4  (separator)
  c665b778b2d822453293003e7ca0bbb2cb08e39d  (self-hosted job)

Action checklist

  1. Search your organization for ci-sync-* branches that contain only sync-check.yml, and for Actions runs on those branches deleted within a minute of finishing.
  2. Check cloud audit logs for role assumptions tied to ci-sync- branches — this catches exposure even where GitHub logs are thin.
  3. If affected, rotate every repository, organization, and environment secret the repo could read, and revoke the tokens that pushed the commits; check their owners' devices for infostealers.
  4. Treat any self-hosted runner that executed a ci-sync branch — and its cloud instance role — as compromised. Do not approve held sync-check runs.
  5. Harden: require reviewers on environments holding production secrets, scope OIDC trust to protected branches, and prefer short-lived fine-grained tokens or GitHub Apps over personal access tokens.

Context

Gheist is the latest in a run of GitHub Actions supply-chain abuse — distinct from GhostAction, which uses different workflow files, but drawing token supply from the same well: infostealer logs and earlier compromises such as the March Trivy incident. Wiz notes that around two-thirds of the affected accounts are no longer on GitHub, likely a response to its report, but the campaign was still live as of October 24. The log-masking bypass is the detail to internalize: masking is a convenience, not a secret-management boundary, and base32 is all it takes to walk around it.

Related stories