Am 25. Oktober veröffentlichte Wiz Research die Ergebnisse einer seit dem 15. September laufenden Verfolgung: eine automatisierte Kampagne, die gestohlene GitHub-Token nutzt, um in jedem erreichbaren Repository einen Workflow zu pflanzen, der die Secrets des Repositories in das eigene Workflow-Log schreibt. Das Tool lädt das Log anschließend über die GitHub-API herunter und löscht den Lauf Sekunden später. Seine Anfragen tragen den User-Agent `gheist` – daher der Name.

Bilanz: mehr als 13.500 gepflanzte Branches in mehr als 850 kompromittierten Konten, von persönlichen Projekten bis zu privaten Repositories großer Unternehmen. Rund zwei Drittel dieser Konten waren am 24. Oktober nicht mehr auf GitHub verfügbar – vermutlich Reaktion, nachdem Wiz den Vorfall gemeldet hatte.

Der Angriff ist erstaunlich langweilig

Für jedes Repository legt das Tool einen Branch namens `ci-sync-` plus sechs Zufallszeichen an und committet dort `.github/workflows/sync-check.yml` mit der Nachricht `ci: sync config`. Der Commit ist unsigniert und erscheint unter dem Namen des Token-Inhabers. Der Push startet den Workflow, und der erledigt den Rest.

Drei Details machen ihn wirksam. GitHub maskiert Secret-Werte in Logs nur, wenn sie wortwörtlich erscheinen – ein base32-kodiertes Secret passt auf kein Muster und wird vollständig ausgegeben. `permissions: {}` beschränkt das `GITHUB_TOKEN` des Jobs, das der Angreifer nicht braucht, aber nicht den `secrets`-Kontext. Und existieren Deployment-Umgebungen, listet das Tool sie in einer Job-Matrix mit `environment: ${{ matrix.environment }}` auf – wodurch umgebungsspezifische Secrets, oft Produktionszugänge, mit ausgegeben werden.

Der entscheidende Punkt ist die Exfiltration: Die Daten verlassen das Unternehmen über GitHubs eigene API. Es gibt keine Angreiferdomain, keinen ausgehenden Verbindungsaufbau, nichts, was eine Firewall blockieren könnte.

GitHubs Schutz funktionierte – nur dort, wo er greift

Seit dem 28. Juli 2026 hält GitHub potenziell bösartige Workflow-Läufe in öffentlichen Repositories zurück, bis ein Mitarbeiter sie freigibt. Diese Kontrolle hat funktioniert: Jeder untersuchte öffentliche Gheist-Lauf, über alle Varianten hinweg, stand auf `action_required`. Die Nutzlast lief nie.

Nur gilt dieser Halt nicht für private Repositories und nicht für GitHub Enterprise Server. Dort zeigen Audit-Logs Läufe auf `ci-sync-*`-Branches, die abgeschlossen und anschließend vom `gheist`-User-Agent gelöscht wurden. CloudTrail zeigt die eigenen Workflows der betroffenen Organisationen, die auf diesen Branches AWS-Rollen annahmen – der Beweis, dass Actions ohne Freigabe ausgeführt wurden. In Unternehmensumgebungen beobachtete Wiz zudem, wie das Tool innerhalb von Minuten Token mehrerer Nutzer und Automatisierungskonten durchlief, private Repositories klonte und Stunden später Cloud-Zugangsdaten derselben Organisation nutzte, um AWS Secrets Manager zu durchsuchen.

Das ist die eigentliche Nachricht: Die wirksamste Schutzmaßnahme der Plattform gegen genau diese Angriffsklasse ist an den Repositories vorbeigegangen, in denen die wertvollsten Secrets liegen.

Das Werkzeug hat dazugelernt

Zwei Varianten liefen parallel. Tool A nutzte ein Hex-Suffix und die schlichte Nutzlast. Tool B nutzte Base36, fügte `timeout-minutes: 6` und die Umgebungsmatrix innerhalb von Stunden nach dem ersten Lauf am 28. September hinzu und passte die Nutzlast pro Repository an. Am 19. Oktober kam ein base32-Separator `LAFA====` für den Log-Parser des Betreibers hinzu, Stunden später ein zweiter Job `sync-host` mit `runs-on: self-hosted`, offenbar nur für Repositories mit registriertem Self-Hosted-Runner. Er sucht unter anderem nach 31 Token-Formaten – darunter Schlüssel für KI-Anbieter wie Anthropic, Hugging Face, Groq und NVIDIA –, nach privaten Schlüsseln, `.env`-Dateien, Terraform-State und Shell-Historien, liest `/proc/*/environ` und versucht über den Docker-Socket einen Container mit dem Root-Dateisystem des Hosts zu starten.

Dieser zweite Job ist allerdings fehlerhaft: falsche Runner-Schlüsseldatei, defekte Regex für URL-Zugangsdaten, keine GCP- oder Azure-Token. Nicht jede Effizienz eines Angreifers ist Absicht.

Ein Konto, viele Kampagnen

Die kompromittierten Konten werden von mehreren Akteuren genutzt. Am 6. Oktober benannten 391 Konten rund 1.461 Repositories in Lockangebote wie `fl-studio-crack` um; 166 davon tauchen in der Gheist-Aktivität nie auf – der Pool ist größer als der Fußabdruck dieser Kampagne. Am 18. Oktober erstellten 25 Gheist-Konten innerhalb von neun Minuten jeweils ein „Windows RDP Runner“-Repository, offenbar um Actions als kostenlose Rechenleistung zu missbrauchen. Bei drei Opfern fanden sich zudem Spuren von PolinRider, Malware mit Bezug zu Nordkorea. Die Spuren der Zugangsdaten passen dazu: Infostealer-Logs und Token aus früheren Supply-Chain-Kompromittierungen, etwa dem Trivy-Vorfall im März.

Was Verteidiger jetzt tun sollten

Suchen: Branches nach dem Muster `ci-sync-` plus sechs Zeichen, deren einzige Änderung eine neue Datei `.github/workflows/sync-check.yml` mit der Commit-Nachricht `ci: sync config` ist. In Audit-Logs: Workflow-Läufe auf `ci-sync-*`, die innerhalb einer Minute nach Abschluss gelöscht wurden. Und – der nützlichste Fund ohne GitHub-Logs – Cloud-Audit-Logs nach Rollenannahmen, deren Subjekt einen `ci-sync-`-Branch enthält.

Reagieren: Jedes Secret rotieren, das für ein betroffenes Repository erreichbar war. Das Löschen von Lauf oder Branch macht die Offenlegung nicht rückgängig. Die pushenden Token widerrufen und die Geräte ihrer Inhaber auf Infostealer prüfen. Lief ein `ci-sync`-Branch auf einem Self-Hosted-Runner, Host und Instanzrolle als kompromittiert behandeln. Gehaltene `sync-check`-Läufe nicht freigeben.

Härten: Reviewer für Umgebungen mit Produktions-Secrets verlangen, OIDC-Vertrauensrichtlinien auf geschützte Branches und Umgebungen begrenzen, GitHub Apps oder kurzlebige fein granulisierte Token statt persönlicher Access-Token nutzen, Self-Hosted-Runner ephemer und ohne Docker-Socket oder uneingeschränkten Metadatenzugriff betreiben.

Eine einzige Designentscheidung bei GitHub – der Freigabe-Halt nur für öffentliche Repositories – hat entschieden, wo dieser Angriff Schaden anrichtete. Solche Entscheidungen sind unsichtbar, bis jemand sie ausnutzt.