Nur was existieren muss, hat eine Tür. Die sicherste Tür ist die, die nie eingebaut wurde.
Nur was existieren muss, hat eine Tür. Die sicherste Tür ist die, die nie eingebaut wurde.

Wir verbringen viel Zeit damit, Mauern um das zu bauen, was existiert: Regeln für Branch Protection, verpflichtende Code Reviews, signierte Commits und Releases. Schicht um Schicht um Dinge, die wir am Leben halten. Aber wir stellen selten die tiefere Frage: Sollte das hier überhaupt existieren?

Jeder Branch in einem Repository ist eine Tür. An jede Tür kann geklopft werden. Und manche, die klopfen, sind verkleidet: Pull Requests, die hilfreich aussehen, die ein oberflächliches Review bestehen, aber modifizierte CI-Konfigurationen, veränderte Build-Skripte oder eingeschleuste Workflow-Befehle mitbringen. Die Pipeline vertraut dem Code, aber der Code verdient dieses Vertrauen nicht immer.

Das ist Poisoned Pipeline Execution. Das ist kein theoretisches Szenario, und es braucht keinen privilegierten Zugang. Es braucht nur ein Ziel: einen Branch, der Pull Requests akzeptiert, verbunden mit einer Pipeline, die ausführt, was ihr gegeben wird.

Was ist Poisoned Pipeline Execution?

Poisoned Pipeline Execution (PPE) ist als CICD-SEC-4 in den OWASP Top 10 CI/CD Security Risks klassifiziert. Diese Angriffe missbrauchen die Vertrauensbeziehungen, die in den Pipelines moderner Softwareentwicklungsprojekte fest verankert sind. Anders als klassische Anwendungsschwachstellen nutzt PPE die Automatisierung und das Vertrauen in Entwicklungsworkflows aus, um Schadcode mit den vollen Rechten des CI/CD-Systems auszuführen.

Sicherheitsforschende unterscheiden drei Varianten:

Direct PPE (D-PPE): Die angreifende Person modifiziert direkt CI-Konfigurationsdateien wie .github/workflows/, .gitlab-ci.yml oder Jenkinsfile. Das geschieht über direkte Commits auf ungeschützte Branches oder über Pull Requests von Forks. Der Schadcode wird ausgeführt, sobald die Pipeline durch das Push- oder Pull-Request-Event angestoßen wird.

Indirect PPE (I-PPE): Wenn die direkte Änderung von CI-Konfigurationen verhindert wird, injiziert die angreifende Person Schadcode in Dateien, die von der Pipeline referenziert werden: Skripte, Makefiles, Build-Tools oder Testabhängigkeiten.

Die im Januar 2026 für PHPUnit veröffentlichte Sicherheitswarnung GHSA-vvj3-c3rp-c85p ist ein Beispiel für diese Kategorie: Eine manipulierte .coverage-Datei stellt einen Angriff auf eine Datei dar, die die Pipeline während der Testausführung verarbeitet.

Noch deutlicher zeigt sich das in der im April 2026 veröffentlichten Sicherheitswarnung GHSA-qrr6-mg7r-m243: Über eine in einem Pull Request manipulierte phpunit.xml lassen sich zusätzliche PHP-INI-Direktiven wie auto_prepend_file in Kindprozesse einschleusen. Im Code Review wirkt eine solche Änderung harmlos, sie ermöglicht aber die Ausführung von Schadcode.

Public PPE (3PE): Diese Variante zielt auf öffentliche Repositories, die automatisch Code aus Pull Requests von Forks ausführen, ohne dass eine Genehmigung erforderlich ist. Organisationen, die CI/CD-Pipelines auf nicht vertrauenswürdigem Code externer Beitragender ausführen, sind diesem Angriffsvektor am stärksten ausgesetzt.

Wenn Theorie Praxis wird

Was das im schlimmsten Fall bedeutet, zeigt eine Reihe prominenter Vorfälle. Nicht jeder davon ist eine PPE im engeren Sinn, aber jeder belegt, was die Kompromittierung einer Build- oder CI/CD-Umgebung anrichten kann.

Beim SolarWinds-Angriff (2020) kompromittierten staatlich unterstützte Angreifende die Build-Infrastruktur von SolarWinds und schleusten die SUNBURST-Malware in Software-Updates ein, die an etwa 18.000 Kundinnen und Kunden verteilt wurden. Die Angreifenden operierten sechs Monate lang unentdeckt. Betroffen waren unter anderem mehrere US-Regierungsbehörden.

Bei der PyTorch CI/CD-Kompromittierung (2023-2024) entdeckten Sicherheitsforschende, dass das PyTorch-Repository auf GitHub mit Self-Hosted Runners für eine neue Klasse von PPE-Angriffen anfällig war. Durch das Einreichen einer einfachen Tippfehlerkorrektur per Fork-Pull-Request wurden die Forschenden zu Beitragenden und konnten beliebigen Code auf PyTorchs Infrastruktur ausführen. Sie extrahierten AWS-Zugangsdaten und GitHub Personal Access Tokens mit Administratorzugang zu 93 Repositories.

2025 identifizierten Forschende kritische PPE-Schwachstellen in Repositories von Microsoft und anderen Fortune 500-Unternehmen. Fehlkonfigurierte Workflows, die den pull_request_target-Trigger verwendeten, erlaubten es Angreifenden, Code in privilegierten Kontexten auszuführen, Secrets zu exfiltrieren und Schadcode in vertrauenswürdige Branches zu pushen.

Auffällig ist dabei ein gemeinsamer Nenner: Verfolgen wir nahezu jeden Vorfall in der Supply Chain von Open Source-Projekten der vergangenen anderthalb Jahre zurück, so landen wir bei einer YAML-Datei in .github/workflows, wie Andrew Nesbitt in GitHub Actions is the weakest link nachzeichnet. Die Kompromittierung von tj-actions betraf 23.000 nachgelagerte Repositories, der Angriff auf nx legte über 5.000 private Repositories offen, und im Fall von Elementary Data genügten zehn Minuten zwischen einem bösartigen Kommentar und der Kompromittierung.

Reduktion als Verteidigung

Die übliche Reaktion auf solche Bedrohungen ist, mehr Schutzschichten hinzuzufügen. Aber es gibt eine wirksamere Frage als „Wie schützen wir diesen Branch?“. Diese Frage lautet: Brauchen wir diesen Branch überhaupt?

Ein Branch, der nicht existiert, kann keinen Pull Request mit einem PPE-Angriff empfangen. Eine CI/CD-Pipeline wird keinen vergifteten Workflow gegen einen Branch ausführen, den es nie gab. Das ist kein Minimalismus um seiner selbst willen, sondern die einfachste Form, die Angriffsfläche zu verkleinern.

Überprüfe die Branches in deinen Repositories. Frage dich: Dient dieser Branch heute einem Zweck, oder ist er ein Überbleibsel eines vergangenen Sprints, ein vergessenes Experiment, ein „das brauchen wir vielleicht noch“? Wenn er nicht aktiv benötigt wird, entferne ihn. Was nicht existiert, kann nicht angegriffen werden.

Schütze, was existieren muss. Entferne, was nicht existieren muss. Die sicherste Tür ist die, die nie eingebaut wurde.

Verteidigung in der Tiefe

Reduktion allein reicht nicht. Für alles, was existieren muss, brauchen wir Verteidigung in der Tiefe: mehrere unabhängige Sicherheitsebenen, die Resilienz bieten, wenn eine einzelne Schicht versagt.

Isolierte Ausführungsumgebungen

Ephemere Runner gehören zu den wirksamsten Gegenmaßnahmen gegen PPE. Diese kurzlebigen Instanzen werden für einzelne CI/CD-Jobs erstellt und sofort nach Abschluss zerstört. Die kurze Lebensdauer reduziert das Zeitfenster für eine Ausnutzung, verhindert persistenten Zugang und stellt sicher, dass jeder Job in einer sauberen Umgebung startet.

Code Review und Branch Protection

Menschliches Review bleibt eine der wirksamsten Verteidigungslinien gegen bösartige Code-Injection. Branch-Protection-Regeln sollten die Genehmigung durch benannte Reviewer vor der Ausführung von Code aus Pull Requests verlangen, direkte Pushes auf geschützte Branches mit CI/CD-Konfigurationen verbieten und Status Checks vor dem Merge erzwingen.

GitHubs pull_request_target-Trigger, der Fork-Pull-Requests erhöhte Berechtigungen einräumt, sollte nur mit expliziten Schutzmaßnahmen verwendet werden. Der sicherere pull_request-Trigger läuft mit eingeschränkten Berechtigungen im Kontext des geforkten Branches.

Secrets Management und Least Privilege

Moderne CI/CD-Systeme sollten zeitlich begrenzte Identitäten pro Lauf statt langlebiger Zugangsdaten verwenden. OIDC ermöglicht die dynamische Generierung von Zugangsdaten ohne deren Speicherung. Jede Pipeline sollte nur auf die Zugangsdaten zugreifen, die für ihren spezifischen Zweck erforderlich sind, mit den minimal notwendigen Rechten. Das begrenzt den Schaden, wenn eine Pipeline kompromittiert wird.

Überwachung und Erkennung

Sicherheitstransparenz muss sich bis in CI/CD-Pipelines erstrecken. Das umfasst die Überwachung von Prozessausführung, Netzwerkverbindungen und Dateioperationen innerhalb von Runnern. Ausgehende Netzwerkverbindungen sollten überwacht und eingeschränkt werden, um Datenexfiltration zu verhindern. Umfassende Audit-Logs aller Pipeline-Aktivitäten sind ebenso unverzichtbar wie die Erkennung von Anomalien: unerwarteter Zugriff auf Secrets, modifizierte Artefakte oder ungewöhnliche Ausführungszeiten.

Geteilte Verantwortung

Sicherheit ist nicht allein Aufgabe derer, die ein Werkzeug entwickeln. Wer es einsetzt, trägt einen Teil der Verantwortung für Sicherheit selbst. Für die CI/CD-Pipeline zieht die Security Policy von PHPUnit diese Linie ausdrücklich:

Protecting the pipeline is the responsibility of the operator of the pipeline: limit which events trigger workflows, require review before workflows run on contributions from outside collaborators, isolate jobs that run untrusted code, and do not expose secrets to such jobs.

Tool-Maintainer tragen die Verantwortung für eine sichere Implementierung innerhalb ihres Wirkungsbereichs. Sie sollten Schwachstellen beheben, klare Dokumentation über betriebliche Grenzen bereitstellen und Schutzmaßnahmen implementieren, wo es machbar ist.

Aber Tool-Maintainer können nicht absichern, was sie nicht kontrollieren. Sie können keine Richtlinien für Code Reviews in deiner Organisation durchsetzen, deine CI/CD-Runner nicht isolieren und keine Least-Privilege-Zugriffskontrollen für deine Pipelines einrichten. Und sie können dich nicht daran hindern, Entwicklungswerkzeuge in Produktionsumgebungen bereitzustellen.

Wer diese Grenzen anerkennt, kann Verteidigung in der Tiefe über sie hinweg aufbauen. Verwende ephemere Runner, um die Exposition zu begrenzen. Verlange Code Reviews, bevor nicht vertrauenswürdiger Code ausgeführt wird. Implementiere Secrets Management mit zeitlich begrenzten Zugangsdaten. Überwache Pipeline-Aktivitäten auf Anomalien. Stelle niemals Entwicklungswerkzeuge in der Produktionsumgebung bereit.

Und vor allem: Entferne Branches, die nicht mehr gebraucht werden. Denn die sicherste Tür ist die, die nie eingebaut wurde.