2026-10-26 Gheist GitHub Actions Secret Dumping — Sentinel Hunts

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

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 desc

Hunt 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 desc

Hunt 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 desc

Hunt 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 desc

Hunt 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 desc

Hunt 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 desc

Hunt 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 desc

Hunt 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 desc

Detection 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.

Leave a Reply