Die Mastra-npm-Kompromittierung, gelesen als Compliance-Post-mortem
Sapphire Sleet brauchte neunzehn Minuten, um über 140 @mastra-Pakete zu vergiften, und der Ausführungspfad, den die Payload nutzte, ist ein Control, das fast niemand aufschreibt: Hier ist der Vorfall abgebildet auf die Supply-Chain-Pläne, die NIST am 30. Juni finalisiert hat, und der Grund, warum eine SBOM ihn nicht erwischt hätte.
++++
field notesNº15
fig — Die Mastra-npm-Kompromittierung, gelesen als Compliance-Post-mortem
Sapphire Sleet brauchte neunzehn Minuten, um über 140 @mastra-Pakete zu vergiften, und der Ausführungspfad, den die Payload nutzte, ist ein Control, das fast niemand aufschreibt: Hier ist der Vorfall abgebildet auf die Supply-Chain-Pläne, die NIST am 30. Juni finalisiert hat, und der Grund, warum eine SBOM ihn nicht erwischt hätte.
In neunzehn Minuten zu 140 Paketen
Am 16. Juni um 07:05 UTC wurde ein Paket namens [email protected] auf npm veröffentlicht. Es war sauber. Das war der Punkt: Es war ein Köder, ein plausibles Utility, das unauffällig in der Registry lag. Achtzehn Stunden später, am 17. Juni um 01:01 UTC, erschien Version 1.11.22 mit einem postinstall-Hook, der einen Dropper namens setup.cjs ausführte. Um 01:20 UTC wurden [email protected] und über 140 weitere @mastra/*-Pakete mit easy-day-js als Abhängigkeit veröffentlicht.
shell
2026-06-16 07:05 UTC [email protected] veröffentlicht (sauberer Köder, keine Payload)2026-06-17 01:01 UTC [email protected] veröffentlicht (fügt postinstall → setup.cjs hinzu)2026-06-17 01:20 UTC [email protected] + 140+ @mastra/*-Pakete veröffentlicht mit easy-day-js als Abhängigkeit
Neunzehn Minuten von der präparierten Veröffentlichung bis zum vollständig vergifteten Scope. Die Zeitleiste stammt aus Microsofts Post-mortem, das die Kampagne mit hoher Sicherheit Sapphire Sleet zuschreibt, „einem nordkoreanischen staatlichen Akteur, der seit mindestens März 2020 aktiv ist“ und der sich „vor allem auf den Finanzsektor konzentriert, einschliesslich Kryptowährungs-, Venture-Capital- und Blockchain-Organisationen“ (alle Zitate aus dem Microsoft-Beitrag sind aus dem englischen Original übersetzt).
Der Zugangsvektor war ein einziges Konto. Noch einmal Microsoft: „Der Bedrohungsakteur erlangte die Kontrolle über das npm-Konto ehindero, einen gelisteten Maintainer mit Publish-Rechten über den gesamten @mastra-Scope.“ Jedes frühere mastra-Release bis einschliesslich v1.13.0 ging über GitHub Actions OIDC hinaus, die normale CI/CD-Pipeline des Projekts. Version 1.13.1 wurde manuell veröffentlicht, von einer Tutamail-Adresse aus. Über diese Anomalie wurde der npm-Supply-Chain-Angriff entdeckt.
Was tatsächlich lief: setup.cjs setzt NODE_TLS_REJECT_UNAUTHORIZED auf '0' und schaltet damit die Zertifikatsprüfung für jede HTTPS-Anfrage im Node.js-Prozess ab. Es schickt ein GET an hxxps://23.254.164[.]92:8000/update/49890878, lädt ein Node.js-Implant der zweiten Stufe nach und startet es als abgekoppelten, versteckten Prozess. Persistenz je nach Betriebssystem: ein HKCU-Run-Key unter Windows, ein RunAtLoad-LaunchAgent unter macOS, eine systemd-User-Unit unter Linux. Es gleicht installierte Browser-Erweiterungen mit einer hartkodierten Liste von 166 Wallet-Extension-IDs ab, kopiert die History-SQLite-Datenbanken der Browser und holt eine PowerShell-Backdoor nach.
Der Satz, der den Rest dieses Beitrags trägt:
„Weil die Payload während der Installation ausgeführt wird, war jede Entwickler-Workstation oder Continuous-Integration-und-Continuous-Delivery-Pipeline (CI/CD), die nach der Veröffentlichung der kompromittierten Versionen npm install oder npm update ausgeführt hat, potenziell exponiert, unabhängig davon, ob das Paket im Anwendungscode importiert wurde.“
Die Exposition bestimmte sich danach, wer npm install ausführte, nicht danach, was in Produktion ging. Die Payload war nie darauf angewiesen, dass Ihre Anwendung irgendetwas importiert.
Die SBOM blieb korrekt und nutzlos
Jetzt der unbequeme Teil. Über den gesamten Vorfall hinweg blieb die SBOM eines betroffenen Projekts korrekt.
[email protected] war ein gültig veröffentlichtes Paket mit gültigem Namen und gültiger Version. Sobald es in einen Abhängigkeitsbaum geriet, hätte jeder kompetente SBOM-Generator es erfasst, und zwar korrekt:
Dieses Fragment ist illustrativ (Paketname und Version stammen aus dem Microsoft-Beitrag; das JSON ist ein Standard-Komponenteneintrag nach CycloneDX). Jedes Feld darin ist wahr. Und kein Feld in CycloneDX oder SPDX hält fest, was die Lifecycle-Skripte eines Pakets zur Installationszeit ausführen. Eine SBOM ist ein Inventarformat. Sie beantwortet „Was ist im Baum?“, nicht „Was lief auf dem Runner, als der Baum materialisiert wurde?“
Das Timing erledigt auch das Ausweichargument. Angenommen, ein Team prüft SBOM-Diffs, bevor es einen Build in die nächste Stufe befördert. Das Fenster zwischen präparierter Veröffentlichung und der Vergiftung des gesamten Scopes betrug neunzehn Minuten, und die Payload zündete bei der Installation, bevor irgendein Batch-Review sich mit einem Diff hätte hinsetzen können. Das Inventar war korrekt. Das Inventar ging auch an der Sache vorbei.
Das zählt, weil das Geld gerade ins Inventar fliesst. ENISAs Bericht zur SBOM-Einführung, veröffentlicht am 9. Juni 2026, stellt fest, dass die Cyberresilienz-Verordnung (CRA) „als Beschleuniger für die SBOM-Einführung wirkt, da Organisationen breit in SBOM-Erzeugung und -Automatisierung investieren“ (aus dem englischen Original übersetzt). 78 % der Befragten haben mit der Einführung von SBOMs begonnen; 44 % befinden sich in der Pilot- oder begrenzten Einführungsphase. Zur Nutzung berichtet ENISA eine Lücke zwischen dem Erzeugen von SBOMs und ihrem Gebrauch: 44 % melden eine „moderate Lücke“, 23 % eine „erhebliche Lücke“, und „nur 7 % haben die Lücke vollständig geschlossen, was darauf hindeutet, dass SBOMs nicht in vollem Umfang aktiv für die Sicherheit genutzt werden, sondern hauptsächlich für Compliance-Zwecke“.
Nichts davon ist ein Argument gegen SBOMs. Es ist eine Aussage über die Reichweite. CRA-getriebene Investitionen fliessen in die Schicht des Komponenteninventars, und dieser Vorfall geschah eine Schicht tiefer, auf einem Ausführungspfad, den kein Inventarformat beschreibt.
Das Control, das niemand aufschreibt
Dreizehn Tage nach der Kompromittierung, am 30. Juni, hat NIST SP 800-18 Rev.2 finalisiert, „Developing Security, Privacy, and Cybersecurity Supply Chain Risk Management Plans for Systems“. Rev.2 löst Rev.1 vom Februar 2006 ab. Zwanzig Jahre zwischen den Revisionen, und die zentrale Änderung ist, dass der Cybersecurity-Supply-Chain-Risikomanagementplan jetzt ein vollwertiger Systemplan ist, neben dem Sicherheitsplan und dem Datenschutzplan.
Das Abstract definiert, wozu alle drei Pläne da sind (die Publikation liegt auf Englisch vor; alle Zitate daraus sind aus dem Original übersetzt):
„Sie beschreiben den Zweck des Systems, den operativen Status der Controls, die zur Erfüllung der Risikomanagementanforderungen ausgewählt und zugewiesen wurden, sowie die Verantwortlichkeiten und das erwartete Verhalten aller Personen, die das System verwalten, unterstützen und darauf zugreifen.“
Abschnitt 2.3 beschreibt den Inhalt des C-SCRM-Plans: Er „beschreibt den Ansatz des Systems für den Umgang mit Supply-Chain-Risiken, die mit Forschung, Entwicklung, Design, Herstellung, Beschaffung, Lieferung, Integration, Betrieb, Wartung und Entsorgung seiner Komponenten oder Dienste verbunden sind“, und „umfasst Lieferanten- und Komponenteninventare“. (Für die Controls selbst verweist §2.3 auf das Control-Overlay in SP 800-161 Appendix A; dieser Verweis steht in 800-18 Rev.2 selbst.)
Bilden Sie den Vorfall jetzt auf diese Struktur ab. Das vergiftete Paket landet in einem Planelement: §3.2, System Component Inventory, das die SBOM als Quelle der Software-Felder benennt, „wie in der Software Bill of Materials (SBOM) angegeben“. Wieder korrekt, und wieder nicht dort, wo der Vorfall stattfand.
Der Ausführungspfad zur Installationszeit gehört ganz woandershin: in die Control Implementation Details („Implementierungsdetails für alle Controls angeben, die dem System zugewiesen sind“) mit einem Wert im Control Implementation Status. Das Statusvokabular von Rev.2 ist unverblümt:
„Planned: Das Control ist ausgewählt, aber die Implementierung hat nicht begonnen. • Partially Implemented: Die geplante Implementierung des Controls hat begonnen, ist aber nicht abgeschlossen ... • Fully Implemented: Der Zustand des Controls ‚wie implementiert‘ ist im operativen System vollständig realisiert.“
Beachten Sie, was in dieser Liste fehlt: Es gibt keinen Status für ein Control, das niemand ausgewählt hat. Ein Control, das nie ausgewählt wurde, hat überhaupt keine Zeile. Das ist der stille Wert der Übung, die Rev.2 beschreibt. Den operativen Status der ausgewählten Controls aufzuschreiben ist der Weg, auf dem ein Team merkt, dass „Paket-Lifecycle-Skripte führen zur Installationszeit beliebigen Code aus“ von Anfang an nie ausgewählt wurde.
Und es ist kein obskurer Pfad. Wer je zugesehen hat, wie node-gyp während npm install etwas kompiliert, weiss, dass Lifecycle-Skripte laufen. Der Pfad ist allgemein bekannt. Das aufgeschriebene Control ist selten. Dieses Missverhältnis ist die ganze Geschichte dieses Vorfalls.
Wie das Aufschreiben aussieht
Microsofts eigene Mitigationsliste enthält das Control, ein Punkt unter neun: „npm install mit --ignore-scripts ausführen, um die automatische Ausführung von postinstall-Hooks während der Installation von Abhängigkeiten zu verhindern.“
Aufgeschrieben ist es langweilig:
shell
# .npmrc — Repo-Root und CI-Basis-Imageignore-scripts=true
Oder pro Aufruf in der CI:
- run: npm ci --ignore-scripts
Der ehrliche Preis: Manche Pakete brauchen ihre Install-Skripte wirklich, typischerweise um nativen Code zu kompilieren oder ein Plattform-Binary zu laden. Mit abgeschalteten Skripten werden sie installiert, aber nie fertig eingerichtet, und der Fehler wandert von der Installationszeit zur Laufzeit. Teams, die dieses Control in der Praxis betreiben, halten eine kurze Allow-List und führen die nötigen Schritte explizit aus, zum Beispiel npm rebuild <pkg> für die Handvoll Pakete, die es sich verdienen. Das ist beobachtete Praxis, keine Compliance-Anforderung von irgendwem.
Aber sehen Sie sich an, was dieser Abwägungssatz ist. „Skripte sind global abgeschaltet; diese N Pakete werden explizit neu gebaut; diese brechen, wenn man es vergisst“ ist genau eine Aussage über den operativen Status, das, wofür die Abschnitte Control Implementation Details und Control Implementation Status eines C-SCRM-Plans da sind. Das Control ist eine Zeile Konfiguration. Der Planeintrag ist das, was die Ausnahmeliste zu etwas Überprüfbarem macht statt zu Stammeswissen.
Version-Pinning gehört in denselben Eintrag. Microsofts Leitfaden benennt die bekannt guten Baselines: mastra 1.13.0 und früher nicht betroffen, @mastra/core 1.42.0 und früher nicht betroffen. Ein Pin ist nur dann ein Control, wenn jemand aufgeschrieben hat, warum diese Version und wer sie wieder anschaut.
Ein Supply-Chain-Plan, welcher Vorlage er auch folgt, läuft auf eine Liste von Ausführungspfaden hinaus, die man bewusst geschlossen oder bewusst akzeptiert hat. „npm install führt beliebigen Code aus“ gehört auf diese Liste, und das letzte Fenster war neunzehn Minuten lang.
devguard hält die Controls, die Sie tatsächlich dokumentiert haben, gegen die Frameworks abgebildet, die sie erwarten: devguard.ch.