2026

12. Mai 2026

Die pull_request_target Falle

Wie ein bekannt gefährliches GitHub Actions Pattern immer wieder npm Pakete kompromittiert. Der TanStack Vorfall, die vorigen Fälle und die Checkliste, die es wirklich verhindert.

S
Sascha Becker
Author

23 Min. Lesezeit

Die pull_request_target Falle

Die pull_request_target Falle

Am 11. Mai 2026, zwischen 19:20 und 19:26 UTC, hat ein Angreifer 84 bösartige Versionen über 42 @tanstack/* Pakete auf npm veröffentlicht.1 Das TanStack Team hat es innerhalb einer guten Stunde bemerkt, die Tarballs serverseitig ziehen lassen, jede betroffene Version deprecated und am selben Tag eine detaillierte Postmortem veröffentlicht. Sie haben nach dem Einbruch fast alles richtig gemacht. Der Einbruch selbst kam, wie schon mehrere zuvor, durch dieselbe Tür: ein GitHub Actions Workflow, der von pull_request_target ausgelöst wurde und Code aus einem Fork ausgecheckt und ausgeführt hat.

Das ist kein neues Problem. GitHubs eigenes Security Lab warnt seit mindestens 2020 davor, unter dem Namen "Pwn Requests".2 Eine Arbeitsliste prominenter OSS Projekte, die in den letzten vierzehn Monaten über genau dieses Pattern kompromittiert wurden, umfasst sechs direkte Opfer (Nx, PostHog, Trivy, LiteLLM downstream, die prt-scan Welle und jetzt TanStack) plus die tj-actions/changed-files Action, deren Kompromittierung das Tradecraft für fast alles geliefert hat, was danach folgte. Das Pattern erwischt Maintainer immer wieder, weil der gefährliche Workflow im Review harmlos aussieht, die Doku auf ein halbes Dutzend GitHub Seiten verteilt ist, und der Failure Mode asymmetrisch ist: jeder Workflow, der ihn korrekt nutzt, sieht identisch aus zu jedem Workflow, der ein Cache Poisoning davon entfernt ist, seine Publish Tokens herzugeben.

Dieser Artikel ist die längere Aufarbeitung, die ich mir an einem Ort gewünscht hätte. Was der Trigger wirklich tut, wie der TanStack Angriff drei Primitive verkettet hat, um zu publishen, ohne je ein npm Token zu stehlen, die früheren Fälle, die jeden Schritt vorhersagten, und eine Checkliste, die den Failure Mode verhindert, statt ihn in weniger YAML Zeilen nachzubauen.

Was pull_request_target wirklich tut

Zwei GitHub Actions Trigger feuern bei Pull Request Aktivität. Sie sehen in YAML fast identisch aus. Sie sind es nicht.

pull_request läuft im Kontext des PR Heads. Es checkt standardmäßig den Code des Beitragenden aus. Wichtig: kommt der PR von einem Fork, läuft der Workflow mit einem read-only GITHUB_TOKEN und ohne Zugriff auf Repository Secrets.2 Ein Angreifer, der einen bösartigen PR an dein Repo öffnet, kann beliebigen Code auf deinen CI Runnern ausführen, aber er kann nicht in dein Repository schreiben oder deine Secrets lesen. Der Blast Radius ist der Runner selbst.

pull_request_target läuft im Kontext des Base Repositories. Es wurde aus einem realen Grund eingeführt: Workflows wie "labelt einen PR basierend auf Dateipfaden", "postet einen Kommentar basierend auf Metadaten" oder "lass Benchmarks auf dem Base Branch mit Fork Inhalten laufen" brauchen Schreibzugriff auf das Base Repo und Zugriff auf Secrets. GitHubs Doku stellt die Warnung an den Anfang der Seite, aber der Trigger ist zu nützlich, um ihn zu entfernen. Wie das Security Lab es formuliert:

Das gefährliche Pattern entsteht, wenn ein pull_request_target Workflow das Eine tut, was das Modell bricht: explizit den Head Code des PR auscheckt und ausführt. Ab diesem Moment läuft fork-kontrolliertes JavaScript auf einem Runner, der das GITHUB_TOKEN des Base Repos im Speicher hält, Zugriff auf jedes auf dem Job deklarierte Secret hat und OIDC Tokens minten kann, wenn der Job id-token: write gesetzt hat. Die Anmerkung des Security Labs:

Dieser eine Satz ist die gesamte Warnung. Es ist auch der Bug, der TanStack, Nx, PostHog, Trivy (und LiteLLM downstream davon), tj-actions/changed-files und die mehr als 26 Ziele der prt-scan Kampagne in den letzten vierzehn Monaten kompromittiert hat.3

Der TanStack Angriff, anatomiert

Die TanStack Postmortem ist ungewöhnlich detailliert und es lohnt sich, sie ganz zu lesen. Die Kurzfassung: drei verkettete Schwachstellen, jede notwendig, keine alleine ausreichend.

Schritt 1: Cache Poisoning aus einem Fork PR

Der verwundbare Workflow war bundle-size.yml. Aus der Postmortem:

Die Maintainer hatten einen Trust Split zwischen den Jobs versucht (eine Teilverteidigung), aber sie haben zwei Tatsachen über GitHub Actions Caches übersehen.

Die erste: Cache Writes werden nicht durch die deklarierten Permissions des Workflows kontrolliert. permissions: contents: read auf einem Job verhindert Writes über das GITHUB_TOKEN des Workflows, aber actions/cache benutzt einen separaten runner-internen Token für den Post Job Save Schritt. Ein Job mit Read-only Permissions kann immer noch in den Cache schreiben. Die Postmortem sagt es trocken:

Die zweite: der Cache Scope kreuzt die Trust Boundary zwischen Fork und Base. Ein Cache Eintrag, der von einem pull_request_target Workflow geschrieben wurde (im Base Scope), ist für nachfolgende Workflows auf dem Base Branch sichtbar, einschließlich release.yml.

Der Angreifer hat einen Fork erstellt, ~30.000 Zeilen gebündeltes JavaScript in packages/history/vite_setup.mjs platziert, einen PR (#7378) eröffnet und den Workflow laufen lassen. Der Benchmark Job hat pnpm install und pnpm nx run @benchmarks/bundle-size:build ausgeführt, wodurch das bösartige vite_setup.mjs aufgerufen wurde. Dieses Skript hat einen vergifteten pnpm Store unter einem deterministischen Schlüssel in den Cache geschrieben (Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11). Anschließend hat der Angreifer den PR mit Force Push zurück auf den Main HEAD gesetzt und ihn so zu einem No-op gemacht, damit der Diff sauber aussieht. Der Cache blieb vergiftet.

Schritt 2: Restore beim Push auf Main

Als TanStack Maintainer unverwandte Arbeit auf main gemerged haben, ist release.yml gefeuert und hat den pnpm Cache mit demselben Schlüssel wiederhergestellt. Jetzt liefen vom Angreifer kontrollierte Binaries während eines Workflows, der Publish Permissions hatte, und der Restore passierte, bevor der Workflow selbst die Integrität der Dependencies prüfen konnte.

Schritt 3: OIDC Token Extraktion aus dem Runner Speicher

Der Release Workflow hatte id-token: write, weil TanStack die Trusted Publisher Bindung von npm via OIDC nutzt. OIDC sollte die sicherere Option im Vergleich zu langlebigen NODE_AUTH_TOKEN Secrets sein: Tokens werden zur Publish Zeit gemintet, auf den Workflow gescoped und nie gespeichert. Das stimmt. Es ist auch irrelevant, wenn bösartiger Code auf demselben Runner läuft.

Die vergifteten Binaries haben den GitHub Actions Runner.Worker Prozess durch das Scannen von /proc/*/cmdline lokalisiert und dann seinen Speicher über /proc/<pid>/maps und /proc/<pid>/mem gedumpt. Sie haben den im Speicher liegenden OIDC Token extrahiert (den der Runner lazy mintet, wenn ein Step ihn braucht) und direkte POST Requests an registry.npmjs.org gesendet, womit sie den Publish Packages Step komplett umgangen haben. Die Postmortem stellt fest, dass dieses Vorgehen nicht neu ist:

Es wurden keine npm Tokens gestohlen. Die Trusted Publisher Bindung hat genau wie geplant funktioniert. Es hat sich nur herausgestellt, dass "korrekt entworfen" und "sicher in einem Workflow zu nutzen, der untrusted Code anfasst" nicht dieselbe Eigenschaft sind.

Das ist jetzt ein Pattern

Der TanStack Vorfall ist der jüngste in einer Reihe öffentlicher Kompromittierungen großer OSS Pakete, die alle mit einem missbrauchten pull_request_target Workflow begonnen haben. Chronologisch aufgelistet, damit der Trend unmissverständlich wird.

tj-actions/changed-files, März 2025

Dieser Vorfall liegt vor fast allen späteren Angriffen. Die Kompromittierung von tj-actions/changed-files, einer beliebten GitHub Action, die in Tausenden von Repos eingesetzt wird, hat das öffentliche Playbook für die Extraktion von OIDC Tokens aus dem Runner Speicher etabliert. Der TanStack Angreifer hat dieselbe /proc/<pid>/mem Technik gegen denselben Runner.Worker Prozess wiederverwendet. Die veröffentlichten Writeups zu jenem Vorfall sind faktisch ein kostenloses Trainingsmaterial für das nächste Jahr an Kompromittierungen geworden.

Nx, August 2025

Angreifer haben einen verwundbaren pull_request_target Workflow im nx Repository ausgenutzt, um erweiterte Privilegien und ein GITHUB_TOKEN zu bekommen, und dann trojanisierte Versionen des nx Pakets auf npm gepusht.4 Die Payload war ein Credential Stealer namens QUIETVAULT, der Umgebungsvariablen, GitHub Personal Access Tokens und SSH Keys abgegriffen hat; damals neuartig: er hat ein lokal installiertes LLM Tool weaponisiert, um das Dateisystem des Entwicklers nach weiteren Secrets zu scannen. Die gestohlenen Credentials haben es den Angreifern erlaubt, von einem Developer Token in unter 72 Stunden auf volle AWS Administrator Rechte zu eskalieren.

PostHog, November 2025

Die PostHog Kompromittierung ist das klarste Beispiel dafür, dass der verwundbare Workflow nicht deine Build Pipeline sein muss. Er muss nur irgendein Workflow sein.7 Die verwundbare Datei war auto-assign-reviewers.yaml, ein Workflow, dessen ganze Aufgabe es war, Reviewer zu eingehenden PRs zuzuweisen. Sie wurde am 11. September 2025 auf pull_request_target umgestellt und blieb so für 74 Tage. Am 18. November hat ein bösartiger PR das assign-reviewers.js Skript modifiziert, das der Workflow ausführte; das Ausführen dieses Skripts mit den Permissions des Base Repos hat den GitHub PAT des posthog-bot Accounts geliefert, der breite Schreibrechte auf das Repo hatte.

Fünf Tage später, am 23. November, hat der Angreifer den Bot PAT genutzt, um einen Detached Commit zu pushen, der den Lint PR Workflow modifiziert hat, um jedes in CI deklarierte Secret zu exfiltrieren. Um 04:11 UTC am 24. November wurden acht npm Pakete mit Shai-Hulud 2.0 vergiftet: posthog-node, posthog-js, posthog-react-native, posthog-docusaurus, posthog-react-native-session-replay, @posthog/agent, @posthog/ai und @posthog/cli. PostHog hat sie innerhalb von sechs Stunden gefangen und gezogen.

Die Lehre hier ist schärfer als bei den anderen: PostHog hatte keine naive Build-and-Publish Pipeline, die das gefährliche Ding tat. Ein Reviewer Assignment Workflow hat gereicht.

Trivy, Februar bis März 2026

Aqua Securitys Trivy Scanner wurde im Februar 2026 über einen fehlkonfigurierten pull_request_target Workflow kompromittiert, der es einem Akteur (unter dem Handle MegaGame10418 in manchen Writeups, hackerbot-claw in anderen) erlaubt hat, den Personal Access Token des aqua-bot zu exfiltrieren. Der Bot Account hatte öffentliche Repos in großem Maßstab nach demselben Fehlkonfigurations Pattern gescannt. Mit dem gestohlenen PAT hat der Angreifer im März 2026 Trivys GitHub Action Tags vergiftet.

LiteLLM, März 2026 (der Downstream Fall)

Der LiteLLM Vorfall ist deshalb relevant, weil LiteLLM keinen fehlkonfigurierten Workflow hatte. Sie waren nicht das unmittelbare Opfer eines Pwn Request. Sie waren das Downstream Opfer des Pwn Request eines anderen: sie haben die (jetzt vergiftete) Trivy Action in ihrer CI/CD Pipeline laufen lassen, und die Malware in dieser Action hat still LiteLLMs PYPI_PUBLISH Token aus der Runner Umgebung extrahiert.8 Mit diesem Token haben die Angreifer am 19. März litellm 1.82.7 um 10:39 UTC und 1.82.8 um 10:52 UTC veröffentlicht, jeweils mit einer dreistufigen Payload: Credential Harvesting, Kubernetes Lateral Movement und eine persistente Backdoor.

Das ist der Failure Mode, bei dem es sich zu verweilen lohnt. Selbst wenn du jeden Workflow in deinem eigenen Repo auditierst, können die Actions, von denen du abhängst, auf dieselbe Weise kompromittiert werden, und du erbst diese Kompromittierung beim nächsten CI Lauf. Die Kompromittierung von tj-actions/changed-files und die Kompromittierung der Trivy Tags funktionieren über denselben Downstream-Opfer Mechanismus. Pinne Actions auf SHA, nicht auf Tag, und das nächste Mal, wenn ein Upstream auffliegt, fliegt dein CI nicht mit.

prt-scan Kampagne, März bis April 2026

Wiz hat eine Kampagne dokumentiert, in der ein Angreifer unter dem Handle ezmtebo über 475 bösartige Pull Requests in 26 Stunden geöffnet hat, dann insgesamt über 500 in sechs Wellen, und dabei Repositories mit verwundbaren pull_request_target Workflows ins Visier genommen hat.3 Zwei npm Pakete wurden erfolgreich kompromittiert. Bemerkenswert: die Payloads des Angreifers haben sich von rohen Bash Skripten in den frühen Wellen zu KI generierten, sprachsensitiven Skripten in den späteren entwickelt. Die Kosten, in großem Maßstab zu scannen und zu exploiten, sind heute geringer als die Kosten eines Maintainer Audits eines einzelnen Workflows.

Shai-Hulud und das Wellen Pattern

Die TanStack Kompromittierung wird auch als vierte Welle der selbstausbreitenden Shai-Hulud npm Wurm Familie verfolgt.5 Frühere Wellen: ursprünglicher Shai-Hulud (September 2025, mehr als 500 Pakete, erster selbstausbreitender npm Wurm), Shai-Hulud 2.0 (November 2025, 492 Pakete mit 132M monatlichen Downloads, über preinstall Hooks), Mini Shai-Hulud (April 2026, gezielt gegen SAP und Intercom Ökosysteme mit Persistenz über Editor Configs). Die Welle vom Mai 2026 war die erste, die gültige SLSA Provenance Attestations für bösartige Pakete produziert hat, und das verdient eine kurze Pause.

Jede Verteidigungsschicht, die in den letzten zwei Jahren zu npm hinzugefügt wurde (2FA, Fine-Grained Tokens, OIDC Trusted Publishing, SLSA Provenance), ist gut. Keine davon verhindert, dass der Workflow selbst zur Angriffsfläche wird. Wenn dein CI bösartigen Code mit Publish Fähigkeit ausführt, ist das veröffentlichte Paket nach jeder kryptographischen Messung legitim.

Warum "setz einfach permissions: read" dich nicht rettet

Der häufigste Rat in Tutorials ist, permissions: contents: read zu pull_request_target Workflows hinzuzufügen. Dieser Rat ist korrekt und unzureichend. Der TanStack Workflow hatte eingeschränkte Permissions. Die Maintainer hatten die Warnungen gelesen. Die Kompromittierung passierte trotzdem, weil zwei Dinge das Permission Modell vollständig umgehen:

  1. Caches. Wie oben gezeigt, nutzen Cache Writes einen runner-internen Token. Ein Read-only Job kann den Cache vergiften, und der Cache wird über Trust Boundaries hinweg geteilt. Jeder Workflow auf main, der aus einem vergifteten Cache wiederherstellt, führt vom Angreifer kontrollierten Code mit den Permissions aus, die jener Workflow hat.

  2. In-Memory Tokens. Permissions kontrollieren, was der Workflow durch das GITHUB_TOKEN* tun kann. Sie kontrollieren nicht, was bösartiger Code, der auf demselben Runner läuft, aus dem Prozessspeicher lesen kann. Sobald id-token: write` gesetzt ist, mintet der Runner einen OIDC Token im Speicher, wenn er gebraucht wird; Permissions des aufrufenden Jobs hindern einen ko-residenten Angreifer nicht daran, ihn abzugreifen.

Die engere Lehre ist, dass "Read-only Permissions" nur den sichtbarsten Angriffspfad einschränken. Die breitere Lehre ist, dass die Trust Boundary der Runner Prozess ist, nicht die Permission Deklaration des Workflows. Alles, was sich diesen Runner mit vom Angreifer kontrolliertem Code teilt, liegt im Scope.

Eine Checkliste, die das wirklich verhindert

Die meisten "Supply Chain Security Checklisten", die kursieren, sind eine Mischung aus "schalt 2FA ein" (ja, mach das), "nutz SBOMs" (super, aber liegt unterhalb des Bugs) und "scan deine Dependencies" (notwendig, ebenfalls unterhalb). Die untenstehende Liste ist enger: sie zielt auf den konkreten Failure Mode, der TanStack, Nx, Trivy, tj-actions und die prt-scan Opfer kompromittiert hat. Geh sie durch jede CI Pipeline durch, die irgendetwas publishet.

1. Behandle pull_request_target als Code Smell

Die Standardantwort ist pull_request. Die einzigen legitimen Anwendungen von pull_request_target sind die engen: Labelling, Kommentieren, Checks gegen den Base Branch laufen lassen, die explizit keinen PR Code anfassen. Wenn ein Workflow pull_request_target nutzt und irgendetwas über diese Operationen hinaus tut, liegt die Beweislast beim Workflow Autor, das in einem Kommentar zu rechtfertigen, den jemand anderes reviewen kann.

2. Kombiniere pull_request_target nie mit einem Checkout des PR Heads

Das ist die einzelne Regel, die die Pwn Request Klasse vollständig verhindert. Wenn du Code aus dem PR ausführen musst (zum Beispiel, um seine Bundle Size zu berechnen), tu das in einem pull_request Workflow ohne Secrets und ohne Schreibrechte und reiche das Ergebnis dann an einen separaten privilegierten Workflow weiter.

3. Nutz das Zwei-Workflow Pattern: pull_request + workflow_run

Das Pattern, das das Security Lab empfiehlt, und auf das TanStack im Hardening PR umgestiegen ist:

  • Ein pull_request Workflow lässt den untrusted Code in einem sandboxed Kontext laufen, ohne Secrets, ohne Schreibrechte, ohne OIDC und ohne geteilten Cache Scope. Er schreibt seinen Output (Size Diff, Test Resultate, was auch immer) in ein Artifact.
  • Ein workflow_run Workflow triggert beim Abschluss des ersten, läuft mit erweiterten Permissions, liest das Artifact und postet den Kommentar oder updated den Check.

Die Trust Boundary ist jetzt die Artifact Übergabe, die im YAML auditierbar ist statt implizit im Speicherlayout des Runners zu stecken.

4. Vertrau GitHub Actions Caches nicht über Trust Boundaries hinweg

Wenn ein Job untrusted Code ausführt und in den Cache schreibt, ist der Cache für jeden späteren Job vergiftet, der daraus wiederherstellt. Drei Mitigationen, in der Reihenfolge, in der du sie greifen solltest:

  • Deaktiviere Cache Writes von jedem pull_request_target oder pull_request-from-fork Workflow. actions/cache unterstützt Save-only und Restore-only Modi; nutz Restore-only in untrusted Kontexten.
  • Scope Cache Keys nach Trust Level. Präfixiere Cache Keys für Fork PR Builds mit etwas vom Angreifer Abgeleitetem (dem PR Author, dem Fork), damit sie nicht mit den Keys kollidieren können, aus denen dein Release Workflow wiederherstellt.
  • Teil keine Build Caches zwischen PR Validierung und Release Pipelines. Der Convenience Gewinn ist klein. Der Blast Radius ist total.

5. Pinne jede Drittanbieter Action auf einen Commit SHA

Floating Tags (@v1, @v6.0.2) lösen sich zur Workflow Laufzeit auf. Wenn die Action kompromittiert wird (wie tj-actions/changed-files es war), führt jeder Workflow, der den Floating Tag nutzt, beim nächsten Lauf den neuen Code aus. SHA Pins (@a1b2c3...) frieren die Action auf einen spezifischen, gereviewten Commit ein. Ja, das macht Upgrades schwerer. Genau das ist der Sinn.

6. Behandle OIDC Trusted Publishing als "besser, nicht sicher"

OIDC entfernt langlebige Secrets aus deinem Repository. Es entfernt nicht den Runner aus der Angriffsfläche. Zwei Konsequenzen:

  • Der Workflow mit id-token: write muss der kleinstmögliche Workflow sein. Build Artefakte woanders, übergib sie dem Publish Workflow als Inputs, und lass den Publish Workflow nichts anderes tun, als zu verifizieren und zu publishen. Keine Drittanbieter Actions, keine Postinstall Skripte, keine untrusted Code Pfade.
  • Füge Publish-time Approvals dort ein, wo sie passen. GitHub Environments unterstützen Required Reviewers; für hochhebelige Publish Jobs verwandelt das eine Workflow Kompromittierung von "sofortigem Publish" zu "Publish nur nach menschlichem Klick auf Approve, mit sichtbarem Diff". Das hätte den TanStack Angriff allein nicht verhindert (der Angreifer kontrollierte den Build, nicht das YAML), aber es hebt die Hürde für die nächste Variante.

7. Schränke ein, was zur Install Zeit läuft

Die Shai-Hulud Wurmfamilie verbreitet sich über preinstall und postinstall Skripte. Die 84 bösartigen TanStack Versionen enthielten einen bösartigen optionalDependencies Eintrag. Zwei Verteidigungen, beide günstig:

  • --ignore-scripts als Standard in CI und auf Entwicklermaschinen, mit expliziten Overrides für Pakete, von denen du weißt, dass sie Skripte brauchen.
  • Minimum Package Age Policies. pnpm unterstützt eine minimumReleaseAge Einstellung; npm hat Drittanbieter Äquivalente. "Installiere keine Versionen, die jünger als 24 Stunden sind" zu setzen, hätte dem npm Security Team Zeit gegeben, die TanStack Tarballs zu ziehen, bevor die meisten Konsumenten sie geladen hätten.

8. Monitore deine eigenen Publishes

Die schmerzhafteste Zeile aus der TanStack Postmortem:

Für einen Maintainer ist die einfachste Version ein Workflow, der die npm Registry auf neue Publishes von Paketen in deinem Scope beobachtet und bei jedem in Slack/Discord/Mail postet. Wenn du dreimal im Monat publishst und ein viertes Event siehst, das du nicht ausgelöst hast, hast du Minuten, nicht Stunden, um zu reagieren. Das kompromittierte Release Fenster für TanStack waren sechs Minuten. Behavioral Analysis bei npm hat alle 84 Versionen innerhalb dieses Fensters geflaggt, aber das Team hat es nicht direkt von npm erfahren.

9. Aktiviere FIDO2 für Maintainer, leg TOTP zu den Akten

GitHub stellt TOTP basierte 2FA zugunsten von WebAuthn / FIDO2 ab. Hardware-gestützte Keys verhindern die Phishing Routen, die die workflow-basierten Angriffe ergänzen. Das stoppt keinen Pwn Request, aber es stoppt den menschlichen Angriffsvektor, der sich oft mit dem technischen kombiniert.6

10. Auditete heute jeden bestehenden pull_request_target Workflow

Die einzelne Aktion mit dem höchsten Wert nach dem Lesen dieses Artikels: grep dein .github/workflows/ Verzeichnis nach pull_request_target und auditete jeden Treffer gegen diese Liste. Die meisten Workflows, die es nutzen, brauchen es nicht. Die meisten, die es brauchen, müssen den PR Head nicht auschecken. Die meisten, die den PR Head auschecken, müssen den Code des Beitragenden nicht ausführen. Jede Schicht, die du entfernst, schneidet die Angriffsfläche um eine Größenordnung.

bash
# Eine Startabfrage für jede Org
gh search code "pull_request_target" --owner YOUR_ORG --extension yml

Was GitHub geändert hat, und was noch fehlt

GitHub hat zwei pull_request_target Härtungsänderungen ausgespielt, die zum 8. Dezember 2025 in Kraft getreten sind:

  • Die Workflow Source löst sich jetzt immer auf den Default Branch auf. Vorher konnte der Base Branch eines PR die Workflow Definition liefern, was Edge Cases erzeugte, in denen Angreifer den Base Branch dazu bringen konnten, eine ältere, verwundbarere Version des Workflows laufen zu lassen.
  • GITHUB_REF löst sich auf den Default Branch und GITHUB_SHA auf seinen letzten Commit auf. Das schließt eine Klasse von Bugs, in denen untrusted Branch Namen die Auswertung in Skripten beeinflussen konnten, die diese Variablen referenzierten.
  • Environment Branch Protection Regeln für pull_request_target werden gegen den Default Branch ausgewertet, nicht den PR Head. Das verhindert, dass PRs an Environment-scoped Reviewer Anforderungen vorbeischlüpfen, indem sie so aussehen, als kämen sie von einem geschützten Branch.

Das sind gute Änderungen. Sie adressieren reale frühere Vorfälle. Sie adressieren nicht den zentralen Failure Mode, der darin besteht, dass der Trigger noch existiert, die Doku die Warnungen noch versteckt und ein Workflow Autor immer noch vier Zeilen YAML schreiben kann, die fork-kontrolliertem Code einen publish-fähigen Runner in die Hand drücken. Das Design des Triggers ist der Bug; alles andere ist Mitigation.

Eine kurze Liste an Änderungen, die das Risiko sinnvoll reduzieren würden:

  1. Ein neuer Trigger, pull_request_external, der explizit keinen PR Head Code auschecken kann. Derselbe Metadata Zugriff wie pull_request_target, keine der gefährlichen Primitive.
  2. Eine Repo-Level Einstellung, um pull_request_target komplett zu deaktivieren, standardmäßig aus für neue Repos.
  3. Per-Workflow Cache Scopes, die die Trust Boundary respektieren, sodass fork-getriggerte Builds keine Caches vergiften können, die von Release Pipelines wiederhergestellt werden.
  4. Verpflichtende Verzögerung zwischen OIDC Mint und Use, mit einem kurzen Approval Fenster. Fünf Sekunden sind nichts für einen legitimen Publish und eine Ewigkeit für einen Speicher scrapenden Wurm.

Keines davon ist technisch schwer. Der Grund, warum sie nicht ausgespielt wurden, ist derselbe Grund, warum pull_request_target immer noch der Trigger der Wahl für Benchmark Workflows ist: jeder vernünftige Use Case sieht in Isolation in Ordnung aus. Die Kompromittierung passiert nur, wenn der Use Case auf die Laufzeit trifft.

Wenn du fünf Dinge mitnimmst

  1. pull_request_target plus Checkout des PR Heads ist die ganze Bug Klasse. Jede große npm und PyPI Ökosystem Kompromittierung der letzten vierzehn Monate verkettet sich darüber, entweder direkt (Nx, PostHog, Trivy, TanStack) oder downstream eines Opfers, das es war (LiteLLM via Trivy). Es ist keine fortgeschrittene Exploitation Technik nötig.
  2. Permission Flags sind partielle Mitigation, keine Isolation. permissions: contents: read stoppt kein Cache Poisoning. Es stoppt kein In-Memory OIDC Token Scraping. Behandle den Runner Prozess, nicht das GITHUB_TOKEN, als Trust Boundary.
  3. Zwei Workflows schlagen einen cleveren Workflow. Lass untrusted Code in einem sandboxed pull_request Workflow laufen, der Artifacts schreibt. Lass die privilegierte Arbeit in einem separaten workflow_run Workflow laufen, der sie konsumiert. Die Trust Boundary wird auditierbar.
  4. OIDC und SLSA sind notwendig, nicht ausreichend. Sie verifizieren den Build, nicht die Legitimität des gebauten Codes. Ein kompromittierter Workflow publishet Pakete mit gültiger Provenance, und jeder Downstream Verifikations Check besteht.
  5. Auditete deine pull_request_target Workflows heute. Das ist die Aktion mit dem höchsten erwarteten Wert von allem auf der Liste. Ein grep und ein sorgfältiges Lesen pro Treffer. Die meisten Treffer in den meisten Repos brauchen den Trigger nicht.

Das Pattern wird sich weiter wiederholen, bis der Trigger weg ist oder bis sich der Standard für Fork PR Workflows ändert. Bis dahin sind die Maintainer, die der nächsten Kompromittierung entgehen, diejenigen, die schon auditet haben.


S
Geschrieben von
Sascha Becker
Weitere Artikel