Back openDesk Edu for a sovereign, open-source education — every vote counts.
Vote nowSave products you love by clicking the heart icon.
Essenzielles Referenzwerk für GitHub Actions und GitLab CI Pipelines, einschließlich Workflow-Syntax, Runnern, Secrets, Artifacts und Deployment-Patterns.
Am 11.05.2026 zwischen 19:20 und 19:26 UTC veröffentlichte ein Angreifer 84 bösartige Versionen über 42 @tanstack/* npm-Pakete hinweg. Der Angriff erforderte drei verschiedene Schwachstellen, von denen keine allein ausreichend gewesen wäre. TanStack hat eine ausführliche Postmortem-Analyse veröffentlicht; dieser Artikel extrahiert die technischen Details, die jeder DevOps-Engineer heute in seinen eigenen Pipelines prüfen sollte.
Der Einstiegspunkt des Angreifers war ein Fork-PR gegen TanStack/router. Der bösartige PR (#7378), der vom Account zblgg aus eröffnet wurde, nutzte den pull_request_target Trigger in bundle-size.yml aus:
on:
pull_request_target:
paths: ["packages/**", "benchmarks/**"]
jobs:
benchmark-pr:
steps:
- uses: actions/checkout@v6.0.2
with:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- uses: TanStack/config/.github/setup@main
# ↑ Transitively calls actions/cache@v5
- run: pnpm nx run @benchmarks/bundle-size:build
# ↑ Executes attacker code in the build step
pull_request_target wird mit Schreibberechtigungen auf dem Basis-Repository ausgeführt und checkt den gemergten Code des PR aus. Der Workflow-Autor beabsichtigte eine Trennung des Vertrauens zwischen „nicht vertrauenswürdigem“ PR-Code und schreibgeschützten Berechtigungen, aber das Post-Job-Save von actions/cache@v5 wird nicht durch permissions: contents: read beschränkt. Cache-Schreibvorgänge verwenden ein Runner-internes Token. Der Build des PR vergiftete den pnpm-Store unter einem Key, den der Produktions-Workflow release.yml später berechnen und wiederherstellen würde.
Als release.yml bei einem Push auf main ausgeführt wurde, stellte es den vergifteten Cache wieder her – vom Angreifer kontrollierte Binärdateien befanden sich nun auf der Festplatte innerhalb des Kontextes des Release-Workflows.
release.yml deklarierte id-token: write rechtmäßig für das npm OIDC Trusted Publishing. Als der vergiftete pnpm-Store wiederhergestellt wurde, wurden diese Binärdateien während des Build-Schritts aufgerufen. Diese führten dann folgende Schritte aus:
/proc/*/cmdline/proc/<pid>/maps und /proc/<pid>/mem, um den Speicher des Workers zu dumpenid-token: write gesetzt ist)registry.npmjs.org – wodurch der „Publish Packages“-Schritt des Workflows vollständig umgangen wurdeDies ist dieselbe Memory-Extraction-Technik und dasselbe wortwörtliche Python-Skript, das bei der tj-actions/changed-files Kompromittierung im März 2025 verwendet wurde. Kein neuartiges Vorgehen – sondern eine Rekombination bereits veröffentlichter Forschung.
Das prepare Lifecycle-Skript der Malware listete zudem andere Pakete auf, die das Opfer verwaltet (registry.npmjs.org/-/v1/search?text=maintainer:<user>), und veröffentlichte diese mit derselben Injektion erneut. Dies ist ein Wurm – nicht nur Diebstahl von Zugangsdaten, sondern eine laterale Ausbreitung über das gesamte Paket-Portfolio des Opfers.
| Metrik | Wert |
|---|---|
| Betroffene Pakete | 42 @tanstack/* Pakete |
| Veröffentlichte Versionen | 84 (zwei pro Paket, im Abstand von 6 Minuten) |
| Erkennung | Externer Forscher (ashishkurmi, StepSecurity), ~20 Min. nach Veröffentlichung |
| Keine npm-Tokens gestohlen | Angriff nutzte OIDC-Token, das innerhalb des Workflows erstellt wurde |
| Selbstausbreitung | Malware listete und veröffentlichte andere Pakete des Opfers erneut |
Die Payload installierte einen Dead-Man's-Switch: ~/.local/bin/gh-token-monitor.sh als systemd-User-Service (Linux) oder LaunchAgent (macOS). Dieser pollt alle 60 Sekunden api.github.com/user. Wenn das gestohlene Token widerrufen wurde, während die infizierte Maschine noch online war, führte das Skript rm -rf ~/ aus.
| Zeit (UTC) | Ereignis |
|---|---|
| 2026-05-10 17:16 | Angreifer forkt TanStack/router als zblgg/configuration |
| 2026-05-10 23:29 | Bösartiger Commit 65bf499d zum Fork hinzugefügt (30.000-Zeilen Payload) |
| 2026-05-11 10:49 | PR #7378 eröffnet; pull_request_target Workflows triggern automatisch |
| 2026-05-11 11:11 | Force-Push liefert Payload-Commit via PR-Head |
| 2026-05-11 11:29 | Vergifteter Cache-Eintrag gespeichert: Linux-pnpm-store-6f9233a50... |
| 2026-05-11 11:31 | PR per Force-Push zurück auf main HEAD, dann geschlossen – Cache-Gift bleibt |
| 2026-05-11 19:15 | Legitimer PR #7369 wird gemergt release.yml triggert |
| 2026-05-11 19:20:39 | Erste Charge von 42 Paketen via gestohlenem OIDC-Token veröffentlicht |
| 2026-05-11 19:20:47 | Release-Workflow-Run schlägt fehl (bösartige Payload hat Tests beschädigt) |
| 2026-05-11 ~19:50 | Externer Forscher eröffnet Issue mit vollständiger IOC-Analyse |
| 2026-05-11 21:00 | Alle 84 betroffenen Versionen deprecated; Caches geleert |
| 2026-05-11 21:30 | Hardening-PR gemergt; GitHub Security Advisory veröffentlicht |
Für Security-Teams, die Downstream-Scans durchführen:
"optionalDependencies": {
"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}
router_init.js (~2,3 MB, Paket-Root, nicht in „files“)Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11filev2.getsession.org, seed{1,2,3}.getsession.orgclaude <claude@users.noreply.github.com> (fabriziert, nicht Anthropic)zblgg (GitHub ID 127806521), voicproducoesDie Analyse von TanStack ist bemerkenswert für ihre Offenheit:
pull_request_target Workflows wurden nie geprüft, obwohl sie ein seit langem bekanntes gefährliches Muster darstellen.@v6.0.2, @main) erzeugen ein dauerhaftes Supply-Chain-Risiko, unabhängig von diesem Vorfall.Dem Angreifer war zudem Glück beschieden: Seine Payload beschädigte die Tests, was dazu führte, dass der Publish-Schritt (der sauberer aussehende Tarballs produziert hätte) übersprungen wurde. Ein vorsichtigerer Angreifer hätte stundenlang unbemerkt veröffentlichen können.
Wenn Sie pull_request_target Workflows betreiben, prüfen Sie diese auf folgende spezifische Bedingungen:
# NEVER do this — checkout of fork PR head inside a workflow with write permissions
- uses: actions/checkout@v6.0.2
with:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
# Instead: use github.event.pull_request.head.repo.full_name to detect cross-repo
# Or: use a separate "untrusted" job that has NO write permissions and NO cache access
pull_request_target und main Branch-Workflows geteiltactions/cache@v5 Post-Job-Save wird nicht durch permissions: contents: read eingeschränkt- uses: actions/cache@v5
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# ↑ Only save cache for trusted contexts
id-token: write erstellt bei der ersten Verwendung verzögert (lazy) ein Token im Runner-Speicherenvironment: publish # Configure environment protection rules in GitHub settings
permissions:
id-token: write
contents: read
Die Security-Community dokumentiert pull_request_target Cache Poisoning bereits seit 2024 (Adnan Khan, "The Monsters in Your Build Cache"). Dieser Angriff zeigt, dass bekannte, dokumentierte Muster aktiv und in großem Stil gegen hochkarätige Open-Source-Projekte ausgenutzt werden.
Die Kette verdeutlicht, dass OIDC Trusted Publishing zwar eine Verbesserung gegenüber statischen Token darstellt, aber nicht verhindert, dass eine kompromittierte CI-Pipeline bösartige Pakete veröffentlicht. Der zweite Faktor – auf der Seite von npm – bleibt die Schwachstelle: Sobald ein OIDC-Token innerhalb eines Workflows erstellt wurde, kann jeder Code in diesem Workflow ihn zum Veröffentlichen nutzen.
Das selbstpropagierende Design (das Aufzählen und erneute Veröffentlichen anderer Pakete des Opfers) stellt eine Eskalation vom reinen Supply-Chain-Angriff hin zu einer automatisierten lateralen Bewegung dar. Hier geht es nicht mehr nur um den Diebstahl von Zugangsdaten – es geht darum, die eigene Infrastruktur zur Paketveröffentlichung in ein Malware-Distributionsnetzwerk zu verwandeln.
Referenzen