Wer GitHub Actions oder GitLab CI intensiv nutzt, stößt irgendwann auf die Grenze der gehosteten Runner: zu langsam, zu teuer bei hohem Volumen, kein Zugriff auf interne Systeme. Die Antwort heißt dann Self-hosted Runner. Was viele Teams dabei übersehen: Ein selbst betriebener Runner ist kein Werkzeug wie jedes andere. Er ist eine Maschine, auf der fremder Code mit euren Rechten läuft.
Was ein Runner eigentlich tut
Ein Runner nimmt Jobs aus der Pipeline entgegen und führt sie aus. Er klont Code, installiert Abhängigkeiten, baut Artefakte, deployt in Zielumgebungen. Dafür braucht er Netzwerkzugang, Zugangsdaten und oft weitreichende Rechte auf dem Host. Bei gehosteten Runnern kümmert sich der Anbieter um Isolation: Jeder Job läuft in einer frischen virtuellen Maschine, die danach verworfen wird. Beim Self-hosted Runner übernehmt ihr diese Verantwortung selbst. Und genau da beginnt das Problem.
Das Kernrisiko: fremder Code auf eurer Infrastruktur
Jeder Pipeline-Job führt Code aus. Nicht nur euren eigenen, sondern auch den von Third-Party Actions, Build-Skripten und Abhängigkeiten, die beim Build nachgeladen werden. Ein einziger kompromittierter npm-Paket-Postinstall-Hook reicht, und auf dem Runner läuft Schadcode. Auf einem gehosteten Runner endet der Schaden mit der VM. Auf eurem Self-hosted Runner sitzt der Angreifer jetzt in eurem Netz.
Besonders kritisch wird es bei öffentlichen Repositories. Wer einen Self-hosted Runner an ein öffentliches Repo hängt, lädt faktisch jeden Pull-Request-Ersteller ein, Code auf der eigenen Maschine auszuführen. GitHub warnt davor ausdrücklich in der eigenen Dokumentation. Trotzdem passiert es regelmäßig, oft aus Bequemlichkeit oder weil das Repo erst später öffentlich wurde.
Persistenz: der Runner vergisst nichts
Der zweite Unterschied zu gehosteten Runnern ist die Lebensdauer. Ein klassischer Self-hosted Runner läuft dauerhaft und führt Job nach Job aus. Was ein Job auf der Maschine hinterlässt, sieht der nächste: Caches, Zugangsdaten in Umgebungsvariablen, geklonte Repositories, Docker-Images, manipulierte Toolchains. Ein Angreifer, der einmal Code ausführen konnte, kann sich einnisten und alle folgenden Builds mitlesen oder manipulieren. Das trifft dann nicht nur ein Projekt, sondern alles, was über diesen Runner läuft.
Was in der Praxis schiefgeht
Drei Muster sehen wir immer wieder. Erstens: Der Runner läuft als Root oder mit Docker-Socket-Zugriff, womit jeder Job faktisch Vollzugriff auf den Host hat. Zweitens: Ein Runner bedient mehrere Teams oder Projekte mit unterschiedlichem Vertrauensniveau, ohne Trennung. Drittens: Der Runner steht im internen Netz ohne Segmentierung und erreicht Datenbanken, interne APIs und Produktionssysteme, die er für den Build gar nicht braucht.
Wie man es besser macht
Das Gegenmodell heißt ephemere Runner: Für jeden Job wird eine frische Instanz gestartet und danach zerstört, ob als Container, VM oder über Autoscaling-Lösungen wie Actions Runner Controller auf Kubernetes. Damit fällt die Persistenz als Angriffsfläche weg. Dazu gehören klare Grundregeln: Runner nie an öffentliche Repos hängen, Jobs nicht als Root laufen lassen, Netzwerkzugriff auf das Nötigste beschränken, Secrets über kurzlebige Tokens statt statischer Zugangsdaten bereitstellen und Runner-Gruppen nach Vertrauensniveau trennen.
Nichts davon ist Raketentechnik. Aber es passiert nicht von allein, und es steht selten im Backlog, weil der Runner ja läuft. Genau solche Punkte sind es, die bei einem Audit oder einem echten Vorfall plötzlich sehr teuer werden.
Wo steht eure Pipeline?
Wenn ihr Self-hosted Runner betreibt und nicht sicher seid, wie es um Isolation, Rechte und Netzwerkzugriff steht, schafft der GitSecOps QuickCheck Klarheit: Wir prüfen eure Pipeline-Konfiguration in 3 Werktagen und liefern eine priorisierte Befundliste mit konkreten Maßnahmen. Festpreis 1.990 Euro netto. Kontakt: mm@crank.zone.