Back openDesk Edu for a sovereign, open-source education â every vote counts.
Vote nowSave products you love by clicking the heart icon.
Shifting cloud cost governance into the pipeline â Infracost cost estimates, OPA/Rego policy gates, OpenCost runtime monitoring, and the three-phase adoption path to hard-deny budgets.
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