AsyncAPI npm compromise: a “pwn request” via pull_request_target smuggles a Miasma botnet into generator & specs
On 14 July 2026, attackers compromised five npm releases in the AsyncAPI namespace: @asyncapi/specs@6.11.2 (plus 6.11.2-alpha.1), @asyncapi/generator@3.3.1, @asyncapi/generator-helpers@1.1.1, and @asyncapi/generator-components@0.7.1 — roughly 2.9 million weekly downloads combined. The cause was an unmerged fix PR (#2092, open since 17 May 2026) for a pull_request_target vulnerability in the generator repo's GitHub Actions workflow: this trigger grants a workflow access to repository secrets even when it checks out code from an external pull request. An attacker opened 37 pull requests; one exfiltrated the npm publish token to a rentry.co dead drop. The resulting payload fetches a ~8.2 MB encrypted loader stage via IPFS, self-identifies as “M-RED-TEAM v6.4” with Miasma branding, and establishes C2 to a dedicated server, Nostr relays, and an Ethereum contract — beaconing roughly every 30 seconds.
TL;DR — 90 seconds
- Affected?
@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— published 14 Jul 2026 between 07:10 and 08:28 UTC. Roughly 2.9M weekly downloads combined.- Risk?
An attacker stole the npm publish token via a
pull_request_targetflaw in thegeneratorrepo's GitHub Actions workflow that had gone unfixed since May. The injected payload fetches an encrypted loader stage via IPFS that self-identifies as “M-RED-TEAM v6.4” with Miasma branding: C2 beacon every ~30s, shell access viachild_process.exec, per-OS persistence. A credential harvester and propagation modules exist in the code but are currently disabled.- Immediate action?
Downgrade affected packages to
6.11.1/3.3.0/1.1.0/0.7.0, purge from lockfiles/caches/build images, check IoCs, rotate secrets from a clean machine.- Recommendation?
The payload runs on
require()/import, not through an npm install hook —--ignore-scriptswould not have helped here. Anyone who imported the affected versions should treat this as an active compromise, not just a supply-chain risk.- Criticality?
high — an active, functional botnet payload with millions of downloads in the blast radius, even though core features like the credential harvester are currently switched off.
What happened?
The asyncapi/generator GitHub repository used a CI workflow with a pull_request_target trigger. This trigger runs the workflow in the context of the target repository — including access to its secrets — even when the checked-out code comes from an external, untrusted pull request. This pattern has been a known vulnerability class, dubbed “pwn request,” for years. A fix PR (#2092) had already been opened on 17 May 2026, but sat unmerged for almost two months.
On 14 July 2026, an attacker used that window: at 05:08 UTC they opened 37 pull requests against the repository. One of them, PR #2155, contained obfuscated JavaScript that, at 05:16 UTC, exfiltrated the project's npm publish token via the vulnerable workflow to the paste/dead-drop service rentry.co. Using that token, the attacker pushed malicious commits to the next branch at 06:58 UTC. The first compromised package versions appeared on npm at 07:10 UTC; between 07:51 and 08:28 UTC, eleven further commits followed in the asyncapi/spec-json-schemas repository, from which the compromised specs versions were published.
The injected payload sits in modules that already execute on normal import/require() calls — not in an npm lifecycle hook like postinstall. In @asyncapi/specs, a downloader spawns a detached Node child process that fetches an encrypted payload.
Who is affected?
| Package | Affected version(s) | Safe 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 |
Anyone whose dependency tree — direct or transitive — resolves one of these versions and actually imports the module is affected, in particular build pipelines and doc/SDK generators that process AsyncAPI specifications. Because the payload executes on import, merely having it in node_modules without importing it doesn't necessarily mean compromise — but build tools frequently import modules automatically, so in practice that distinction offers little reliable protection.
Impact: what the payload can actually do
The infection chain runs in three stages. A downloader (via spawn('node', [...], {detached: true, stdio: 'ignore'})) fetches an encrypted Node.js loader file over IPFS (dedicated content IDs for the specs and generator builds) and writes it as sync.js into the per-user Node.js directory. The loader (~8.2 MB of obfuscated JavaScript) derives an AES-256-GCM key via HKDF-SHA256, decrypts an embedded vault, and evaluates rotated ASCII plaintext. The implant self-identifies as “M-RED-TEAM v6.4,” generates a secp256k1 keypair, establishes persistence, and beacons to 85.137.53.71:8080 roughly every 30 seconds.
Functionally the implant offers: per-OS persistence (macOS: appends to shell RC files; Windows: HKCU\Run; Linux: a systemd user service), signed/encrypted C2 commands with an unencrypted HTTP fallback, shell execution via child_process.exec() (only the killall command is blacklisted), sandboxed file operations scoped to a /sim-fs path (delete is not implemented), and an update mechanism that polls signed Nostr relays and an unsigned Ethereum contract every two minutes for new payload versions.
Present in the code but currently disabled: a credential harvester targeting cloud, SSH/GPG, browser, CI/CD, AI-tool, and wallet credentials; propagation vectors for npm/PyPI/Cargo/Ruby; a mutation engine; evasion techniques; AI-tool poisoning; and a deadman switch. Three configuration fields that look like safety mechanisms turned out to be inert under analysis: safeMode: true (the associated validator is never called), actualPersist: false (the code reads toggles.persist: true instead), and canaryPercent: 5 (the responsible BatchDispatch function isn't implemented). Since the update mechanism is active, disabled modules could be switched on remotely at any time.
Mitigation / immediate steps
Operational decision block
- Act now if … your lockfile resolves one of the affected versions, regardless of whether it's currently imported.
- Treat as an active compromise if … the version was installed or built after 07:10 UTC on 14 Jul 2026 and the module is or was imported.
- Also review if … you run your own GitHub Actions workflows using
pull_request_target— the same pattern enabled this attack.
Step 1 — find and downgrade affected versions
# check installed versions
npm ls @asyncapi/specs @asyncapi/generator @asyncapi/generator-helpers @asyncapi/generator-components
# search the lockfile for affected versions
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'
# downgrade to known-good versions
npm install @asyncapi/specs@6.11.1 @asyncapi/generator@3.3.0 @asyncapi/generator-helpers@1.1.0 @asyncapi/generator-components@0.7.0
# purge from local/CI caches and build images
npm cache clean --force
# rebuild build images with affected versions, don't just redeploy
Step 2 — clean up artifacts
# search for IoC files (see Detection section)
# remove the persistence service (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
Step 3 — rotate secrets from a clean machine
# npm publish token, source control access, cloud credentials,
# CI/CD secrets, SSH/signing keys, browser sessions
# NEVER run rotation from a machine that may itself be compromised
Note: according to available sources, the @asyncapi/specs@6.11.2-alpha.1 tarball may still be reachable via CDN despite removed registry metadata — verify explicitly rather than relying on the npm registry view alone.
Detection / verification
Network indicators (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) used as an update channel
0x12c37A86a0Ed0beBe5d1d6a43E42f07860eAc710
# Nostr relays used as an update channel
wss://relay.damus.io
wss://relay.nostr.com/
Search for host artifacts
# drop file
find ~ -name "sync.js" -path "*nodejs*" 2>/dev/null
# lock file
ls -la ~/.config/.miasma/run/node.lock 2>/dev/null
# macOS identity file
ls -la ~/Library/"Application Support"/com.apple.spotlight/index-v2.cache 2>/dev/null
# Linux identity file
ls -la ~/.cache/mesa_shader_cache/gl_cache.bin 2>/dev/null
# Windows identity file (PowerShell)
# Test-Path "$env:USERPROFILE\AppData\Roaming\Microsoft\CryptnetUrlCache\Content\msrt.dat"
# Linux persistence service
systemctl --user status miasma-monitor.service 2>/dev/null
cat ~/.config/systemd/user/miasma-monitor.service 2>/dev/null
Running processes and connections
# detached Node processes with no obvious parent/TTY
ps -eo pid,ppid,tty,cmd | grep -i node
# active connections to the C2 ports
ss -tnp | grep -E "85\.137\.53\.71"
# unusual outbound connections to IPFS gateways from CI/build hosts
Because the payload runs on import/require(), a package-version check alone is not sufficient — what matters is whether the module was actually loaded on a given host (build logs, CI job history, runtime monitoring).
Operator guidance
Mid-market
If AsyncAPI tooling (documentation or SDK generation from event-API specs, e.g. for Kafka/MQTT/WebSocket APIs) is used anywhere in your Node toolchain or CI: check versions immediately and treat a hit as an active compromise, not just an update task.
Enterprise / CI owners
Regardless of direct exposure: search your own GitHub Actions workflows for pull_request_target usage (grep -rl "pull_request_target" .github/workflows/). Where the trigger applies to code from external pull requests, either switch to pull_request without secret access, or enforce an explicit review gate before the workflow runs. Prioritize open fix PRs for this exact class of workflow vulnerability — two months of sitting unmerged was the decisive window here.
Decision block
Act today if: your dependency tree resolves an affected version and it was installed/built after 07:10 UTC on 14 Jul 2026 — full IoC sweep plus credential rotation. Monitor and still check if: no affected version was found in your lockfile — use it as a reason to audit your own pull_request_target workflows regardless of the AsyncAPI connection.
Frequently asked questions about the AsyncAPI compromise
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.
Conclusion
This is already the third incident on this blog within a few months to carry Miasma branding — after the npm worm wave at @redhat-cloud-services and the editor-config wave targeting AI coding agents. The technical implementation differs enough this time that a simple “same actor” attribution would be premature — but the branding alone is already a signal that toolkits and naming conventions in this space are being reused or copied.
The real lesson isn't in the payload, though, it's in the entry point: pull_request_target with secret access on externally submitted code has been a documented GitHub Actions vulnerability class known as “pwn request” for years — and yet a ready fix PR sat unmerged for two months until an attacker found it. Open security PRs aren't a backlog item that can wait; they're a known window that someone eventually uses.
Sources
- Aikido Security — AsyncAPI npm packages backdoored via GitHub Actions (14 Jul 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” (July 2026)
I review your GitHub Actions workflows for pull_request_target pitfalls, downgrade affected packages, and rotate compromised secrets.
Audit of all workflow triggers for secret access on externally submitted code, downgrade of affected AsyncAPI packages, full credential rotation, and IoC sweep.
Platform operations instead of advice on paper: I continuously check, harden, and monitor your CI/CD and Node toolchain.
About the author
![[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.