A stolen token can reach far beyond a repository
A compromised developer token is not just a way into someone’s code. In the Gheist campaign, attackers used GitHub credentials to plant workflows designed to expose repository secrets—and, in some cases, credentials tied to cloud environments. That’s the uncomfortable part: the attack can borrow the organization’s own automation and GitHub’s own logging path, leaving defenders with fewer obvious network clues.
Wiz Research counted more than 13,500 malicious branches across over 850 compromised GitHub accounts. The campaign touched personal projects as well as private enterprise repositories. GitHub’s safeguards for public repositories do not solve the problem once an attacker has a credential with write access to an enterprise repository. Dark Reading and Wiz Research describe the campaign and its mechanics.
That distinction matters. A public-repository protection is useful, but it is not a substitute for controlling who holds a token, what it can change, and how long it remains valid.
The workflow was built to blend in
The observed commits created branches with names beginning ci-sync-, followed by a short suffix, then added .github/workflows/sync-check.yml. One reported commit message was ci: sync config. The unsigned commit appeared under the token owner’s identity, making the activity look like ordinary repository work at a glance.
A push to the branch triggered GitHub Actions. The workflow tried to print environment variables and repository secrets into its logs. It encoded the output in Base32 first. That choice is practical, not exotic: secret masking commonly catches a secret when it appears verbatim, but an encoded value no longer matches the original string. The attacker then retrieved the logs through GitHub’s API and quickly deleted the workflow run.
This is why a network-only response can miss the point. The data was retrieved through GitHub itself; there may be no external attacker domain or outbound connection to block. The suspicious behavior is in the repository, workflow, audit trail, and potentially the cloud identity trail.
The workflow also attempted to expose secrets made available through deployment environments. Where repositories had environments configured, the job could enumerate them using a matrix and run in each environment. That raises the stakes because environment-scoped values can include production credentials. A restrictive GITHUB_TOKEN permission setting does not by itself remove the workflow’s access to the secrets context.
The campaign changed as it ran
Wiz reported two parallel variants. One used hexadecimal branch suffixes and a simpler payload. Another, called Tool B in the research, used base36 suffixes and evolved quickly: it added a timeout and environment-matrix behavior, then introduced a Base32 separator that likely helped an operator parse the logs.
Tool B later added a second job aimed at repositories with self-hosted runners. The job ran on the host rather than only on GitHub-managed infrastructure and attempted to search for credentials, including private keys, process environments, AWS instance-role credentials, runner credential files, and Kubernetes service-account tokens. Wiz described this job as buggy and reported no successful run in its analysis. That caveat matters; attempted collection is not proof that those credentials were stolen.
The presence of a self-hosted runner still changes incident scope. If a ci-sync branch executed there, teams should investigate the runner host and its attached cloud identity, not just the repository. The research identifies files such as .gdmp_pat, .gdmp_patg, and .gdmp_sweep, plus gdmp-pivot log markers, as clues associated with this activity.
Private repositories are not a safety boundary
The campaign’s enterprise activity illustrates a basic but often underappreciated point: repository privacy controls who can see code, not what a compromised writer can do with the repository’s automation. Wiz observed attackers cycling through tokens belonging to several users and automation accounts, cloning private repositories, and planting workflows. It also reported later use of cloud credentials from the same organization to enumerate AWS Secrets Manager.
The breach path therefore can cross several administrative boundaries: a developer account, a repository, an Actions workflow, an environment secret, and a cloud role. Each handoff deserves separate controls and logging. If an organization treats “private repo” as equivalent to “safe repo,” it has confused confidentiality with authorization.
Wiz also found overlap between Gheist-affected accounts and other abuse, including malware distribution and attempts to use GitHub Actions as a source of computing resources. The Hacker News reported that some of the same accounts were involved in separate repository abuse and discussed possible malware infections among victims. That overlap is a reason to examine the account and device history broadly, not assume the branch is the whole incident. The Hacker News
What defenders should search for
Start with the repository history. Look for branches matching ci-sync- followed by six characters, particularly where the only added file is .github/workflows/sync-check.yml and the commit message is ci: sync config. Check GitHub audit logs for workflow runs on those branches that were deleted shortly after completion, and search for activity associated with the gheist user agent.
Then follow the identity outward. For workflows that authenticate to cloud providers with OIDC, inspect cloud audit logs for role assumptions or token exchanges whose subject includes a ci-sync- branch. This can reveal a workflow run even when GitHub logs are incomplete or unavailable. If the repository uses self-hosted runners, search host files and job logs for the markers identified above.
These indicators are useful, but no single one should be treated as a complete verdict. A branch name can be copied, and a deleted run can have benign explanations. Correlate the branch, commit, workflow contents, actor, timing, and cloud activity before closing the investigation.
Containment means rotating secrets, not just deleting branches
If a repository was affected, remove the malicious workflow and branch—but don’t mistake cleanup for containment. Rotate every repository, organization, and environment secret available to the affected workflow. Revoke the token that pushed the commits, and investigate the token owner’s device for possible infostealer activity. Where a branch ran on a self-hosted runner, treat the host and its instance role as potentially compromised.
Do not approve held sync-check runs or execute code from ci-sync branches. If a cloud role may have been assumed, review its activity and rotate or revoke the relevant credentials according to the organization’s incident process. The sources describe the risk and recommended checks; the exact response should follow the organization’s evidence and cloud configuration.
Longer term, use short-lived, narrowly scoped credentials instead of broad personal access tokens where possible. Require reviewers for environments that contain production secrets, and constrain OIDC trust policies to protected branches or environments. Self-hosted runners should be ephemeral and isolated; they should not expose an unrestricted Docker socket or broad metadata access.
The lesson isn’t that GitHub Actions is inherently unsafe. It’s that workflow permissions, credential lifetime, environment approvals, and runner isolation have to be designed as a chain. A stolen token can reach the next link if that link trusts the repository too much.