High 6 Hunt Leads

Gheist Turns Stolen GitHub Tokens Into Secret Dumps via Actions Logs

Wiz says the Gheist campaign used stolen GitHub tokens to plant 13,500+ branches across 850+ accounts, each running a workflow that dumps repository secrets into Actions logs and deletes the run.

A campaign Wiz calls Gheist is using stolen GitHub tokens to push a small workflow into victims’ repositories that prints every secret into the Actions log. It then downloads the log through GitHub’s own API and deletes the run seconds later. Wiz says it recovered more than 13,500 planted branches from more than 850 compromised accounts, ranging from personal projects to private enterprise repositories. Wiz says the campaign was still active as of Oct. 24.

For defenders

How it works: Wiz traced the tokens to infostealer logs and earlier supply-chain leaks such as the Trivy compromise. The tool creates a branch named ci-sync- plus six characters and commits one file, .github/workflows/sync-check.yml, with the message ci: sync config. The workflow base32-encodes the job’s secrets, which gets past GitHub’s log masking, T1552 (Unsecured Credentials). One variant runs a job per deployment environment to grab production secrets too. Because the secrets leave through GitHub’s API, there’s no attacker server to block. In enterprise orgs, Wiz saw cloned private repos, T1213.003 (Data from Information Repositories: Code Repositories), followed hours later by AWS Secrets Manager enumeration with that organization’s cloud credentials.

The approval gate only half-worked: GitHub has held flagged workflow runs for approval since July 28, and every public Gheist run Wiz checked was stuck there. The hold doesn’t cover private repos or GitHub Enterprise Server, though, and Wiz saw runs complete and get deleted there.

Hunt: Look for the gheist user agent in audit logs, runs on ci-sync-* branches deleted within a minute, and cloud OIDC role assumptions whose subject contains ci-sync-. On self-hosted runners, look for .gdmp_pat, .gdmp_patg, .gdmp_sweep and the string gdmp-pivot. Wiz says some victims are also infected with PolinRider, but it doesn’t tie Gheist itself to an actor.

Repository artifacts
User agent: gheist
Branch regex: ^ci-sync-[0-9a-z]{6}$
Commit message: ci: sync config
Workflow file: .github/workflows/sync-check.yml
Blob SHA: f3da2b8b357dcda02fd455d314be2a95007516a6
Blob SHA: d09535857a741bbb976bdc084a9a021713133291
Blob SHA: 1dc5af9cce950aad1eb1546ecce8565b117540c4
Blob SHA: c665b778b2d822453293003e7ca0bbb2cb08e39d

What it means for you

On GitHub, anyone with write access to a repository can read its stored secrets, and that includes someone holding your stolen token. If a password-stealing infection ever hit a developer’s laptop, their GitHub token may be in someone’s hands now. This is the same kind of slip that let attackers hijack LiteLLM’s releases. If you don’t use GitHub Actions secrets, you can skip this one.

Do this now

  1. Search your organization for ci-sync- branches and sync-check.yml files. Don’t approve any held run that came from one.
  2. If you find a hit, rotate every repository, organization and environment secret it could reach, revoke the token that pushed it, and check its owner’s machine for infostealers.
  3. Require reviewers on environments that hold production secrets, and limit cloud OIDC trust to protected branches.
  4. Replace long-lived personal access tokens with GitHub Apps or short-lived fine-grained tokens.

Source article: Gheist: attempted scaled abuse of stolen GitHub tokens stumbles
Publisher: Wiz (Rami McCarthy, Hila Ramati)
Published: Oct. 25, 2026
URL: https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles

Executive Summary

Wiz Research has tracked Gheist, an automated campaign named after the gheist user agent on its GitHub API requests, since Sept. 15, 2026; Wiz does not attribute it to a known actor and says it is distinct from the earlier GhostAction campaign. The operator feeds GitHub tokens harvested from infostealer logs and from earlier supply-chain leaks such as the March Trivy compromise into a tool that, for every repository the token can reach, creates a branch named ci-sync- plus six characters and commits a single file, .github/workflows/sync-check.yml, with the message ci: sync config. The push triggers the workflow, which base32-encodes the job’s environment and the serialized secrets context so GitHub’s log masking does not catch them, prints them to the run log, and the tool then downloads that log through the GitHub API and deletes the run seconds later. Later builds add a job per deployment environment to capture production secrets, and a sync-host job aimed at self-hosted runners that sweeps the host, the Docker-mounted host filesystem, process environments, cloud metadata and runner credentials under a gdmp-pivot header. Wiz recovered more than 13,500 planted branches from more than 850 compromised accounts. GitHub’s approval hold for suspicious runs, in place since July 28, stopped every public-repository run Wiz checked, but it does not cover private repositories or GitHub Enterprise Server, where runs completed and were deleted; in enterprise organizations the tool cycled through several user and automation tokens, cloned private repositories, and hours later the organizations’ own cloud credentials were used to enumerate AWS Secrets Manager. There is no command-and-control server: secrets leave through GitHub itself. Some victims were also infected with the DPRK-linked PolinRider malware, and some of the same accounts were reused for cracked-software lure repositories and free-compute abuse, but Wiz does not tie Gheist to those operators. Hunt leads, false-positive expectations, the absence of published rule text, and the indicators are detailed below.

ATT&CK chain: Initial Access → Execution → Credential Access → Defense Evasion → Discovery → Collection

Hunt Lead Grid

Hunt Lead Level Hunt Lead Name ATT&CK Risk of False Positives
1 (Junior) ci-sync Branch, Commit Message and Workflow Blob Sweep T1677 (Poisoned Pipeline Execution), T1552 (Unsecured Credentials) Low. A branch matching ^ci-sync-[0-9a-z]{6}$ whose only change adds sync-check.yml with the commit message ci: sync config is not a naming pattern legitimate CI tooling uses; the sweep anchors on all three together, plus the four published blob IDs.
1 (Junior) gheist User Agent in GitHub Audit Logs T1550.001 (Use Alternate Authentication Material: Application Access Token) Low. No legitimate client identifies itself as gheist; the only realistic benign match is a security team replaying the tool in a lab.
1 (Junior) gdmp Helper Files and gdmp-pivot Markers on Self-Hosted Runners T1552.001 (Unsecured Credentials: Credentials In Files), T1552.005 (Unsecured Credentials: Cloud Instance Metadata API), T1611 (Escape to Host) Low. The file names .gdmp_pat, .gdmp_patg, .gdmp_sweep and the string ===== gdmp-pivot: are specific to this tool’s self-hosted job.
2 (Pro) Workflow Run Completed and Deleted Within a Minute T1070 (Indicator Removal) Medium. Some housekeeping scripts and admins delete runs in bulk, so the rule anchors on deletion within about a minute of completion, by the same token that pushed the triggering commit, on a branch created minutes earlier.
2 (Pro) Cloud OIDC Role Assumptions From ci-sync Branches T1078.004 (Valid Accounts: Cloud Accounts) Low. OIDC trust is normally scoped to protected branches, so a token exchange whose subject names a ci-sync- branch only appears when an unreviewed branch ran with cloud access; the rule anchors on the subject claim.
3 (Elite) Multi-Token Private Repo Cloning Followed by Secrets Manager Enumeration T1213.003 (Data from Information Repositories: Code Repositories), T1555.006 (Credentials from Password Stores: Cloud Secrets Management Stores) Medium. Backup jobs and new hires clone many repositories, and deployment roles read secrets, so the correlation anchors on several distinct tokens cloning private repositories within minutes, a workflow push from those tokens, and Secrets Manager list calls from a principal or source outside its baseline within hours.

ci-sync Branch, Commit Message and Workflow Blob Sweep

For each repository a stolen token can reach, the tool creates a branch matching ^ci-sync-[0-9a-z]{6}$ (Tool A used a hexadecimal suffix, Tool B base36), commits only .github/workflows/sync-check.yml with the unsigned commit message ci: sync config attributed to the token’s owner, and the push triggers the workflow. The workflow runs one Ubuntu job with permissions set to none, which restricts GITHUB_TOKEN but not the secrets context, and prints a base32-encoded dump built from toJSON(secrets); from Sept. 28 Tool B added a six-minute timeout and a job matrix over the repository’s deployment environments, and from Oct. 19 a base32 separator line between dumps. Wiz publishes the Git blob IDs of the workflow file: f3da2b8b357dcda02fd455d314be2a95007516a6 (plain), d09535857a741bbb976bdc084a9a021713133291 (timeout variant), 1dc5af9cce950aad1eb1546ecce8565b117540c4 (separator variant) and c665b778b2d822453293003e7ca0bbb2cb08e39d (self-hosted job); environment-matrix variants differ per repository. The detection logic is a sweep of every repository in the organization, including branches that have since been deleted where push events are retained, for branches matching the pattern, commits with that exact message touching only that path, workflow files whose blob ID matches one of the four values, and any workflow content that pipes toJSON(secrets) through base32.

Use the GitHub organization or enterprise audit log (branch creation and git.push events where Git events are streamed), the repository list via the API, and a code search for sync-check.yml and toJSON(secrets); Wiz notes that 610 ci-sync branches were wiped as a side effect when accounts renamed repositories into lures, so branch listings alone undercount and push history matters. Sweep back to Sept. 15, 2026. Do not rely on the blob IDs alone, since the environment-matrix variants change per repository; the branch name plus commit message plus path is the stronger anchor. Do not approve any held sync-check run and do not check out or run code from a ci-sync branch. Any hit means the pushing token is stolen: revoke it, check its owner’s machine for infostealers, and rotate every repository, organization and environment secret the repository could reach, because deleting the branch or run does not undo exposure. The pattern of an unreviewed branch adding a workflow that serializes the secrets context generalizes to other secret-dumping campaigns.

Hunt Lead Level: 1 (Junior Analyst)

ATT&CK: Execution → T1677 (Poisoned Pipeline Execution) (our mapping; Wiz publishes no ATT&CK mapping); Credential Access → T1552 (Unsecured Credentials) (our mapping)

Sources: “Gheist: attempted scaled abuse of stolen GitHub tokens stumbles”, Wiz, Oct. 25, 2026, https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles (source article)

gheist User Agent in GitHub Audit Logs

Every API request from the tool, from branch creation through log download and run deletion, carries the user agent gheist, and Wiz saw it in enterprise audit logs alongside ci-sync-* runs completing and being deleted. In enterprise organizations the tool cycled through several user and automation tokens within minutes, so the same user agent can appear under different actors in a short window. The detection logic is an exact, case-insensitive match on gheist in the user-agent field of GitHub audit log events, grouped by actor and token, with every matching actor treated as a compromised identity.

Use the GitHub enterprise or organization audit log via streaming to a SIEM or the audit log API, which records the user agent for API-driven events where the export includes it; the default audit log retention in the web interface is limited, so streamed history is what makes a sweep back to Sept. 15, 2026 possible. Scope to all members, outside collaborators and machine accounts. Blocking the string at a proxy is the wrong control, since the requests go straight to GitHub from the operator’s infrastructure and the user agent is trivially changed; treat it as a retrospective and alerting signal only. Any hit should be escalated as a confirmed token theft for each actor involved: revoke the tokens, review what else those tokens touched, and rotate reachable secrets.

Hunt Lead Level: 1 (Junior Analyst)

ATT&CK: Defense Evasion and Lateral Movement → T1550.001 (Use Alternate Authentication Material: Application Access Token) (our mapping; Wiz publishes no ATT&CK mapping)

Sources: “Gheist: attempted scaled abuse of stolen GitHub tokens stumbles”, Wiz, Oct. 25, 2026, https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles (source article)

gdmp Helper Files and gdmp-pivot Markers on Self-Hosted Runners

Where a self-hosted runner was registered, Tool B pushed a second job, sync-host, targeting self-hosted runners. It writes three helper files, .gdmp_pat, .gdmp_patg and .gdmp_sweep, and logs each stage under a ===== gdmp-pivot: header: host details and crontab; a recursive search of home, workspace and system paths for 31 token formats including AI provider keys, private keys, .env files, Terraform state and shell history; process environments read from /proc; use of the Docker socket to start a container with the host root filesystem mounted and repeat the search; AWS instance-role credentials and GCP and Azure metadata; GitHub Actions and GitLab runner credential files; and the Kubernetes service account token. Wiz notes the job is buggy (wrong runner key file, broken URL-credential regex, no GCP or Azure token collection), but the AWS role and file sweeps work. The detection logic is a file search on every self-hosted runner for the three helper files, a search of job logs for gdmp-pivot, and an EDR or audit rule for a runner job process that starts a container with / bind-mounted through the Docker socket.

Use EDR or auditd file and process telemetry on runner hosts, retained job logs, Docker daemon logs, and AWS CloudTrail for the runner’s instance role. Sweep back to Sept. 28, 2026, when Tool B first ran, and further where logs allow, since host persistence or stolen instance-role credentials outlive the job. Scope to every host registered as a self-hosted runner, including ephemeral pools whose base images or caches persist. False positives on the helper file names and marker string are not expected; the Docker host-mount rule will match some legitimate build jobs, so pair it with a ci-sync branch or a first-time workflow file. Any hit means the runner and its instance role are compromised: take the runner out of rotation, rotate the instance role’s sessions and every credential found on the host, and rebuild it. The Docker host-mount and metadata-credential behaviors generalize to other CI runner abuse.

Hunt Lead Level: 1 (Junior Analyst)

ATT&CK: Credential Access → T1552.001 (Unsecured Credentials: Credentials In Files) (our mapping); Credential Access → T1552.005 (Unsecured Credentials: Cloud Instance Metadata API) (our mapping); Privilege Escalation → T1611 (Escape to Host) (our mapping; Wiz publishes no ATT&CK mapping)

Sources: “Gheist: attempted scaled abuse of stolen GitHub tokens stumbles”, Wiz, Oct. 25, 2026, https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles (source article)

Workflow Run Completed and Deleted Within a Minute

After the workflow prints the encoded secrets, the tool downloads the run log through the GitHub API and deletes the run seconds later, so the evidence disappears from the repository’s Actions tab; Wiz saw ci-sync-* runs completing and then being deleted under the gheist user agent in private repositories and on GitHub Enterprise Server, where GitHub’s approval hold does not apply. Wiz’s own product detection for this is titled “New Workflow Completed And Deleted In Short Time Span”, but the rule text is not published. The detection logic is a per-run correlation in the audit log: a workflow run completion event followed within about 60 seconds by a workflow run deletion, especially when the deleting actor is the same identity that pushed the triggering commit and the branch was created minutes earlier, with a log-download API call in between where your telemetry records it.

Use the GitHub audit log streamed to a SIEM, joining workflow run completion and deletion events (for example workflows.completed_workflow_run and workflows.delete_workflow_run) on repository and run ID, enriched with branch creation and push events. Back-test from Sept. 15, 2026, then run continuously. Expect some benign matches from cleanup automation and admins clearing failed runs, which typically act on older runs in bulk and from known service identities; allowlist those by actor and exclude deletions of runs older than a few minutes. The wrong response is to treat a missing run as nothing to investigate: a run deleted seconds after completion on a fresh branch should be treated as a probable secret dump and handled like the branch sweep hit above. This generalizes to any CI abuse that cleans up after itself.

Hunt Lead Level: 2 (Pro)

ATT&CK: Defense Evasion → T1070 (Indicator Removal) (our mapping; Wiz publishes no ATT&CK mapping)

Sources: “Gheist: attempted scaled abuse of stolen GitHub tokens stumbles”, Wiz, Oct. 25, 2026, https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles (source article)

Cloud OIDC Role Assumptions From ci-sync Branches

Wiz found that cloud logs showed the victim organizations’ own workflows running on the planted branches and assuming AWS roles, proof that Actions executed without approval, and recommends checking cloud audit logs for role assumptions or token exchanges whose subject includes a ci-sync- branch, because this reveals execution even where GitHub logs are missing or the run was deleted. The detection logic is an alert on AWS STS AssumeRoleWithWebIdentity (and the equivalent federated token exchanges in GCP and Azure) where the identity provider is GitHub Actions and the subject claim contains ref:refs/heads/ci-sync-, plus a broader rule for any OIDC subject naming a branch that is not protected.

Use AWS CloudTrail management events (the subject is recorded in the web identity session details), GCP workload identity federation audit logs, and Azure federated credential sign-in logs. Sweep back to Sept. 15, 2026; CloudTrail’s default 90-day event history covers most of the campaign without a trail export. Scope to every role or service account that trusts GitHub’s OIDC provider. False positives should be rare once trust is scoped to protected branches; if trust policies use wildcards, expect legitimate feature-branch deployments and allowlist them by known branch prefixes rather than loosening the rule. The control that fixes the root cause is tightening the trust policy’s subject condition to protected branches or environments, which Wiz also recommends. Any hit means the role’s temporary credentials were available to the attacker’s workflow: review everything that session did and rotate whatever it could read.

Hunt Lead Level: 2 (Pro)

ATT&CK: Defense Evasion, Persistence, Privilege Escalation and Initial Access → T1078.004 (Valid Accounts: Cloud Accounts) (our mapping; Wiz publishes no ATT&CK mapping)

Sources: “Gheist: attempted scaled abuse of stolen GitHub tokens stumbles”, Wiz, Oct. 25, 2026, https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles (source article)

Multi-Token Private Repo Cloning Followed by Secrets Manager Enumeration

In enterprise organizations Wiz saw the tool cycle through several user and automation tokens within minutes, clone private repositories before planting the workflow, and, hours later, use cloud credentials from the same organization to enumerate AWS Secrets Manager. The detection logic is a two-stage correlation: first, within a short window such as 15 minutes, three or more distinct GitHub identities (users or machine accounts) cloning private repositories at a rate above their baseline, or followed by a ci-sync push from any of them; second, within 24 hours, AWS ListSecrets and bulk GetSecretValue or DescribeSecret calls from a principal, source IP or user agent not seen for that account in the prior 30 days.

This needs GitHub audit log Git events (git.clone) streamed to a SIEM, CloudTrail with data or management events for Secrets Manager, and a per-identity baseline of clone volume and per-principal baseline of Secrets Manager use. Back-test against Sept. 15 to Oct. 24, 2026, then run continuously. Backup jobs, mirroring tools and new developers clone many repositories, and deployment roles read secrets routinely, so allowlist known mirror and backup identities and anchor the cloud stage on novelty of principal or source rather than volume alone. Neither stage is reliable alone; the join is what separates this campaign from normal engineering. A correlated hit should go straight to incident response: revoke all tokens in the cluster, rotate the secrets that were listed or read, and check the token owners for infostealer infection. The correlation generalizes to any stolen-token intrusion that moves from source code to cloud secrets.

Hunt Lead Level: 3 (Elite)

ATT&CK: Collection → T1213.003 (Data from Information Repositories: Code Repositories) (our mapping); Credential Access → T1555.006 (Credentials from Password Stores: Cloud Secrets Management Stores) (our mapping; Wiz publishes no ATT&CK mapping)

Sources: “Gheist: attempted scaled abuse of stolen GitHub tokens stumbles”, Wiz, Oct. 25, 2026, https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles (source article)

Hash Appendix

Gheist workflow file (.github/workflows/sync-check.yml)

Wiz publishes only Git blob object IDs (SHA-1 over Git’s blob header plus content), not standard file hashes; they match in git tooling and GitHub’s API, not in EDR file-hash fields. No MD5 or SHA-256 values are published, and because these are repository objects rather than malware samples there is no OTX or VirusTotal record to pivot to. Wiz publishes no IP addresses, domains, YARA or Sigma rules; its product detections are named but their logic is not published.

MD5s

(not available)  # sync-check.yml, all variants

SHA1s (Git blob IDs)

f3da2b8b357dcda02fd455d314be2a95007516a6  # sync-check.yml, plain payload (Tool A) -> .github/workflows/sync-check.yml on ci-sync-* branch
d09535857a741bbb976bdc084a9a021713133291  # sync-check.yml, timeout variant (Tool B) -> .github/workflows/sync-check.yml
1dc5af9cce950aad1eb1546ecce8565b117540c4  # sync-check.yml, base32 separator variant (Tool B, from Oct. 19) -> .github/workflows/sync-check.yml
c665b778b2d822453293003e7ca0bbb2cb08e39d  # sync-check.yml with sync-host self-hosted runner job (Tool B) -> .github/workflows/sync-check.yml

SHA256s

(not available)  # sync-check.yml, all variants

References Appendix

  • “Gheist: attempted scaled abuse of stolen GitHub tokens stumbles”, Wiz (Rami McCarthy, Hila Ramati), Oct. 25, 2026, https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles (source article; timeline, attack chain, workflow design, tool variants, self-hosted job stages, approval-hold gaps, related activity, indicators, hunting, response and hardening guidance)
  • MITRE ATT&CK Enterprise technique pages linked above, MITRE, https://attack.mitre.org/

More from the Roundup