1 Punkte von GN⁺ 2 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Am 2026-05-11 zwischen 19:20 und 19:26 UTC veröffentlichte ein Angreifer 84 bösartige Versionen in 42 @tanstack/-npm-Paketen
  • Die Angriffskette kombinierte einen pull_request_target-„Pwn Request“, GitHub-Actions-Cache-Vergiftung und das Extrahieren von OIDC-Tokens aus dem Runner-Speicher
  • npm-Token und der Publish-Workflow wurden weder gestohlen noch kompromittiert; die Schadsoftware führte stattdessen direkte POSTs an die Registry mit OIDC trusted publisher-Rechten aus
  • Bei der Installation betroffener Versionen könnten AWS-, GCP-, Kubernetes-, Vault-, GitHub-, npm- und SSH-Zugangsdaten offengelegt worden sein und sollten ersetzt werden
  • Alle betroffenen Versionen wurden als deprecated markiert, mit npm security wurde die Entfernung der Tarballs eingeleitet, und ein Tracking-Issue sowie ein GitHub Security Advisory wurden veröffentlicht

Überblick über den Vorfall

  • Zwischen 19:20 und 19:26 UTC am 2026-05-11 veröffentlichte ein Angreifer 84 bösartige Versionen in 42 @tanstack/*-npm-Paketen
  • Die Angriffskette kombinierte das pull_request_target-„Pwn Request“-Muster, eine GitHub-Actions-Cache-Vergiftung über die Vertrauensgrenze zwischen Fork und Base hinweg sowie das Extrahieren von OIDC-Tokens aus dem Prozessspeicher von GitHub-Actions-Runnern
  • Es wurde bestätigt, dass keine npm-Token gestohlen wurden und auch der npm-Publish-Workflow selbst nicht kompromittiert war
  • Die bösartigen Versionen wurden vom externen Forscher ashishkurmi bei stepsecurity öffentlich innerhalb von 20 Minuten entdeckt
  • Alle betroffenen Versionen wurden als deprecated markiert, und zusammen mit npm security wurde die Entfernung der Tarballs aus der Registry eingeleitet
  • Nutzer, die am 2026-05-11 betroffene Versionen installiert haben, müssen AWS-, GCP-, Kubernetes-, Vault-, GitHub-, npm- und SSH-Zugangsdaten, auf die vom Installations-Host aus zugegriffen werden konnte, ersetzen
  • Das Tracking-Issue ist TanStack/router#7383, das GitHub Security Advisory ist GHSA-g7cv-rxg3-hmpx

Betroffene Reichweite

  • Betroffene Pakete

    • Der Umfang umfasst 42 Pakete und 84 Versionen; pro Paket wurden zwei Versionen im Abstand von etwa 6 Minuten veröffentlicht
    • Die vollständige Liste ist im Tracking-Issue enthalten
    • Als sicher nicht betroffene Produktfamilien bestätigt sind @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store und das Meta-Paket @tanstack/start
    • @tanstack/start-* ist nicht in der bestätigten Liste nicht betroffener Pakete enthalten
  • Verhalten der Schadsoftware

    • Wenn eine Entwickler- oder CI-Umgebung für betroffene Versionen npm install, pnpm install oder yarn install ausführt, wertet npm den bösartigen optionalDependencies-Eintrag aus und holt einen verwaisten Payload-Commit aus dem Fork-Netzwerk
    • Anschließend wird das Lifecycle-Skript prepare ausgeführt, wobei die im betroffenen Tarball versteckte, etwa 2,3 MB große obfuskierte Datei router_init.js aktiv wird
    • Das bösartige Skript sammelt Zugangsdaten aus gängigen Speicherorten, darunter AWS IMDS/Secrets Manager, GCP-Metadaten, Kubernetes-Service-Account-Token, Vault-Token, ~/.npmrc, GitHub-Token, die gh-CLI, .git-credentials, SSH-Private-Key und weitere
    • Die exfiltrierten Daten werden über das Session/Oxen-Messenger-File-Upload-Netzwerk übertragen; Zielsysteme sind filev2.getsession.org und seed{1,2,3}.getsession.org
    • Da dieses Netzwerk Ende-zu-Ende-verschlüsselt ist und kein vom Angreifer kontrolliertes C2 besitzt, bleiben als Netzwerkminderung nur IP-/Domain-Sperren
    • Die Logik zur Selbstverbreitung zählt mit registry.npmjs.org/-/v1/search?text=maintainer:<user> weitere vom Opfer gepflegte Pakete auf und veröffentlicht sie anschließend mit derselben Injektionsmethode erneut
    • Da die Payload als Teil des npm-install-Lifecycles ausgeführt wird, sollten Hosts, die am 2026-05-11 betroffene Versionen installiert haben, als potenziell kompromittiert behandelt werden

Zeitleiste

  • Vor dem Angriff: Phase der Cache-Vergiftung

    • Am 2026-05-10 um 17:16 UTC erstellte der Angreifer github.com/zblgg/configuration, einen Fork von TanStack/router, und änderte den Namen, um die Suche in der Fork-Liste zu umgehen.
    • Am 2026-05-10 um 23:29 UTC wurde mit der manipulierten Identität claude <claude@users.noreply.github.com> der schädliche Commit 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14 in den Fork geschrieben.
    • Dieser Commit fügte packages/history/vite_setup.mjs hinzu, einen gebündelten JS-Payload mit rund 30.000 Zeilen, und versah die Commit-Message mit [skip ci], um die CI bei Push-Events zu unterdrücken.
    • Am 2026-05-11 gegen 10:49 UTC eröffnete zblgg den PR #7378 gegen TanStack/router main mit dem Titel „WIP: simplify history build“.
    • bundle-size.yml und labeler.yml wurden beide für den PR automatisch über pull_request_target ausgeführt; da pull_request_target das Freigabe-Gate für Erstbeitragende umgeht, war keine separate Freigabe erforderlich.
    • pr.yml, das pull_request verwendet, blieb wegen ausstehender Freigabe blockiert und wurde nicht ausgeführt.
    • Zwischen 11:01 und 11:11 UTC am 2026-05-11 führte zblgg mehrfach Force-Pushes auf den PR-Head aus und löste dadurch zusätzliche pull_request_target-Ausführungen aus.
    • Am 2026-05-11 um 11:11 UTC landete der schädliche Commit 65bf499d auf dem PR-Head, und der Job benchmark-pr aus bundle-size.yml checkte anschließend refs/pull/7378/merge aus und führte pnpm install sowie pnpm nx run @benchmarks/bundle-size:build aus, wodurch vite_setup.mjs ausgeführt wurde.
    • Am 2026-05-11 um 11:29 UTC wurde in TanStack/router ein 1,1-GB-GitHub-Actions-Cache mit dem Namen Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 gespeichert.
    • Dieser Cache wurde im Scope refs/heads/main gespeichert und so aufgebaut, dass er zu dem Schlüssel passte, den release.yml beim nächsten Push auf main abrufen würde.
    • Am 2026-05-11 um 11:31 UTC setzte der Angreifer den PR auf den damaligen main-HEAD b1c061af zurück, sodass der sichtbare PR wie ein 0-Dateien-No-op aussah, und schloss im selben Moment den PR und löschte den Branch, aber der vergiftete Cache blieb bestehen.
  • Auslösung: Veröffentlichungsphase

    • Am 2026-05-11 um 19:15 UTC löste Manuel durch das Mergen von PR #7369 einen Push auf main aus; daraufhin startete der Workflow-Run 25613093674 von release.yml um 19:15:44 und schlug anschließend fehl.
    • Am 2026-05-11 um 19:20:39 UTC empfing die npm-Registry die Veröffentlichungen von @tanstack/history@1.161.9 und 41 zugehörigen Paketen.
    • Insgesamt wurden über 42 Pakete hinweg etwa 84 Versionen veröffentlicht, aber zu genau diesem Zeitpunkt war nur ungefähr die Hälfte sichtbar; der Rest wurde im zweiten Run veröffentlicht.
    • Die Publish-Authentifizierung erfolgte über ein OIDC trusted-publisher binding für TanStack/router release.yml@refs/heads/main, sie stammte jedoch nicht aus dem Step Publish Packages des wegen fehlgeschlagener Tests übersprungenen Workflows.
    • Der tatsächliche Publisher war die Malware, die während der Test-/Bereinigungsschritte ausgeführt wurde; sie mintete mit der Berechtigung id-token: write ein OIDC-Token und sendete dann direkt einen POST an registry.npmjs.org.
    • Am 2026-05-11 um 19:20:47 UTC wurde Run 25613093674 mit dem Status failure abgeschlossen.
    • Am 2026-05-11 um 19:16 UTC löste Manuel durch das Mergen von PR #7382 einen zweiten Push auf main aus, und um 19:16:22 begann Workflow-Run 25691781302.
    • Auch der zweite Run stellte denselben vergifteten Cache wieder her, und am 2026-05-11 um 19:26:14 UTC wurde mit demselben OIDC-Mechanismus ein zweiter Versionssatz pro Paket veröffentlicht, darunter @tanstack/history@1.161.12.
    • Am 2026-05-11 um 19:26:20 UTC wurde auch Run 25691781302 mit dem Status failure abgeschlossen.
  • Erkennung und Reaktion

    • Am 2026-05-11 gegen 19:50 UTC eröffnete der externe Forscher carlini das Issue #7383 mit einem schädlichen optionalDependencies-Fingerprint und einer Paketliste.
    • Die erste Liste umfasste 14 von 42 Paketen, und der Forscher informierte auch direkt npm security.
    • Am 2026-05-11 gegen 20:00 UTC bestätigte Manuel in #7383 den Vorfall und begann mit der Reaktion.
    • Am 2026-05-11 gegen 20:10 UTC entfernte Manuel die GitHub-Push-Berechtigungen anderer Teammitglieder, um auf eine mögliche Kompromittierung von Benutzerrechnern vorbereitet zu sein.
    • Am 2026-05-11 gegen 20:30 UTC schickte Tanner die vollständige IOC-Liste und die Anfrage zur Entfernung der Tarballs auf Registry-Seite an security@npmjs.com und reichte über npm einen formalen Malware-Report ein.
    • Am 2026-05-11 gegen 21:00 UTC bestätigte ein vollständiger Scan aller 295 @tanstack/*-Pakete den Umfang mit 42 Paketen und 84 Versionen.
    • Tanner begann mit der npm-Deprecation aller 84 betroffenen Pakete, und @tan_stack sowie die Maintainer veröffentlichten öffentliche Warnungen auf Twitter/X, LinkedIn und Bluesky.
    • Am 2026-05-11 um 21:30 UTC wurden der Cache-Vergiftungsvektor über pull_request_target in bundle-size.yml und der Fork zblgg/configuration identifiziert.
    • Cache-Einträge aller TanStack/*-GitHub-Repositories wurden per API entfernt.
    • Ein Hardening-PR wurde gemergt, bundle-size.yml wurde neu konfiguriert, ein repository_owner-Guard wurde hinzugefügt und die Refs von Third-Party-Actions wurden auf SHA festgeschrieben.
    • Ein offizielles GitHub Security Advisory wurde veröffentlicht und eine CVE beantragt.

Grundursache

  • Kombination aus drei Schwachstellen

    • Für den Angriff waren alle drei Schwachstellen erforderlich; keine einzelne war für sich allein ausreichend.
    • Der Fork-PR-Code gelangte in den Cache des Basis-Repositorys, der Cache des Basis-Repositorys in die Runtime des Release-Workflows, und die Runtime des Release-Workflows führte schließlich zu Schreibrechten auf die npm-Registry – so verband jede Schwachstelle die Vertrauensgrenzen der jeweils anderen.
  • pull_request_target-„Pwn Request“-Muster

    • bundle-size.yml wurde für Fork-PRs mit pull_request_target ausgeführt, checkte in diesem Trigger-Kontext den PR-Merge-Ref des Forks aus und führte dann den Build aus.
    • Die Kernstruktur sah wie folgt 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 # fork's merged code
    
          - uses: TanStack/config/.github/setup@main # transitively calls actions/cache@v5
    
          - run: pnpm nx run @benchmarks/bundle-size:build # executes fork-controlled code
    
    • Die Verfasser des Workflows versuchten, die Vertrauensgrenzen zu trennen, indem sie den comment-pr-Job und den benchmark-pr-Job separierten; in einem YAML-Kommentar stand, dass benchmark-pr als „untrusted with read-only permissions“ beibehalten werden sollte.
    • Das Post-Job-Speichern von actions/cache@v5 wird jedoch nicht durch permissions: blockiert, und Cache-Schreibzugriffe verwenden nicht das GITHUB_TOKEN des Workflows, sondern ein internes Token des Runners.
    • Daher konnte die Einstellung permissions: contents: read die Mutation des Caches nicht verhindern.
    • Der Cache-Scope ist auf Repository-Ebene, und ein pull_request_target-Run mit dem Cache-Scope des Basis-Repositorys wird zusammen mit einem main-Push geteilt.
    • Ein PR, der im Cache-Scope des Basis-Repositorys läuft, kann daher einen Cache-Eintrag vergiften, den später der Production-Workflow von main wiederherstellt.
  • Vergiftung des GitHub-Actions-Caches

    • Die bösartige vite_setup.mjs war so entworfen, dass sie Daten passend zu dem pnpm-store-Schlüssel schreibt, den der legitime Workflow release.yml berechnet und abfragt.
    • Der Zielschlüssel hatte das Format Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')}.
    • Beim Ende des benchmark-pr-Jobs speicherte der Post-Step von actions/cache@v5 den vergifteten pnpm-Store unter genau diesem Schlüssel.
    • Als später bei einem main-Push release.yml ausgeführt wurde, stellte der Step Setup Tools den vergifteten Eintrag wie vorgesehen wieder her.
    • Dieser Angriffstyp gehört zur Klasse der GitHub-Actions-Cache-Poisoning-Angriffe, die 2024 von Adnan Khan dokumentiert wurden; es handelt sich nicht um einen auf TanStack beschränkten Bug, sondern um ein Designproblem von GitHub Actions, das bewusste Gegenmaßnahmen erfordert.
  • Extraktion des OIDC-Tokens aus dem Runner-Speicher

    • release.yml deklariert berechtigterweise id-token: write, da dies für npm OIDC trusted publishing benötigt wird.
    • Wenn der vergiftete pnpm-Store auf dem Runner wiederhergestellt wird, befindet sich eine vom Angreifer kontrollierte Binärdatei auf der Platte und wird im Build-Step aufgerufen.
    • Diese Binärdatei findet über /proc/*/cmdline den GitHub-Actions-Prozess Runner.Worker und liest /proc/<pid>/maps sowie /proc/<pid>/mem, um den Worker-Speicher zu dumpen.
    • Anschließend extrahiert sie aus dem Speicher das OIDC-Token, das der Runner unter der Einstellung id-token: write per Lazy Minting erzeugt hat.
    • Mit dem extrahierten Token authentifizierte der Angreifer direkte POST-Anfragen an registry.npmjs.org und umging damit den Step Publish Packages des Workflows vollständig.
    • Diese Methode der Speicherextraktion entspricht der Methode, die beim Kompromittierungsfall tj-actions/changed-files im März 2025 verwendet wurde; dabei kam dasselbe Python-Skript inklusive Attribution-Kommentar zum Einsatz.
    • Der Angreifer hat also keine neue Technik erfunden, sondern öffentliche Forschung neu kombiniert.
  • Warum die einzelnen Elemente für sich allein nicht ausreichten

    • pull_request_target selbst kann für vertrauenswürdige Aufgaben wie Labels oder Kommentare verwendet werden.
    • Cache Poisoning innerhalb einer bereits kompromittierten Dependency reicht für sich genommen nicht aus, sondern benötigt zusätzlich ein separates Publish-Vehikel.
    • OIDC-Token-Extraktion allein setzt bereits vorhandene Code-Ausführung auf dem Runner voraus.

Erkennung und IOC

  • Erkennungspfad

    • Die Erkennung erfolgte nicht intern, sondern von außen.
    • carlini eröffnete etwa 20 Minuten nach der Veröffentlichung Issue #7383 und lieferte darin eine vollständige technische Analyse.
    • Tanner erhielt kurz nach Beginn des War Room einen Anruf von Socket.dev, der die Lage bestätigte.
  • Fingerprints für Downstream-Maintainer und Security-Tools

    • In den Package-Manifests von @tanstack/* ist der folgende Eintrag unter optionalDependencies der zentrale IOC.
    "optionalDependencies": {
      "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
    }
    
    • Der Datei-IOC ist router_init.js im Package-Root; die Datei ist etwa 2,3 MB groß und nicht in "files" enthalten.
    • Der Cache-Schlüssel lautet Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11.
    • Die URLs der Payload der zweiten Stufe sind https://litter.catbox.moe/h8nc9u.js und https://litter.catbox.moe/7rrc6l.mjs.
    • Das Exfiltrationsnetzwerk ist filev2.getsession.org, seed{1,2,3}.getsession.org.
    • Die gefälschte Commit-Identität lautet claude <claude@users.noreply.github.com>; dabei handelt es sich nicht um das echte Anthropic Claude, sondern um eine manipulierte GitHub-No-Reply-E-Mail.
    • Die tatsächlichen Angreiferkonten sind zblgg id 127806521 und voicproducoes id 269549300.
    • Der Fork des Angreifers ist github.com/zblgg/configuration und wurde zur Umgehung der Suche von einem TanStack/router-Fork umbenannt.
    • Der Orphan-Payload-Commit im Fork-Netzwerk ist 79ac49eedf774dd4b0cfa308722bc463cfe5885c.
    • Die Workflow-Runs, die die bösartige Veröffentlichung durchgeführt haben, sind github.com/TanStack/router/actions/runs/25613093674 Versuch 4 und github.com/TanStack/router/actions/runs/25691781302.

Erkenntnisse

  • Was gut lief

    • Externe Forschende entdeckten den Vorfall rund 20 Minuten nach dem Ereignis und meldeten ihn mit vollständigen technischen Details.
    • Das Maintainer-Team koordinierte sich sofort über mehrere Zeitzonen hinweg.
    • Die Detection-Community hatte innerhalb weniger Stunden klare öffentliche IOC-Muster.
  • Was verbessert werden muss

    • Es gab kein internes Alerting, und die Kompromittierung wurde zuerst von Dritten bekannt gemacht.
    • Eigenes Publish-Monitoring ist nötig; zudem ist geplant, enger mit Unternehmen für Ökosystem-Sicherheitsforschung zusammenzuarbeiten, die solche Probleme schnell erkennen können, und die Feedback-Schleife zu verkürzen.
    • Der pull_request_target-Workflow war schon lange als riskantes Muster bekannt, wurde aber nicht auditiert.
    • Floating Refs von Third-Party-Actions wie @v6.0.2 und @main schaffen unabhängig von diesem Vorfall ein dauerhaftes Supply-Chain-Risiko.
    • Wegen der npm-Richtlinie „kein Unpublish bei vorhandenen Dependents“ war ein Unpublish bei fast allen betroffenen Paketen unmöglich.
    • Für das Entfernen der Tarballs auf Registry-Seite musste man sich auf npm Security verlassen, wodurch die bösartigen Tarballs noch mehrere Stunden installierbar blieben.
    • Die Liste von 7 Maintainers im npm-Scope bedeutet 7 separate Ziele für Credential Theft bei identischem Blast Radius.
    • Beim OIDC-Trusted-Publisher-Binding gibt es keine Review pro Publish; ist es einmal eingerichtet, kann jeder Codepfad im Workflow ein veröffentlichungsfähiges Token minten.
    • Eine nötige Alternative wäre der Wechsel zu kurzlebigen Classic Tokens mit manueller Review oder das Hinzufügen einer Provenance-Source-Verification, die Publishes aus unerwarteten Workflow-Schritten erkennt.
  • Wo Glück im Spiel war

    • Der Angreifer wählte eine Payload, die die Tests kaputt machte, sodass der normale Publish-Schritt übersprungen wurde und kein sauberer wirkender Tarball erzeugt wurde.
    • Dadurch wurde der Angriff auffällig genug, um schnell entdeckt zu werden.
    • Ein vorsichtigerer Angreifer hätte ohne das Brechen der Tests noch mehrere Stunden unauffällig publizieren können.
    • Der Angreifer verwendete ein öffentliches Memory-Dump-Skript mit Attribution-Kommentar wieder und schrieb keinen neuen Code, was das IOC-Matching beschleunigte.

Offene Fragen

  • Es muss bestätigt werden, ob der Schritt Setup Tools in bundle-size.yml tatsächlich actions/cache@v5 aufgerufen hat.
  • Zur Verifikation muss das Post-Job-Log eines pull_request_target-Runs für PR #7378 gelesen werden; eine Beispiel-Run-ID ist 25666610798.
  • Es muss geprüft werden, was sich im ursprünglichen PR-Head-Commit befand, bevor es durch einen Force-Push verschwand; es könnte noch im GitHub-Reflog stehen.
  • Es muss bestätigt werden, ob der bösartige Commit per direktem Git-Push in den Git-Object-Store des Forks gelangte oder über eine Erstellung in der GitHub-Web-UI, die einen Audit-Log-Eintrag hinterlassen hätte.
  • Es muss abgeglichen werden, ob voicproducoes ein reales Konto oder eine Sock Puppet ist, anhand des Aktivitätsverlaufs.
  • Es muss geprüft werden, ob auch der npm-Cache kontaminiert war, der wie 6 doppelte linux-npm-store-*-Einträge aussieht, und ob er tatsächlich genutzt wurde.
  • Es muss geprüft werden, ob für den Angriff Nx Cloud erforderlich war oder ob GitHub Actions Cache allein ausgereicht hätte.
  • Es muss geprüft werden, ob sich im Fork-Netzwerk von TanStack/router weitere Forks mit dem verwaisten Payload-Commit identifizieren lassen.
  • Wenn andere Forks diesen Commit hosten, bleibt die Erreichbarkeit über github:tanstack/router#79ac49ee... bestehen, was die Bereinigung erschwert.
  • Es ist ein Audit nötig, ob andere TanStack-Repositories wie router, query, table, form und virtual dasselbe Muster im Stil von bundle-size.yml verwenden.
  • Von npm Support muss die Zahl der Nutzer eingeholt werden, die die betroffenen Versionen während des Publish-Fensters tatsächlich heruntergeladen haben.
  • Es muss geprüft werden, ob die Rechner der 7 Maintainers separat kompromittiert wurden.
  • Für den bösartigen Publish wurde zwar kein npm-Token eines Maintainers verwendet, aber die Rechner der Maintainers könnten sekundäre Ziele der Self-Propagation-Logik gewesen sein.

Referenzmaterial

1 Kommentare

 
GN⁺ 2 시간 전
Hacker-News-Kommentare
  • Man sollte vorsichtig sein, wenn man Tokens widerruft. Es sieht so aus, als würde die Payload einen dead-man's switch unter ~/.local/bin/gh-token-monitor.sh installieren und ihn unter Linux als systemd-Benutzerdienst sowie unter macOS als LaunchAgent com.user.gh-token-monitor registrieren
    Mit dem gestohlenen Token wird alle 60 Sekunden api.github.com/user abgefragt, und wenn der Token widerrufen wurde und ein HTTP-40x zurückkommt, wird rm -rf ~/ ausgeführt
    https://github.com/TanStack/router/issues/7383#issuecomment-...

    • Realistisch gesehen muss man den Computer ohnehin komplett neu aufsetzen, wenn man Malware installiert hat
    • Erschreckend. Das ist eine Art gegenseitig zugesicherte Zerstörung
      Die Softwarewelt dürfte in den nächsten fünf Jahren wirklich rau werden, und air-gapped Systeme könnten sehr wichtig werden
    • Eigentlich hätte man immer Backups einrichten sollen, aber wenn dieses Ereignis Menschen immerhin dazu bringt, dann ist das zumindest etwas Gutes
  • Auch das npm-Paket @mistralai/mistralai wurde als Teil dieses Wurms kompromittiert
    https://github.com/mistralai/client-ts/issues/217
    Inzwischen wurde es aus dem npm-Registry entfernt

  • Leider scheint das ein Beleg dafür zu sein, dass Trusted Publishing allein nicht ausreicht, um sicher aus CI heraus zu veröffentlichen. Mit einem Angreifer innerhalb der CI-Pipeline oder gestohlenen Repository-Admin-Rechten lässt sich leicht veröffentlichen
    Das ist keine neue Information, und Trusted Publishing wurde auch nicht dafür entworfen, das zu garantieren, aber wenn man von lokalem Deployment mit Zwei-Faktor-Authentifizierung auf Trusted Publishing umstellt, entsteht dieser Angriffsweg über kompromittierte CI. Der zweite Faktor, der beim lokalen Arbeiten npm publish verhindert hätte, fällt damit weg
    Nach dem bisherigen Verlauf hat der Angreifer die CI/CD-Pipeline übernommen und konnte die Veröffentlichung abschließen, weil bei npm publish kein zweiter Faktor vorhanden war, indem er den OIDC-Token gestohlen hat. Interessant, aber getrennt davon scheint der eigentliche Deployment-Job fehlgeschlagen zu sein, während die Payload im bösartigen Commit sich mit dem OIDC-Token des Workflows selbst veröffentlichen konnte
    Wünschenswert wäre, das Trusted-Publisher-Modell ohne langlebige Tokens beizubehalten, aber für CI-Deployments dennoch einen zweiten Faktor außerhalb von GitHub zu haben. Es braucht also ein stufenweises Deployment, bei dem jemand auf npm-Seite das Artefakt per Zwei-Faktor-Authentifizierung tatsächlich in den öffentlichen Zustand überführt
    Wenn Deployment ausschließlich innerhalb des GitHub-Vertrauensmodells möglich ist, kann jeder, der ein Repository-Admin-Token stiehlt oder bösartigen Code in die Pipeline bringt, die Veröffentlichung leicht abschließen. Mit einem echten zweiten Faktor außerhalb des GitHub-Kontexts kann man zwar das Repository beschädigen oder Malware einschleusen, aber ohne den zweiten Faktor für die Registry nicht veröffentlichen

    • Ich habe ein mäßig populäres Paket und nutze weiterhin lokales Deployment und Zwei-Faktor-Authentifizierung. Trusted Publishing wirkt zu komplex und scheint ständig gehackt zu werden, sodass ich mich frage, ob es für einen sicheren Betrieb nicht zu komplex ist und vielleicht von Grund auf neu entworfen werden müsste
    • Ich halte Trusted Publishing immer noch für eine große Verbesserung, aber die Idee, beim tatsächlichen Freigeben eines Releases einen zweiten Faktor zu verlangen, ist gut. Das würde es sehr schwer machen, solche CI-Würmer auszuführen
    • Ich würde gern mit etwas wie einem YubiKey per Touch-Signatur signieren. Schon die Grundidee, der Cloud die Verwaltung der Credentials zu überlassen, wirkt wie ein Fehler
    • Im astral-Blog wurde kürzlich gezeigt, wie man trotz Trusted Publishing ein Release-Gate einbaut, also eine manuelle Freigabe im Release-Workflow. Leider erwähnen die Trusted-Publishing-Dokumentationen von NPM/PyPI/Rubygems diese Möglichkeit nicht einmal und bieten sie auch nicht standardmäßig an
    • Ich habe nie ganz verstanden, warum Leute sagen, dass Trusted Publishing bei dieser Art von Supply-Chain-Angriffen einen Unterschied macht
  • Postmortem: https://tanstack.com/blog/npm-supply-chain-compromise-postmo...

    • Das Postmortem von TanStack ist zwar willkommen, aber aus Sicht des gesamten npm-Ökosystems scheinen die Sicherheitsprobleme weiterhin berechtigte laufende Sorgen zu sein
      Ich frage mich, ob es Belege dafür gibt, dass man Unterpakete, die TanStack-Pakete heruntergezogen oder eingebunden haben könnten, als sicher betrachten kann
  • postinstall-Skripte sind fatal. Alle sollten pnpm verwenden
    Es ist absurd, dass ein „verwaister“ Commit, der in einen FORK gepusht wurde, beim npm-Client so etwas auslösen kann. Ich finde, auch GitHub trägt hier große Verantwortung. Dass Commits aus bösartigen Forks über GitHubs gemeinsamen Objektspeicher unter einer URI erreichbar sind, die nicht von einem legitimen Repository zu unterscheiden ist, ist ein völlig irrsinniges Design

    • Wenn man die App mit aktualisierten Abhängigkeiten ausführt, wird der Code ohnehin ausgeführt. Ob root oder non-root spielt keine Rolle; alles Wichtige ist mit den Benutzerrechten zugänglich, unter denen die Anwendung läuft
    • Ich verstehe nicht, warum das kein P0-Ausfall bei GitHub ist. Kann das jemand erklären?
      Als ich es zuerst gelesen habe, dachte ich, das Wort „fork“ sei falsch verwendet worden und gemeint sei eigentlich ein Branch des offiziellen Repositorys. Ich dachte, das könne unmöglich echt sein, aber meine Güte
  • https://tanstack.com/blog/npm-supply-chain-compromise-postmo...
    TanStack hat gerade ein Postmortem zu diesem Vorfall veröffentlicht

  • Das ist eine Erinnerung daran, die npm-Umgebung sicher zu konfigurieren
    https://gajus.com/blog/3-pnpm-settings-to-protect-yourself-f...
    Schon mit ein paar Einstellungen lassen sich große Probleme vermeiden

    • In npm ab v11 gibt es auch allow-git=none: https://github.blog/changelog/2026-02-18-npm-bulk-trusted-pu...
    • Ich glaube, dieser Artikel liegt bei der Mindestalter-Einstellung für Releases von npm falsch. 1) Der Name der Einstellung ist min-release-age. 2) Aus irgendeinem Grund wurde sie in Tagen statt in Minuten umgesetzt: https://docs.npmjs.com/cli/v11/using-npm/config#min-release-...
      Ich halte den Bereich der Abhängigkeitsmanager für völlig unnötig fragmentiert
    • Die Behauptung, man würde mit einem Mindestalter von 7 Tagen bei npm „nie“ Supply-Chain-Schwachstellen erleben, ist übertrieben
    • Alle Abhängigkeiten müssen unbedingt festgepinnt werden
      Wenn Versionsabhängigkeiten wie ^1.0.0 oder sogar "*" verwendet werden, sollte man nicht weiter lesen, sondern sofort auf sichere Versionen pinnen
  • Ich habe das hastig mit Claude gebaut, um die Ausbreitung einzudämmen. Natürlich sollte man es selbst prüfen, aber es scannt, ob die erwähnten kompromittierten Pakete auf der Maschine vorhanden sind: https://github.com/PaulSinghDev/tanstack-shai-hulud-fix

  • Es wirkt inzwischen so, als wären wir an dem Punkt angekommen, an dem alle ihre Projekte in eigenen VMs ausführen sollten
    Wenn man sich die jüngsten lokalen Privilege-Escalation-Schwachstellen ansieht, reicht Docker allein definitiv nicht aus. Container wurden schließlich auch nicht als primäre Sicherheitsgrenze entworfen

    • Devcontainers sind die bekannteste Form dieses Konzepts einer „isolierten Entwicklungsumgebung“, aber keine vollständigen VMs und hätten auch in diesem Fall nicht vollständig geschützt, weil GitHub-Credentials automatisch in den Container gelangen
      Wenn es weitere Cloud-Dienste gibt, auf die man im Container zugreifen muss, wird dieser Credential-Stealer sie ebenfalls abgreifen. Trotzdem verkleinert das immerhin den Blast Radius und ist damit wenigstens eine Verbesserung
    • QubesOS weist in die richtige Richtung. Man möchte mehrere VMs auf der Root-Ebene und mehrschichtige Sicherheitslagen
    • Wenn man unbedingt Container nutzen will, kann man auch einen VM-pro-Container-Ansatz wählen. Die letzten Wochen waren ziemlich entspannt, weil alles auf VMs lief und nicht auf irgendeinem zufälligen Kubernetes-Dienst
    • Zum Glück sind Projekte, die ein sichereres Sprachökosystem wie C und C++ verwenden, von solchen Problemen verschont :-)
  • Wow, schon wieder ein riesiges Paket. Ich poste erneut den öffentlichen Hinweis, den ich nach der Kompromittierung von Axios und LiteLLM gepostet hatte. Das gilt auch für Lifecycle-Skripte
    npm/bun/pnpm/uv unterstützen inzwischen alle eine Einstellung für das Mindestalter von Paket-Releases. In ~/.npmrc habe ich außerdem ignore-scripts=true gesetzt, und nach der Analyse hätte allein das die Schwachstelle entschärfen können. bun und pnpm führen Lifecycle-Skripte standardmäßig nicht aus
    So lässt sich global ein Mindestalter von 7 Tagen setzen
    ~/.config/uv/uv.toml
    exclude-newer = "7 days"
    ~/.npmrc
    min-release-age=7 # days
    ignore-scripts=true
    ~/Library/Preferences/pnpm/rc
    minimum-release-age=10080 # minutes
    ~/.bunfig.toml
    [install]
    minimumReleaseAge = 604800 # seconds
    Wenn man die globale Einstellung überschreiben muss, kann man CLI-Flags verwenden
    npm install --min-release-age 0
    pnpm add --minimum-release-age 0
    uv add --exclude-newer "0 days"
    bun add --minimum-release-age 0
    Noch ein Punkt: Es scheint Bedenken zu geben, dass eine breit angelegte Einführung von Wartezeiten für Abhängigkeiten dazu führt, dass Schwachstellen später entdeckt werden, oder dass solche Wartezeiten eine Art Trittbrettfahrerei sind. Dem stimme ich nicht zu. Was man gegen Wartezeiten für Abhängigkeiten eintauscht, ist Zeitpräferenz, und es wird immer Menschen geben, deren Zeitpräferenz höher ist als meine
    0: https://news.ycombinator.com/item?id=47582220
    1: https://news.ycombinator.com/item?id=47513932

    • Stimme zu. Gut, dass ich diese Einstellungen schon im März aktiviert hatte, also vor den letzten beiden Wellen. Zusätzlich sollte man das Lockfile ins Repository committen und beim Hinzufügen neuer Abhängigkeiten vorsichtig sein
      Um unerwartete Änderungen zu vermeiden, kann man pnpm install --frozen-lockfile verwenden. Man sollte außerdem im Hinterkopf behalten, dass man ohne gesetztes min-release-age auch über transitive Abhängigkeiten kompromittierte Pakete hereinziehen kann. Wenn möglich, ist es auch sinnvoll, die Version des Paketmanagers festzupinnen