Feed
CriticalPublished 14 Jul 20264 packages · 5 versions

AsyncAPI npm org compromised — 4 packages / 5 versions ship Miasma botnet loader via GitHub Actions `pull_request_target` pwn request

Summary

On 2026-07-14 an attacker exploited a pull_request_target misconfiguration in asyncapi/generator to steal a highly-privileged GitHub PAT, then published five malicious @asyncapi/* releases through the project's own trusted GitHub Actions release pipeline — valid npm OIDC provenance and all. The payload fires on require(), spawns a detached Node process, fetches an 8.25 MB IPFS loader (Miasma), and installs a 3.09 MB implant with six independent C2 channels (HTTP, Nostr, IPFS, BitTorrent DHT, libp2p GossipSub, Ethereum smart contract). Combined weekly downloads: ~2.9M.

ci-cd-compromisecredential-theftinfostealerobfuscationaccount-takeover
Threat actor
Miasma
Detected by
Socket · Datadog Security Labs · StepSecurity · Aikido · Wiz · OX Security · Chainguard · SafeDep
Also known as
AsyncAPI Miasma compromise · AsyncAPI pwn request compromise · M-Red-Team AsyncAPI
Ecosystems
npm
Packages tracked
4

What happened

On 2026-07-14 at approximately 05:03 UTC an attacker (GitHub user elzotebo999, subsequently deleted and now displayed as "ghost") opened the first of 37 spam pull requests against asyncapi/generator. At 05:08 UTC the attacker authored malicious commit 47be388; at 05:11 UTC a pull_request_target-triggered Docs Preview (Netlify) workflow ran with the repository's privileged GITHUB_TOKEN and organization-scoped PAT in scope. By 05:17 UTC the workflow had exfiltrated the PAT to a Rentry paste. Between 05:40 and 07:05 UTC a series of malicious commits were force-pushed to the repository's next branch. At 07:10:42 UTC the project's legitimate release workflow triggered on the poisoned next branch and published the first malicious tarball — the compromise had, from PR-open to malicious-release-live, taken about two hours and seven minutes.

Package + version manifest

Exact publish timestamps confirmed against the public npm registry time map:

| Package | Malicious version | Publish (UTC) | Clean predecessor | |---|---|---|---| | @asyncapi/generator-helpers | 1.1.1 | 2026-07-14 07:10:42 | 1.1.0 | | @asyncapi/generator-components | 0.7.1 | 2026-07-14 07:10:44 | 0.7.0 | | @asyncapi/generator | 3.3.1 | 2026-07-14 07:10:48 | 3.3.0 | | @asyncapi/specs | 6.11.2-alpha.1 | 2026-07-14 08:06:20 | 6.11.1 | | @asyncapi/specs | 6.11.2 | 2026-07-14 08:30:09 | 6.11.1 |

@asyncapi/specs accounts for approximately 2.7M of the ~2.9M combined weekly downloads across the four packages. The alpha.1 publish is unusual for AsyncAPI's stable-release cadence and appears to be either a probe or a fallback for lockfiles pinned to alpha channels — the same malicious payload shipped in both alpha.1 and the stable 6.11.2.

Attack chain — pwn request against pull_request_target

Datadog Security Labs' reconstruction and StepSecurity's CI-side analysis both attribute the entry to a pull_request_target "pwn request" vulnerability in the Docs Preview (Netlify) workflow. The workflow was configured to check out the PR HEAD (attacker-controlled code) while running with the repository's privileged secrets in scope. PR #2155 — one of 37 spam PRs in the burst — modified files under apps/generator/docs/ in a way that caused the workflow to execute attacker-controlled JavaScript with the repository's GITHUB_TOKEN and a highly-privileged organization-scoped PAT accessible.

The attacker exfiltrated the stolen PAT to a Rentry paste URL and then used the PAT out-of-band to force-push malicious commits directly to the next branch (bypassing branch protection because the PAT had admin-level scope). When the project's legitimate release workflow triggered on that branch, it published tarballs derived from the attacker's code — with valid npm OIDC provenance attestations, because the release workflow itself is the trusted publisher. npm audit signatures PASSES on the malicious tarballs.

The payload — Miasma botnet loader

The malicious code is embedded in normal runtime modules (not lifecycle scripts) and executes on require() or import. On load it:

  1. Uses child_process.spawn to launch a detached node -e process (.unref()'d, hidden from installer process tree).
  2. Fetches an encrypted 8.25 MB JavaScript loader from IPFS CID QmQobZSp1wRPrpSEQ56qnyq7ecZh5Bg5k1fnjt4SUwwHb9, saves it locally as sync.js.
  3. Decrypts a 3.09 MB implant (744 bundled modules) using an attacker public key (0432fa4ba871877d94081fe83323fa24dfa1491e9de8725cbab7b734de9e9be3b233ef6742fd6264437c9532223d687b05fa540b70af6a516b8539af84d0eeb48e — secp256k1).
  4. Establishes six parallel C2 channels: HTTP to 85.137.53.71:8080/8081/8091, Nostr relay, IPFS-based command retrieval, BitTorrent DHT rendezvous, libp2p GossipSub P2P mesh, and Ethereum contract callbacks at 0x12c37A86a0Ed0beBe5d1d6a43E42f07860eAc710.
  5. Harvests browser passwords/cookies (Chrome, Brave, Firefox, Edge), SSH keys, npm/GitHub tokens, AWS/GCP credentials, macOS Keychain, Slack/Discord/Telegram tokens, crypto wallets.
  6. Establishes persistence by writing malicious task definitions into .vscode/tasks.json and .claude/settings.json in the victim's home directory — the implant re-launches whenever the developer next opens VS Code or Claude Code.

The payload family is the same Miasma botnet loader documented in Aikido's Miasma variant writeup and previously observed in the Miasma / redhat-cloud-services June cluster. The AsyncAPI variant is the largest-blast-radius Miasma delivery to date.

Remediation status

All five malicious versions have been unpublished from the public npm registry (not merely deprecated — the tarballs no longer resolve). The current dist-tag latest for each package points at the clean predecessor. However:

  • Private registry mirrors (Verdaccio, Artifactory, Nexus, Cloudsmith) will keep serving the cached tarballs until an operator manually evicts them.
  • Build images baked between 2026-07-14 07:10 UTC and the unpublish still contain the malicious code and remain live until re-baked.
  • The AsyncAPI project has published an official incident report and a post-mortem committing to migrate away from pull_request_target on all publish-adjacent workflows.

Context — three high-profile GitHub-Actions supply-chain compromises in eight days

This is the third public npm supply chain incident in eight days rooted in GitHub Actions / repository-side compromise:

The pattern is a shift away from typosquats toward compromise of legitimate high-download packages via CI/CD or maintainer-account weaknesses — an arc that Palo Alto Networks' npm threat landscape monitoring has been flagging since Q2 2026.

Affected packages (4)

  • npm@asyncapi/generator
    3.3.1
  • npm@asyncapi/generator-components
    0.7.1
  • npm@asyncapi/generator-helpers
    1.1.1
  • npm@asyncapi/specs
    6.11.2-alpha.16.11.2

Impact

  • Any Node.js process that require()d or imported @asyncapi/generator@3.3.1, @asyncapi/generator-components@0.7.1, @asyncapi/generator-helpers@1.1.1, @asyncapi/specs@6.11.2, or @asyncapi/specs@6.11.2-alpha.1 during the exposure window (2026-07-14 07:10 UTC → unpublish, roughly 24–36 hours) must be treated as fully compromised — the payload runs at import time, not just install time, so --ignore-scripts provides zero mitigation
  • Massive transitive blast radius: @asyncapi/specs alone carries ~2.7M weekly downloads and is a direct dependency of AsyncAPI CLI, Kong Insomnia, Postman workspaces, and many enterprise API-tooling stacks; combined weekly downloads across the four packages exceeds 2.9M. Any project that pulled the malicious version through a transitive AsyncAPI dependency inherited the full Miasma implant
  • The Miasma loader harvests: browser saved passwords and cookies (Chrome, Brave, Firefox, Edge), SSH keys, npm and GitHub tokens, AWS credentials, macOS Keychain, and cryptocurrency wallet data. Persistence is achieved through injection into .vscode/tasks.json and .claude/settings.json on the victim's workstation — meaning the implant survives reboot AND continues to fire whenever the developer opens VS Code or Claude Code
  • The implant is a command framework with six independent C2 channels (HTTP, Nostr relay, IPFS, BitTorrent DHT, libp2p GossipSub P2P mesh, Ethereum smart contract callback at 0x12c37A86a0Ed0beBe5d1d6a43E42f07860eAc710). Blocking any single egress channel does not neutralize the implant; six channels means single-egress firewalls will not stop C2
  • The malicious versions were published through the AsyncAPI project's own legitimate GitHub Actions release workflow with valid npm OIDC provenance attestations — meaning npm audit signatures PASSES on these tarballs. Provenance-based trust checks that expect "if OIDC-signed then benign" will not flag these publishes
  • Reveals a class of pull_request_target GitHub Actions vulnerability the security community calls a "pwn request": a PR that touches a specific path (apps/generator/docs/) triggered a Docs Preview (Netlify) workflow running with the repository's privileged GITHUB_TOKEN and organization-scoped PAT in scope — the attacker chained a run of the workflow to exfiltrate the PAT to a Rentry paste and then used the PAT to force-push malicious commits to protected branches

What to do

  1. 1Grep every lockfile (package-lock.json, yarn.lock, pnpm-lock.yaml, deno.lock) for the five malicious version pins: @asyncapi/generator@3.3.1, @asyncapi/generator-components@0.7.1, @asyncapi/generator-helpers@1.1.1, @asyncapi/specs@6.11.2, @asyncapi/specs@6.11.2-alpha.1
  2. 2Pin back to the clean predecessors: @asyncapi/generator@3.3.0, @asyncapi/generator-components@0.7.0, @asyncapi/generator-helpers@1.1.0, @asyncapi/specs@6.11.1. All five malicious versions are now UNPUBLISHED from the public npm registry — but a private mirror (Verdaccio, Artifactory, Nexus, Cloudsmith) that cached them will keep serving them until manually purged
  3. 3If any host installed or required a malicious version: rotate ALL credentials that the affected process (or a developer using the same workstation) could reach — npm tokens, GitHub PATs and GitHub CLI config, SSH keys, AWS/GCP/Azure cloud credentials, browser-saved passwords, Slack/Discord/Telegram tokens, crypto wallets. Miasma implants persist through VS Code and Claude Code settings — inspect .vscode/tasks.json and .claude/settings.json on the affected workstations for injected command / runTask / postCommand entries
  4. 4Block outbound traffic to the known IOCs while incident-responding: primary HTTP C2 at 85.137.53.71 (ports 8080, 8081, 8091), IPFS gateway fetch of QmQobZSp1wRPrpSEQ56qnyq7ecZh5Bg5k1fnjt4SUwwHb9, and Ethereum contract callbacks to 0x12c37A86a0Ed0beBe5d1d6a43E42f07860eAc710. Note that the implant's six-channel design means blocking these will not stop all C2, only make the operator's job harder
  5. 5Audit every GitHub Actions workflow in your organization that uses pull_request_target — the OWASP CI/CD Security Cheat Sheet and Chainguard's pwn request writeup both flag pull_request_target triggers that check out the PR HEAD as a top-tier misconfiguration. Migrate to plain pull_request where possible; when pull_request_target is required, do NOT check out the PR HEAD, do NOT expose repository secrets, and gate the workflow behind if: github.event.pull_request.head.repo.full_name == github.repository
  6. 6If your organization publishes npm packages through GitHub Actions with npm OIDC trust, review the workflow scoping: the malicious AsyncAPI tarballs shipped with valid provenance because the workflow itself was hijacked. Consider requiring environment: gating with manual approval on all publish jobs, and rotate the OIDC trust relationship if any actor-authored commit merged in the last 30 days
  7. 7Assume the Miasma operator has multi-week persistence on any affected developer workstation. Full re-image (not just npm uninstall) is the safe reset for a workstation that installed a malicious tarball

References

npm-2026-07-14-asyncapi-miasma-actions-pwn-request