GitHub-Actions-Lücke: KI prüfte, KI fand sie

Bei Snowflake ersetzte ein PR mit Copilot-Signatur ein sicheres env-Muster durch direkte Interpolation. Fünf Tage später fand das ein KI-Agent.

8 Min. Lesezeit

Am 17.08.2026 hat die Sicherheitsfirma Wiz einen Fall veröffentlicht, der ungewöhnlich gut dokumentiert ist: In einem öffentlichen Repository von Snowflake konnte jeder beliebige GitHub-Nutzer Befehle auf einem Actions-Runner ausführen, indem er ein Issue mit einem präparierten Titel öffnete. Der Weg dahin führt über einen Pull Request, in dessen Commit "Copilot Autofix powered by AI" als Mitautor steht.

Wir haben den Commit selbst geöffnet, statt den Bericht nachzuerzählen. Er sieht anders aus, als die Zusammenfassung vermuten lässt, und das ist der interessante Teil.

Was im Commit tatsächlich steht

Betroffen ist snowflakedb/snowflake-connector-net, der .NET-Connector. Der Pull Request #1218 vom 18.06.2026 trägt den Titel "Update jira workflows" und fasst zwei Workflow-Dateien an. Die eine, jira_close.yml, wird ausschließlich umformatiert: 38 Zeilen raus, 38 Zeilen rein, Einrückung von vier auf zwei Leerzeichen. Ein Aufräum-PR also.

In der zweiten Datei, jira_issue.yml, passiert daneben etwas anderes. Vorher wurde der Issue-Titel über eine Umgebungsvariable in die Shell gereicht:

env:
  ISSUE_TITLE: ${{ github.event.issue.title }}
  ISSUE_BODY: ${{ github.event.issue.body }}
run: |
  TITLE=$(echo "${ISSUE_TITLE//`/\\`}" | sed 's/"/\\"/g' | sed "s/'/\\\'/g")

Nachher steht der Ausdruck direkt im Shell-Block:

run: |
  TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g' | sed "s/'/\\\'/g")

Die drei env-Zeilen sind weg. Damit ist die Sache entschieden, und zwar aus einem Grund, der beim Überfliegen leicht untergeht: GitHub setzt ${{ ... }} ein, bevor die Shell die Zeile überhaupt sieht. Das sed läuft danach und kommt zu spät. Ein einfaches Anführungszeichen im Issue-Titel beendet das echo '...', alles Weitere ist Kommando.

Der Auslöser ist on: issues: types: [opened]. Ein Issue öffnen darf in einem öffentlichen Repository jeder.

Die zweite Lücke war schon vorher da

Der Workflow hatte eine Bedingung, die nach Schutz aussieht:

if: (github.event_name == 'issues' && github.event.pull_request.user.login != 'whitesource-for-github-com[bot]')

Bei einem issues-Event gibt es aber gar keinen Pull Request. github.event.pull_request ist dann leer, und die Prüfung läuft auf "leer ist ungleich Bot-Name" hinaus. Das ist immer wahr. Die Bedingung hat nie jemanden aufgehalten.

Diese Zeile steht im Diff auf beiden Seiten wortgleich. Sie ist also nicht in diesem PR entstanden, sie war vorher schon da und ist es bis heute. Wer den Fall erzählt, sollte die zwei Dinge auseinanderhalten: Der Aufräum-PR hat die Absicherung entfernt, die kaputte Torwächter-Bedingung stammt von woanders.

Wie der Agent vorgegangen ist

Gefunden hat das kein Mensch. Wiz betreibt einen autonomen Agenten namens "Red Agent", der über Snowflakes Programm zur Schwachstellenmeldung bei HackerOne unterwegs war. Er hat die GitHub-Organisation abgeklappert und den Workflow als anfällig markiert.

Beim Ausnutzen ist ihm zuerst ein Fehler unterlaufen. Sein erster Versuch kommentierte den Zeilenrest mit # aus, wodurch auch die schließende Klammer von TITLE=$(...) verschwand und die Shell mit einem Syntaxfehler abbrach. Statt aufzugeben, hat der Agent die Fehlermeldung gelesen, die Nutzlast auf ; echo ' umgebaut und es erneut versucht. Der zweite Versuch lief.

Zurück kam ein Aufruf von einem Actions-Runner mit den base64-kodierten Jira-Zugangsdaten. Das Token authentifizierte sich als qa@snowflake.net gegen Snowflakes Atlassian-Instanz und hatte Lesezugriff auf Projekte aus Engineering, Security Compliance und der Bug-Bounty-Verwaltung.

Snowflake hat am Tag der Meldung gepatcht, das Token am Tag darauf rotiert und über Audit-Logs nachgewiesen, dass im gesamten Zeitfenster nur Wiz zugegriffen hat.

DatumWas passierte
18.06.2026PR #1218 gemerged, Lücke ist live
23.06.2026Red Agent findet und nutzt sie aus, Meldung über HackerOne
23.06.2026Snowflake patcht am selben Tag (PR #1402)
24.06.2026Jira-Token rotiert
17.08.2026Veröffentlichung durch Wiz

Fünf Tage zwischen "live" und "gefunden". Das ist die Zahl, die hängen bleibt.

Was Wiz noch am Erscheinungstag zurückgenommen hat

Der Blogeintrag hat um 19:57 UTC ein Update bekommen, und das verschiebt die Aussage deutlich. Wiz stellt darin klar: Sicher ist, dass Copilot als Mitautor den gemergten PR geprüft und für unauffällig befunden hat. Ob die Codeänderung selbst von einer KI stammt, sei unklar.

Das ist ein Unterschied, der zählt. Die Schlagzeile "KI baut Sicherheitslücke" wird damit zu "KI übersieht Sicherheitslücke", und das ist die schwächere, aber besser belegte Behauptung. Im Commit stehen drei Mitautoren: zwei Snowflake-Beschäftigte und der Bot der GitHub-Codeanalyse. Diese Signatur trägt üblicherweise ein, wer einen automatisch vorgeschlagenen Fix übernimmt. Zwingend ist der Schluss trotzdem nicht, und Wiz zieht ihn ausdrücklich nicht mehr.

Für die Praxis ändert das wenig, für die Redlichkeit viel. Wer den Fall im Team weitergibt, sollte ihn als das erzählen, was belegt ist: Eine Absicherung verschwand in einem Aufräum-PR, und die automatische Sicherheitsprüfung von GitHub hat das durchgewinkt.

Was daraus für eigene Repositories folgt

Der Fix, den Snowflake gefahren hat, ist die beste Vorlage. Er stellt die env-Übergabe wieder her und baut die JSON-Nutzlast danach mit jq statt mit Anführungszeichen-Bastelei:

env:
  ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
  PAYLOAD=$(jq -n --arg title "$ISSUE_TITLE" '{fields: {summary: $title}}')

Damit landet der Titel als Daten in der Variable und nie als Text im Skript. Was sich daraus für den eigenen Bestand mitnehmen lässt:

Kein github.event.* direkt in einem run-Block. Betroffen sind alle von außen befüllbaren Felder: Issue-Titel und -Text, Kommentare, Branch- und Pull-Request-Namen, Commit-Nachrichten. Immer über env gehen, dann sind es Daten und keine Anweisungen. Ein grep -rn 'github.event' .github/workflows/ zeigt in einer Minute, wo es bei einem selbst steht.

Den Grund neben die Absicherung schreiben, nicht nur in den Commit. Das sichere Muster stand im Repository, weil jemand es einmal bewusst so gebaut hat. Dieser Grund war nur in der Historie greifbar, und Historie liest weder ein aufräumender Mensch noch ein Modell mit begrenztem Kontext. Eine Kommentarzeile darüber ist billiger als jede Leitplanke.

Aufräum-PRs sind keine harmlose Kategorie. Der PR sah aus wie eine Einrückungskorrektur, und ein Teil davon war es auch. In derselben Änderung verschwanden drei env-Zeilen. Wer Formatierung und Logik im selben PR mischt, macht Reviews blind, und das gilt für KI-Vorschläge genauso wie für menschliche.

Rechte und Laufzeiten klein halten. In dem Workflow lagen langlebige Jira-Zugangsdaten als Secret. Sie waren in dem Moment weg, in dem der Runner fremden Code ausführte. Kurzlebige Token und ein minimales permissions: je Job begrenzen den Schaden, wenn doch etwas durchrutscht.

Einordnung

Script Injection in GitHub Actions ist keine neue Klasse, sie steht seit Jahren in der Härtungsanleitung von GitHub. Neu ist die Geschwindigkeit auf der anderen Seite. Fünf Tage Fenster bei einem Fehler, der früher jahrelang unbemerkt geblieben wäre, heißt: Automatisierte Suche findet solche Muster inzwischen schneller, als der übliche Patch-Rhythmus reagiert. Das spricht für kurzlebige Secrets, nicht für Panik.

Und es zeigt eine Grenze, die wir schon beschrieben haben: Ein Modell sieht den Code, nicht die Entscheidung dahinter. Warum das beim Prüfen von KI-Änderungen der entscheidende Punkt ist, steht in Die 79-Prozent-Falle. Wie man Agenten im Team so aufstellt, dass sie an Stellen wie diese gar nicht erst kommen, behandelt Coding-Agenten im Team sicher betreiben.

Quellen4