Fast jeder GitHub-Actions-Workflow zieht fremden Code. actions/checkout@v4, docker/build-push-action@v6, dazu ein Dutzend kleinerer Helfer aus dem Marketplace. Bequem, aber jede dieser Zeilen ist eine Vertrauensentscheidung. Wer @v4 schreibt, sagt damit: ich fuehre aus, was hinter diesem Tag heute steht, und auch das, was morgen dahinter steht. Genau das ist das Problem.
Ein Tag ist kein Vertrag
Ein Git-Tag ist beweglich. Der Maintainer einer Action kann v4 jederzeit auf einen neuen Commit umbiegen. Im Normalfall ist das ein Patch. Im schlechten Fall uebernimmt jemand das Repository, kompromittiert das Konto des Maintainers oder schleust ueber eine manipulierte Abhaengigkeit Code ein und schiebt den Tag still auf einen boesartigen Stand. Beim naechsten Lauf zieht deine Pipeline diesen Code, mit allen Secrets und Token, die im Job verfuegbar sind.
Das ist kein theoretisches Szenario. Der Angriff auf tj-actions im Fruehjahr 2025 hat genau so funktioniert. Ein kompromittierter Zugang, ein umgebogener Tag, und tausende Workflows haben ihre Secrets in die Logs geschrieben. Wer auf feste Commit-Hashes gepinnt hatte, war nicht betroffen.
SHA-Pinning statt Tag
Die Loesung ist unspektakulaer: statt eines Tags referenzierst du den vollen Commit-SHA. Ein Commit-Hash ist unveraenderlich. Er zeigt auf exakt einen Stand des Codes, und niemand kann diesen Stand nachtraeglich austauschen, ohne dass sich der Hash aendert.
Aus uses: actions/checkout@v4 wird uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2. Der Kommentar mit der Versionsnummer bleibt, damit ein Mensch noch lesen kann, welche Version das ist. Ausgefuehrt wird aber nur der Hash. Kippt der Maintainer den Tag, aendert das an deinem Lauf nichts.
Wichtig: der kurze Hash reicht nicht. Pinne immer den vollen 40-stelligen SHA. Kurze Hashes lassen sich unter Umstaenden mit Kollisionen aushebeln, und GitHub loest sie ohnehin gegen den vollen Wert auf.
Der Preis: Updates werden sichtbar
SHA-Pins haben einen offensichtlichen Nachteil. Sicherheitspatches der Action landen nicht mehr automatisch bei dir. Ein gepinnter Hash bleibt stehen, bis jemand ihn bewusst hebt. Das ist aber kein Argument gegen Pinning, sondern ein Argument fuer Automatisierung.
Dependabot und Renovate koennen SHA-Pins pflegen. Beide erkennen den Kommentar mit der Version, schlagen den neuen Hash als Pull Request vor und halten den Versionskommentar aktuell. Damit bekommst du beides: unveraenderliche Referenzen im Normalbetrieb und einen kontrollierten, sichtbaren Update-Pfad ueber Pull Requests, die ein Mensch anschaut, bevor sie mergen.
Allowlist: welche Actions ueberhaupt erlaubt sind
Pinning sichert eine einzelne Zeile ab. Es sagt nichts darueber, welche Actions in der Organisation ueberhaupt laufen duerfen. Genau hier setzt die Allowlist an. In den GitHub-Einstellungen einer Organisation laesst sich unter Actions genau festlegen, welche Actions zugelassen sind: nur die von GitHub selbst, verifizierte Publisher, oder eine explizit gepflegte Liste einzelner Repositories.
Der praktische Effekt ist gross. Ein Entwickler kann nicht mehr spontan eine unbekannte Action aus dem Marketplace einbinden, die niemand geprueft hat. Neue Actions muessen bewusst auf die Liste, was einen kurzen Reviewschritt erzwingt. In Kombination mit SHA-Pinning entsteht daraus eine zweistufige Kontrolle: die Allowlist entscheidet, ob eine Action laufen darf, der Pin entscheidet, welcher exakte Code laeuft.
Wo anfangen
Der Einstieg ist ueberschaubar. Erst die kritischen Workflows durchgehen, alle Third-Party-Actions auf volle SHAs pinnen und den Versionskommentar setzen. Dann Dependabot oder Renovate so konfigurieren, dass sie diese Pins pflegen. Zuletzt auf Organisationsebene die Allowlist aktivieren und mit den bereits genutzten, geprueften Actions befuellen. Reihenfolge ist wichtig, sonst blockiert die Allowlist laufende Pipelines.
Du willst wissen, welche Actions in deinen Pipelines ungepinnt laufen und wo Token unnoetig exponiert sind? Der GitSecOps QuickCheck prueft deine GitHub-Actions-Workflows systematisch und liefert eine priorisierte Befundliste. 1.990 Euro netto, Ergebnis in 3 Werktagen. Kontakt: mm@crank.zone.