Threat Overview
Wiz Research documented Gheist, an ongoing campaign abusing stolen GitHub access tokens since September 15. As of October 24, researchers recovered over 13,500 `ci-sync-*` branches associated with more than 850 compromised accounts. The attacker commits `.github/workflows/sync-check.yml` with `ci: sync config`, prints environment variables and secrets as base32 into Actions logs, retrieves those logs through GitHub's own API, then deletes runs. Private repositories and GitHub Enterprise environments had successful execution and follow-on activity; Wiz observed public-repository malicious runs held for approval rather than executed. A self-hosted-runner variant attempts to sweep host secrets and cloud metadata; successful execution of that specific job was not confirmed in the published analysis. The campaign is distinct from GhostAction and does not rely on a conventional C2 domain.
References
- Wiz Research — Gheist: Attempted Scaled Abuse of Stolen GitHub Tokens Stumbles (October 25, 2026) — https://www.wiz.io/blog/gheist-attempted-scaled-abuse-of-stolen-github-tokens-stumbles
- The Hacker News — Gheist Uses Stolen GitHub Tokens to Steal Private Repository Secrets via Actions Logs (October 26, 2026) — https://thehackernews.com/2026/10/gheist-uses-stolen-github-tokens-to.html
Impacted Systems
- **Vendor/product:** GitHub Actions, GitHub private repositories, GitHub Enterprise Server, self-hosted Actions runners; AWS STS/Secrets Manager when workflows assume cloud roles.
- **Versions:** No vulnerable GitHub release or fixed build is reported; stolen tokens and workflow permissions are the prerequisite.
- **Deployment:** GitHub-hosted cloud repositories or self-managed Enterprise Server and self-hosted runners on Linux/Windows.
- **Prerequisite:** Compromised GitHub personal/automation token with write access to repositories; workflow must run to expose secrets.
- **Exposure:** Private repositories and Enterprise Server were confirmed vulnerable to observed execution; public-repository runs checked by Wiz were held for approval.
- **Unaffected:** Public repositories whose malicious workflows remain unapproved did not execute in Wiz's observed sample; not a guarantee for all configurations.
Why this matters
Secret exposure through first-party GitHub Actions logs can evade network IOC detections. MSSP detection requires GitHub audit ingestion, AWS CloudTrail OIDC detail, or self-hosted runner telemetry. GitHub product presence alone is not evidence those logs are in Sentinel.
Exploitation Status
Wiz confirmed successful executions in private/enterprise environments, with follow-on activity; public repo execution prevented in its examined cases. Research October 25, 2026.
What this hunt looks for
Layered, behavior-first detection for the reported campaign, using available Microsoft Sentinel data. Each query requires its listed telemetry; no customer-specific execution is claimed.
Required logs
- AWSCloudTrail
- DeviceFileEvents
- DeviceProcessEvents
- GitHubAuditLogsV2_CL / V3_CL / GitHubAuditLog_CL
- Syslog
Hunt 01 — GitHub audit events with exact Gheist branch and workflow markers
Required Sentinel telemetry: GitHubAuditLogsV2_CL / V3_CL / GitHubAuditLog_CL
union isfuzzy=true GitHubAuditLogsV2_CL, GitHubAuditLogsV3_CL, GitHubAuditLog_CL
| where TimeGenerated >= ago(30d)
| extend Raw=tostring(pack_all())
| where Raw matches regex @"ci-sync-[0-9a-z]{6}" and (Raw has "sync-check" or Raw has "ci: sync config")
| project TimeGenerated, Raw
| order by TimeGenerated descHunt 02 — GitHub audit events carrying the reported gheist user agent
Required Sentinel telemetry: GitHubAuditLogsV2_CL / V3_CL / GitHubAuditLog_CL
union isfuzzy=true GitHubAuditLogsV2_CL, GitHubAuditLogsV3_CL, GitHubAuditLog_CL
| where TimeGenerated >= ago(30d)
| extend Raw=tostring(pack_all())
| where Raw contains 'gheist'
| project TimeGenerated, Raw
| order by TimeGenerated descHunt 03 — Rapid creation and deletion of ci-sync workflow runs
Required Sentinel telemetry: GitHubAuditLogsV2_CL / V3_CL / GitHubAuditLog_CL
union isfuzzy=true GitHubAuditLogsV2_CL, GitHubAuditLogsV3_CL, GitHubAuditLog_CL
| where TimeGenerated >= ago(30d)
| extend Raw=tostring(pack_all()), Branch=extract(@"(ci-sync-[0-9a-z]{6})",1,tostring(pack_all()))
| where isnotempty(Branch)
| extend EventKind=case(Raw has_any ("workflow_run.delete","workflow_run.destroy","workflow_run_deleted"),"Delete",Raw has_any ("workflow_run.completed","workflow_run.complete","completed"),"Complete","Other")
| where EventKind != "Other"
| summarize FirstComplete=minif(TimeGenerated,EventKind=="Complete"), FirstDelete=minif(TimeGenerated,EventKind=="Delete"), Events=make_set(EventKind) by Branch
| where isnotnull(FirstComplete) and isnotnull(FirstDelete) and FirstDelete between (FirstComplete .. FirstComplete+2m)
| order by FirstDelete descHunt 04 — GitHub OIDC AWS role assumption on ci-sync branch
Required Sentinel telemetry: AWSCloudTrail
AWSCloudTrail
| where TimeGenerated >= ago(30d)
| where EventName == "AssumeRoleWithWebIdentity"
| extend Raw=strcat(tostring(RequestParameters)," ",tostring(AdditionalEventData)," ",tostring(UserIdentityPrincipalid))
| where Raw matches regex @"ci-sync-[0-9a-z]{6}"
| project TimeGenerated, EventName, SourceIpAddress, UserIdentityArn, RequestParameters, AdditionalEventData, UserIdentityPrincipalid, AWSRegion
| order by TimeGenerated descHunt 05 — Secrets Manager enumeration following reported GitHub branch OIDC role
Required Sentinel telemetry: AWSCloudTrail
AWSCloudTrail
| where TimeGenerated >= ago(30d)
| where EventSource =~ "secretsmanager.amazonaws.com"
| where EventName in~ ("ListSecrets","DescribeSecret","GetSecretValue")
| where UserIdentityArn matches regex @"ci-sync-[0-9a-z]{6}"
| project TimeGenerated, EventName, SourceIpAddress, AWSRegion, UserIdentityArn, RequestParameters
| order by TimeGenerated descHunt 06 — Gheist self-hosted runner secret sweep files
Required Sentinel telemetry: DeviceFileEvents
DeviceFileEvents
| where TimeGenerated >= ago(30d)
| where FileName in~ (".gdmp_pat",".gdmp_patg",".gdmp_sweep")
| project TimeGenerated, DeviceName, FolderPath, FileName, ActionType, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName
| order by TimeGenerated descHunt 07 — Gheist self-hosted runner commands and markers
Required Sentinel telemetry: DeviceProcessEvents
DeviceProcessEvents
| where TimeGenerated >= ago(30d)
| where ProcessCommandLine has_any ("gdmp-pivot",".gdmp_sweep",".gdmp_patg",".gdmp_pat") or (ProcessCommandLine contains "base32 -w 120" and ProcessCommandLine has_any ("secrets","env"))
| project TimeGenerated, DeviceName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName
| order by TimeGenerated descHunt 08 — Linux Syslog fallback for Gheist runner markers
Required Sentinel telemetry: Syslog
Syslog
| where TimeGenerated >= ago(30d)
| where SyslogMessage has_any ("gdmp-pivot",".gdmp_sweep",".gdmp_patg",".gdmp_pat")
| project TimeGenerated, Computer, ProcessName, SyslogMessage, SeverityLevel
| order by TimeGenerated descDetection Notes
Highest signal: exact branch plus workflow/commit, user agent `gheist`, AWS OIDC assumption from `ci-sync-*`, and `.gdmp_*` files on runners. GitHub connector tables are custom and may be differently named; the union requires at least one listed table. `pack_all()` deliberately avoids invented normalized fields but depends on raw payload retention. CloudTrail can omit OIDC subject; a negative result is not conclusive. Self-hosted job reportedly has bugs, so not all intended collection steps necessarily succeed.
Scope: Results are limited to log sources and retention ingested into Microsoft Sentinel. No customer workspace was queried for this publication.