Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 4 min read

A stolen token turns your own CI into a secret dump. I built a local detector that watches for the act

Your private repository's CI can betray you without a single strange domain ever appearing. That is the shape of Gheist, a campaign Wiz Research has tracked since September 15, 2026: more than 13,500 planted branches rec

A stolen token turns your own CI into a secret dump. I built a local detector that watches for the act

Your private repository's CI can betray you without a single strange domain ever appearing. That is the shape of Gheist, a campaign Wiz Research has tracked since September 15, 2026: more than 13,500 planted branches recovered from more than 850 compromised accounts, from personal projects to enterprise private repositories.

The mechanism is brutally simple. A stolen token β€” Wiz points mainly to infostealer logs and credentials exposed in earlier supply-chain incidents β€” creates a branch named ci-sync- plus six random characters, then commits a workflow at .github/workflows/sync-check.yml with the message ci: sync config. The push triggers the workflow, which prints repository secrets and environment variables after base32-encoding them. GitHub masks secret values in logs only when they appear verbatim, so an encoded secret prints in full. Seconds after the job finishes, the attacker's tool downloads the workflow log through the GitHub API and deletes the run. No external command-and-control is needed, because the secrets leave through GitHub's own API. A second variant, seen October 19, 2026, targets self-hosted runners with a job named sync-host, hunting the host filesystem, process environments, and mounted containers for 31 credential formats.

Deleting the branch or the run does not undo the exposure. Every secret the repository could read must be rotated, and the token that pushed the branch must be revoked.

Why the existing answers miss it

GitHub's control from July 28, 2026 holds suspicious workflow runs in public repositories pending collaborator approval, and it stopped every public run Wiz checked. It does not cover private repositories or GitHub Enterprise Server β€” exactly where Gheist succeeds.

The organization audit log is ground truth: it records the created, completed, and deleted workflow-run events, plus the tool's user agent. But the audit-log API is available to organization owners only, and nothing ships a packaged Gheist rule β€” the operator must already know the pattern, write the query, and interpret the result.

Static secret scanners scan content. A dump workflow contains no secret text, so it is invisible to them by construction β€” and deleting the offending line does nothing to the credential anyway. Rotation is the only step that neutralizes exposure.

SIEM forwarding works, but it needs an enterprise plan, streaming setup, and hand-written queries. Manual audit runbooks are exact and free, but they are one-off: no scheduling, no report, no false-positive discipline, no checklist.

What I built: gheist-guard

gheist-guard is a local, deterministic, read-only behavioral detector. It watches for the act, from the evidence that survives it: the branch name, the audit-log sequence, the tool fingerprint, and the rotate checklist.

  • One command, one verdict per repository: CLEAR, SUSPECT, or CONFIRMED.
  • Three commands: scan (REST path over branches, workflows, and runs), audit --org (audit-log path, or --events with recorded payloads), and checklist (re-print the rotate checklist from a saved report).
  • Seven rules: the ci-sync- branch pattern, the sync-check workflow identity, base32/base64 dump content, completed-then-deleted within 60 seconds, the gheist user agent, a generic unreviewed-workflow rule that also catches the adjacent GhostAction shape, and the self-hosted sync-host variant.
  • Read-only by default. The checklist lists secret NAMES only β€” it never prints a secret value, never deletes anything, never rotates anything itself. A partial signal is SUSPECT with its next check, never silently CLEAR and never CONFIRMED. The name "Gheist" appears in a verdict only when the full documented pattern matches.

Install from source (the tool is not on npm):

git clone https://github.com/techdevfun/gheist-guard
cd gheist-guard
npm ci
npm run build

Or use it as a GitHub Action: techdevfun/gheist-guard@v1.

Measured numbers (build 2026-10-27)

  • Test suite: 52/52 green, TypeScript strict clean.
  • False-positive probe: 24 legitimate branch names + 5 legitimate workflows β†’ 0 CONFIRMED, 1 SUSPECT (ci-sync-config matches the six-character pattern by design; documented boundary).
  • Rule-evaluation latency (in-process, 500 iterations on the planted fixture): median 0.0045 ms, p95 0.0148 ms per repository.
  • Live REST end-to-end (real GitHub API, read-only): 3 real repositories scanned, all CLEAR (vetgate, context-diet, breakcheck); plus a private test repository with a branch-only plant: SUSPECT before a real adapter fix (it only read the default branch) and CONFIRMED after (R1+R2+R3).
  • Demo (real CLI runs on fixtures, not staged): a 9.4-second GIF plus the full transcript in the repo's examples.

Where rivals beat us

Honestly, and this stays in the README too:

  • GitHub's native controls beat us on authority and position. The audit log is ground truth; the approval hold stops public execution before any dump. We only read what the platform records.
  • Socket, StepSecurity, and GitGuardian beat us on threat-intelligence scale. They see campaigns across the ecosystem; we see only your repositories. We will never be the first to name a new campaign.
  • Gitleaks and TruffleHog beat us on secret-pattern breadth in code and history. We do not scan secrets in code at all β€” run one of them alongside this tool if you need that.
  • SIEM tooling beats us on central correlation across sources. We are a single-purpose local tool.
  • Manual runbooks beat us on zero-install immediacy. Copy-paste commands work today with no install. We have to earn the install with scheduling, reporting, false-positive discipline, and the checklist.
  • The July 28 approval hold beats us on prevention for public repositories. Our value concentrates where the hold does not reach: private repositories, GitHub Enterprise Server, and the response after a run has completed and been deleted.

Limits, stated plainly

  • The audit-log path is fixture-tested end to end. No organization-owner account was available for a live audit-log test.
  • GitHub Enterprise Server logs are documented but untested.
  • Pull-request association is an approximation: a workflow commit with an associated pull request is treated as reviewed.
  • The tool cannot see a run that was deleted before the first scan unless the branch or the audit log still carries it.
  • "Gheist" is Wiz's name for the campaign. This tool credits Wiz as the source of the name and pattern; there is no link to or endorsement by Wiz.

The code is MIT, on GitHub: https://github.com/techdevfun/gheist-guard

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.