AsyncAPI-npm-Kompromittierung: „Pwn Request“ über pull_request_target schleust Miasma-Botnet in generator & specs
Am 14. Juli 2026 haben Angreifer fünf npm-Releases im AsyncAPI-Namespace kompromittiert: @asyncapi/specs@6.11.2 (plus 6.11.2-alpha.1), @asyncapi/generator@3.3.1, @asyncapi/generator-helpers@1.1.1 und @asyncapi/generator-components@0.7.1 — zusammen rund 2,9 Millionen wöchentliche Downloads. Ursache war eine seit dem 17. Mai 2026 ungemergte Fix-PR (#2092) für eine pull_request_target-Schwachstelle im GitHub-Actions-Workflow des generator-Repos: dieser Trigger gewährt Workflows Zugriff auf Repository-Secrets, selbst wenn Code aus einem externen Pull-Request ausgecheckt wird. Ein Angreifer eröffnete 37 Pull-Requests; einer davon exfiltrierte das npm-Publish-Token an einen rentry.co-Dead-Drop. Die daraus resultierende Payload lädt über IPFS eine ~8,2-MB-verschlüsselte Loader-Stage nach, identifiziert sich als „M-RED-TEAM v6.4“ mit Miasma-Branding und baut C2-Verbindungen zu einem eigenen Server, Nostr-Relays und einem Ethereum-Contract auf — mit Beacon-Intervall von rund 30 Sekunden.
TL;DR — 90 Sekunden
- Betroffen?
@asyncapi/specs@6.11.2/6.11.2-alpha.1,@asyncapi/generator@3.3.1,@asyncapi/generator-helpers@1.1.1,@asyncapi/generator-components@0.7.1— veröffentlicht am 14.07.2026 zwischen 07:10 und 08:28 UTC. Zusammen rund 2,9 Mio. wöchentliche Downloads.- Risiko?
Ein Angreifer stahl das npm-Publish-Token über eine seit Mai ungemergte
pull_request_target-Schwachstelle im GitHub-Actions-Workflow desgenerator-Repos. Die eingeschleuste Payload lädt über IPFS eine verschlüsselte Loader-Stage nach, die sich als „M-RED-TEAM v6.4“ mit Miasma-Branding identifiziert: C2-Beacon alle ~30s, Shell-Zugriff viachild_process.exec, Persistenz pro Betriebssystem. Credential-Harvester und Propagationsmodule sind im Code vorhanden, aber aktuell deaktiviert.- Sofortmaßnahme?
Betroffene Pakete auf
6.11.1/3.3.0/1.1.0/0.7.0zurückstufen, aus Lockfiles/Caches/Build-Images entfernen, IoCs prüfen, Secrets von einem sauberen Rechner aus rotieren.- Empfehlung?
Der Payload läuft beim
require()/Import, nicht erst über einen npm-Install-Hook —--ignore-scriptshätte hier nicht geholfen. Wer die Versionen importiert hat, sollte das als aktive Kompromittierung behandeln, nicht nur als Supply-Chain-Risiko.- Kritikalität?
hoch — aktiver, funktionsfähiger Botnet-Payload mit Mio.-Downloads im Blast-Radius, auch wenn Kernfunktionen wie der Credential-Harvester aktuell abgeschaltet sind.
Was ist passiert?
Das asyncapi/generator-Repository auf GitHub nutzte in seiner CI einen Workflow mit pull_request_target-Trigger. Dieser Trigger führt den Workflow im Kontext des Zielrepositorys aus — inklusive Zugriff auf dessen Secrets — selbst wenn der Code aus einem externen, nicht vertrauenswürdigen Pull-Request ausgecheckt wird. Dieses Muster ist als „pwn request“-Schwachstellenklasse seit Jahren bekannt. Eine Fix-PR (#2092) war bereits am 17. Mai 2026 eröffnet worden, blieb aber knapp zwei Monate lang ungemergt liegen.
Am 14. Juli 2026 nutzte ein Angreifer dieses Zeitfenster: Um 05:08 UTC eröffnete er 37 Pull-Requests gegen das Repository. Einer davon, PR #2155, enthielt obfuskiertes JavaScript, das um 05:16 UTC über den verwundbaren Workflow das npm-Publish-Token des Projekts an den Paste-/Dead-Drop-Dienst rentry.co exfiltrierte. Mit diesem Token pushte der Angreifer um 06:58 UTC bösartige Commits in den next-Branch. Um 07:10 UTC erschienen die ersten kompromittierten Paket-Versionen auf npm; zwischen 07:51 und 08:28 UTC folgten elf weitere Commits im Repository asyncapi/spec-json-schemas, aus denen die kompromittierten specs-Versionen veröffentlicht wurden.
Die eingeschleuste Payload sitzt in Modulen, die bereits bei normalen import/require()-Aufrufen ausgeführt werden — nicht in einem npm-Lifecycle-Hook wie postinstall. In @asyncapi/specs startet ein Downloader per spawn() einen detachten Node-Kindprozess, der eine verschlüsselte Payload nachlädt.
Wer ist betroffen?
| Paket | Betroffene Version(en) | Sichere Version |
|---|---|---|
@asyncapi/specs | 6.11.2, 6.11.2-alpha.1 | 6.11.1 |
@asyncapi/generator | 3.3.1 | 3.3.0 |
@asyncapi/generator-helpers | 1.1.1 | 1.1.0 |
@asyncapi/generator-components | 0.7.1 | 0.7.0 |
Betroffen ist jeder, dessen Abhängigkeitsbaum — direkt oder transitiv — eine dieser Versionen auflöst und dabei das jeweilige Modul tatsächlich importiert, insbesondere Build-Pipelines und Doku-/SDK-Generatoren, die AsyncAPI-Spezifikationen verarbeiten. Da der Payload beim Import ausgeführt wird, reicht bloßes Vorhandensein in node_modules ohne Import nicht zwangsläufig für eine Kompromittierung — Build-Tools requiren Module aber häufig automatisch, weshalb dieser Unterschied in der Praxis kaum als verlässlicher Schutz taugt.
Auswirkungen: was die Payload tatsächlich kann
Die Infektionskette läuft in drei Stufen: Ein Downloader (per spawn('node', [...], {detached: true, stdio: 'ignore'})) holt eine verschlüsselte Node.js-Loader-Datei über IPFS (eigene Content-IDs für die specs- und generator-Builds) und schreibt sie als sync.js ins nutzerspezifische NodeJS-Verzeichnis. Der Loader (~8,2 MB obfuskiertes JavaScript) leitet per HKDF-SHA256 einen AES-256-GCM-Schlüssel ab, entschlüsselt einen eingebetteten Vault und wertet rotierten ASCII-Klartext aus. Das Implant identifiziert sich selbst als „M-RED-TEAM v6.4“, generiert ein secp256k1-Schlüsselpaar, richtet Persistenz ein und sendet alle ~30 Sekunden ein Beacon an 85.137.53.71:8080.
Funktional bietet das Implant: Persistenz je Betriebssystem (macOS: Anhängen an Shell-RC-Dateien; Windows: HKCU\Run; Linux: systemd-User-Service), signierte/verschlüsselte C2-Kommandos mit unverschlüsseltem HTTP-Fallback, Shell-Ausführung via child_process.exec() (nur der Befehl killall ist geblacklistet), sandboxed Dateioperationen auf einen /sim-fs-Pfad (Löschen ist nicht implementiert) sowie einen Update-Mechanismus, der alle zwei Minuten über signierte Nostr-Relays und einen unsignierten Ethereum-Contract nach neuen Payload-Versionen pollt.
Im Code vorhanden, aber aktuell deaktiviert: ein Credential-Harvester für Cloud-, SSH-/GPG-, Browser-, CI/CD-, KI-Tool- und Wallet-Zugangsdaten, Propagationsvektoren für npm/PyPI/Cargo/Ruby, eine Mutation-Engine, Evasion-Techniken, KI-Tool-Poisoning und ein Deadman-Switch. Drei Konfigurationsfelder, die wie Sicherheitsmechanismen aussehen, erwiesen sich bei der Analyse als wirkungslos: safeMode: true (der zugehörige Validator wird nie aufgerufen), actualPersist: false (der Code liest stattdessen toggles.persist: true) und canaryPercent: 5 (die zuständige BatchDispatch-Funktion ist nicht implementiert). Da der Update-Mechanismus aktiv ist, könnten deaktivierte Module jederzeit nachträglich aktiviert werden.
Mitigation / Sofortmaßnahmen
Operativer Entscheidungsblock
- Jetzt handeln, wenn … Ihr Lockfile eine der betroffenen Versionen auflöst, unabhängig davon, ob sie derzeit importiert wird.
- Als aktive Kompromittierung behandeln, wenn … die Version nach 07:10 UTC am 14.07.2026 installiert oder gebaut wurde und das Modul importiert wird/wurde.
- Zusätzlich prüfen, wenn … Sie eigene GitHub-Actions-Workflows mit
pull_request_targetbetreiben — dasselbe Muster ermöglichte diesen Angriff.
Schritt 1 — betroffene Versionen finden und zurückstufen
# installierte Versionen prüfen
npm ls @asyncapi/specs @asyncapi/generator @asyncapi/generator-helpers @asyncapi/generator-components
# Lockfile nach betroffenen Versionen durchsuchen
grep -E '"(@asyncapi/(specs|generator|generator-helpers|generator-components))":' package-lock.json | grep -E '6\.11\.2|3\.3\.1|1\.1\.1|0\.7\.1'
# auf bekannt-gute Versionen zurückstufen
npm install @asyncapi/specs@6.11.1 @asyncapi/generator@3.3.0 @asyncapi/generator-helpers@1.1.0 @asyncapi/generator-components@0.7.0
# aus lokalen/CI-Caches und Build-Images entfernen
npm cache clean --force
# Build-Images mit betroffenen Versionen neu bauen, nicht nur neu deployen
Schritt 2 — Artefakte bereinigen
# nach IoC-Dateien suchen (siehe Detection-Abschnitt)
# Persistenz-Service entfernen (Linux)
systemctl --user disable --now miasma-monitor.service 2>/dev/null
rm -f ~/.config/systemd/user/miasma-monitor.service
rm -f ~/.config/.miasma/run/node.lock
Schritt 3 — Secrets von einem sauberen Rechner aus rotieren
# npm-Publish-Token, Source-Control-Zugriff, Cloud-Credentials,
# CI/CD-Secrets, SSH-/Signing-Keys, Browser-Sessions
# Rotation NIEMALS von einem möglicherweise kompromittierten Rechner ausführen
Hinweis: Der Tarball von @asyncapi/specs@6.11.2-alpha.1 kann laut Quellenlage trotz entfernter Registry-Metadaten weiterhin über CDN erreichbar sein — explizit gegenprüfen, nicht nur auf die npm-Registry-Ansicht verlassen.
Detection / Prüfung
Netzwerk-Indikatoren (IoCs)
# C2-Server (Beacon-Port 8080, Upload 8081, Proxy 8091)
85.137.53.71:8080
85.137.53.71:8081
85.137.53.71:8091
# Ethereum-Contract (Chain-ID 1) als Update-Kanal
0x12c37A86a0Ed0beBe5d1d6a43E42f07860eAc710
# Nostr-Relays als Update-Kanal
wss://relay.damus.io
wss://relay.nostr.com/
Host-Artefakte suchen
# Drop-Datei
find ~ -name "sync.js" -path "*nodejs*" 2>/dev/null
# Lock-Datei
ls -la ~/.config/.miasma/run/node.lock 2>/dev/null
# macOS Identity-Datei
ls -la ~/Library/"Application Support"/com.apple.spotlight/index-v2.cache 2>/dev/null
# Linux Identity-Datei
ls -la ~/.cache/mesa_shader_cache/gl_cache.bin 2>/dev/null
# Windows Identity-Datei (PowerShell)
# Test-Path "$env:USERPROFILE\AppData\Roaming\Microsoft\CryptnetUrlCache\Content\msrt.dat"
# Linux Persistenz-Service
systemctl --user status miasma-monitor.service 2>/dev/null
cat ~/.config/systemd/user/miasma-monitor.service 2>/dev/null
Laufende Prozesse und Verbindungen
# detachte Node-Prozesse ohne erkennbaren Elternprozess/TTY
ps -eo pid,ppid,tty,cmd | grep -i node
# aktive Verbindungen zu den C2-Ports
ss -tnp | grep -E "85\.137\.53\.71"
# ungewöhnliche ausgehende Verbindungen zu IPFS-Gateways von CI-/Build-Hosts
Da der Payload beim import/require() läuft, ist eine reine Paket-Versionsprüfung nicht ausreichend — relevant ist, ob das Modul auf einem Host tatsächlich geladen wurde (Build-Logs, CI-Job-Historie, Runtime-Monitoring).
Betreiberempfehlung
Mid-Market
Falls AsyncAPI-Tooling (Dokumentations- oder SDK-Generierung aus Event-API-Spezifikationen, etwa für Kafka-/MQTT-/WebSocket-APIs) in der eigenen Node-Toolchain oder CI eingesetzt wird: Versionen sofort prüfen und bei Treffer als aktive Kompromittierung behandeln, nicht als reines Update-Thema.
Enterprise / CI-Verantwortliche
Unabhängig von der direkten Betroffenheit: die eigenen GitHub-Actions-Workflows auf pull_request_target-Nutzung durchsuchen (grep -rl "pull_request_target" .github/workflows/). Wo dieser Trigger auf Code aus externen Pull-Requests angewendet wird, entweder auf pull_request ohne Secret-Zugriff umstellen oder ein explizites Review-Gate vor der Workflow-Ausführung erzwingen. Offene Fix-PRs für genau diese Klasse von Workflow-Schwachstellen sollten priorisiert werden — zwei Monate Liegezeit waren hier das entscheidende Zeitfenster für den Angreifer.
Entscheidungsblock
Heute handeln, wenn: Ihr Abhängigkeitsbaum eine betroffene Version auflöst und nach 07:10 UTC am 14.07.2026 installiert/gebaut wurde — vollständiger IoC-Sweep plus Credential-Rotation. Beobachten und trotzdem prüfen, wenn: keine betroffene Version im Lockfile gefunden wurde — dann als Anlass nutzen, die eigenen pull_request_target-Workflows zu auditieren, unabhängig vom AsyncAPI-Bezug.
Häufige Fragen zur AsyncAPI-Kompromittierung
Wurde die pull_request_target-Lücke im generator-Repo inzwischen behoben?+
Die zugehörige Fix-PR #2092 war seit dem 17. Mai 2026 offen. Ob sie im Zuge dieses Vorfalls gemerged wurde, geht aus den herangezogenen Quellen nicht eindeutig hervor — vor einer Wiederverwendung des CI-Workflows den aktuellen Stand im Repository selbst prüfen.
Betrifft mich das als TYPO3-/PHP-Entwickler überhaupt?+
Direkt nur, wenn AsyncAPI-Tooling in der eigenen Node-Toolchain zum Einsatz kommt — etwa zur Dokumentation von Event-APIs neben einem TYPO3-/PHP-Backend. Das zugrunde liegende CI-Muster (pull_request_target mit Secret-Zugriff auf Fork-PRs) ist aber unabhängig vom Sprachstack relevant für jedes GitHub-Actions-Setup, auch reine PHP-Projekte.
Was, wenn ich die Pakete nur installiert, aber nie importiert habe?+
Das Risiko ist geringer, aber nicht null — Build-Tools und Doku-Generatoren requiren Abhängigkeiten häufig automatisch im Rahmen ihres normalen Betriebs. Best Practice bleibt Downgrade plus IoC-Check, auch ohne bewussten eigenen Import-Aufruf.
Ist das dieselbe Kampagne wie die früheren Miasma-Wellen auf diesem Blog?+
Unklar. Das Branding (Dateinamen, Pfade, Bezeichner) ist identisch mit früheren Miasma-Vorfällen, die technische Umsetzung unterscheidet sich aber deutlich — Node statt Bun, secp256k1/AES-GCM statt RSA/AES-CBC, HTTP-C2 statt aktiver Propagation. Ob derselbe Akteur, Code-Wiederverwendung oder bloße Namensübernahme vorliegt, lässt sich aus den verfügbaren Quellen nicht abschließend klären.
Ist der Credential-Harvester im Code aktiv ausnutzbar?+
Laut den herangezogenen Analysen aktuell nein — er ist im Code vorhanden, aber über Toggle-Felder deaktiviert. Da das Implant per Nostr- und Ethereum-Polling regelmäßig nach Updates sucht, kann sich das jedoch jederzeit ändern, ohne dass ein neues npm-Release nötig wäre.
Reicht --ignore-scripts, um mich zu schützen?+
Nein. Der Payload läuft beim require()/Import des Moduls in normalem Anwendungscode, nicht über einen npm-Lifecycle-Hook wie postinstall. --ignore-scripts hätte diesen Angriff nicht verhindert.
Fazit
Es ist bereits der dritte Vorfall auf diesem Blog innerhalb weniger Monate, der Miasma-Branding trägt — nach der npm-Wurm-Welle bei @redhat-cloud-services und der Editor-Konfigurations-Welle gegen KI-Coding-Agenten. Die technische Umsetzung unterscheidet sich diesmal deutlich genug, dass eine einfache „gleicher Akteur“-Zuordnung verfrüht wäre — aber das Branding allein ist bereits ein Signal, dass Toolkits und Namenskonventionen in diesem Bereich wiederverwendet oder kopiert werden.
Die eigentliche Lehre liegt aber nicht im Payload, sondern im Einfallstor: pull_request_target mit Secret-Zugriff auf extern eingereichten Code ist eine seit Jahren dokumentierte, unter dem Namen „pwn request“ bekannte Schwachstellenklasse in GitHub Actions — und trotzdem lag hier eine fertige Fix-PR zwei Monate lang ungemergt herum, bis ein Angreifer sie fand. Offene Sicherheits-PRs sind kein Backlog-Item, das warten kann; sie sind ein bekanntes Zeitfenster, das jemand irgendwann nutzt.
Quellen
- Aikido Security — AsyncAPI npm packages backdoored via GitHub Actions (14.07.2026)
- Datadog Security Labs — Compromised AsyncAPI npm packages: inside a CI supply-chain attack
- The Hacker News — Compromised AsyncAPI npm Packages Deliver Multi-Stage Botnet Malware
- Chainguard Unchained — AsyncAPI supply chain compromise: npm packages backdoored via GitHub Actions “pwn request” (Juli 2026)
Ich prüfe Ihre GitHub-Actions-Workflows auf pull_request_target-Fallstricke, downgrade betroffene Pakete und rotiere kompromittierte Secrets.
Audit aller Workflow-Trigger auf Secret-Zugriff bei extern eingereichtem Code, Downgrade betroffener AsyncAPI-Pakete, vollständige Credential-Rotation und IoC-Sweep.
Plattform-Betrieb statt Beratung auf Papier: Ich prüfe, härte und überwache Ihre CI/CD- und Node-Toolchain laufend.
Über den Autor
![[Translate to English:] Foto von Kai Ole Hartwig.](/fileadmin/_processed_/e/9/csm_ole-neu_73323ad80d.jpeg)
Kai Ole Hartwig
Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.