Contents
ToggleExecutive Summary
On July 14, 2026, an attacker compromised two AsyncAPI GitHub repositories and used each project’s own release pipeline to publish four malicious npm packages (five versions total) under the @asyncapi namespace. The entry point was a “pwn request”: a GitHub Actions workflow that triggered on pull_request_target but checked out and executed the pull request’s untrusted code, exposing repository secrets to an attacker-controlled branch. The attacker used the noise of 37 near-simultaneous pull requests as camouflage while a single PR harvested a highly privileged asyncapi-bot Personal Access Token.
With that token, the attacker pushed commits under a placeholder git identity to the next branch of asyncapi/generator and the master branch of asyncapi/spec-json-schemas, letting each repository’s legitimate release workflow publish the packages through npm’s GitHub OIDC trusted-publisher integration. The published packages carry valid SLSA provenance attestations. That provenance proves only that the authorized workflow produced the artifacts, not that the triggering commits were legitimate. The combined packages see over three million downloads per week, and the payload fires on require(), not on install, so –ignore-scripts provides no protection.
The payload is a three-stage dropper terminating in the Miasma RAT: a 3.08 MB modular framework with credential harvesting across 130+ target types, six independent command-and-control channels, LAN lateral movement, AI coding-assistant poisoning, and multi-platform persistence. No CVE has been assigned. During the roughly four-hour exposure window, CVE-based SCA tooling had nothing to match against: an active, high-download compromise with no identifier for a scanner to key on. This is the zero-CVE gap.
TL;DR for engineering teams
What it is: A CI/CD pipeline compromise of the @asyncapi npm namespace delivering the Miasma RAT (staged dropper, C2, credential theft). No CVE, no CVSS. Root cause is a GitHub Actions pwn request (pull_request_target executing untrusted PR code with secret access).
Where it bites: @asyncapi/generator@3.3.1, @asyncapi/generator-helpers@1.1.1, @asyncapi/generator-components@0.7.1, @asyncapi/specs@6.11.2 and @asyncapi/specs@6.11.2-alpha.1. Also pulled transitively: @asyncapi/parser declares @asyncapi/specs: ^6.11.1, and that range resolves the malicious 6.11.2.
Why it matters: Payload executes on require() during normal generator/parser use, not at npm install, so install-time script blocking does not help. Packages carry valid SLSA provenance, so provenance checks pass. Over 3M combined weekly downloads.
Patch status: All five malicious versions unpublished from npm as of 11:18 UTC, July 14, 2026. Latest dist-tags now resolve to clean versions. Fresh installs are safe. Lock files and installations captured during the exposure window are not.
Immediate action: Regenerate lock files, pin the last-safe versions, hunt for the dropped sync.js under OS-specific NodeJS directories, and rotate every credential present on affected machines and CI runners.
Vulnerability Overview
| Field | Value |
|---|---|
| Vendor | AsyncAPI Initiative |
| Product | AsyncAPI tooling (@asyncapi npm namespace) |
| Affected components | generator, generator-helpers, generator-components, specs |
| Vulnerability Type | Supply chain compromise via CI/CD pwn request; RAT delivery |
| Root cause class | CWE-269 (Improper Privilege Management) / GitHub Actions pull_request_target misuse |
| Patch Available | Yes: malicious versions unpublished; clean dist-tags restored |
| Active Exploitation | Confirmed (packages published and live for ~4 hours) |
| Attribution | Not definitive. Miasma-framework code markers present; dead-drop naming matches the prt-scan campaign |
Miasma “Mini Shai-Hulud” Supply Chain Campaign
Campaign ID: MIASMA_MINI_SHAIHULUD_2026
Family: Miasma (Shai-Hulud derivative, TeamPCP-linked tradecraft)
Ecosystems: npm, PyPI, Go
First seen: 2026-06-01
Last activity: 2026-07-04
Zero-CVE
Summary
Miasma is the latest Mini Shai-Hulud supply chain wave. First observed in compromised Red Hat Cloud Services packages, then spread to the Vapi server SDK (@vapi-ai) and the ai-sdk-ollama, autotel, awaitly, executable-stories, node-env-resolver, and wrangler-deploy ecosystems. It turns package installation into credential theft and self-propagation using TeamPCP-linked tradecraft. Name derives from actor-authored repo descriptions (“Miasma: The Spreading Blight”), matching the worm’s self-spreading behavior and the crew’s Greek/Dune-themed variant naming.
Tradecraft
- Obfuscated install-time payloads (fires before app code runs)
- Bun-based staging — payload executes under Bun to evade Node-focused EDR/SCA/runtime monitoring
- GitHub Actions runner secret extraction
- npm + cloud (AWS/GCP/Azure) credential harvesting
- Encrypted GitHub dead-drop exfiltration (GitHub-native C2, no dedicated infra)
- Automated downstream package compromise → propagation
Scale
| Metric | Value |
| Affected package artifacts | 527 |
| Unique packages | 177 |
| Ecosystems | npm, PyPI, Go |
| Artifacts (last 7 days) | 0 (vs prior 7d) |
| CVE / GHSA / OSV | None assigned |
Ecosystem split (artifacts / unique packages): npm 457 / 134 · PyPI 62 / 38 · Go 8 / 5.
Concentration: the compromise is namespace-clustered, not scattered. @redhat-cloud-services alone accounts for 95 artifacts — the initial and largest blast radius — followed by @immobiliarelabs (22), @ethlete (9), @forjacms (8), and @vapi-ai (4). The 379 no-namespace artifacts are unscoped npm/PyPI/Go packages (the named victim SDKs sit here).
Two distinct waves by detection date: an opening burst Jun 1–8 (~473 artifacts, peaking at 292 on Jun 4) centered on Red Hat Cloud Services, then a second wave Jun 24–27 (~50 artifacts) that pulled in the Vapi SDK and the additional ecosystems. Activity is quiet in the trailing 7 days (0 new artifacts), but the 177-package footprint stays installable in any lockfile pinned during the active window.
Technical Anatomy


Root cause

The failure is in asyncapi/generator’s GitHub Actions configuration. A workflow triggered on pull_request_target but then checked out the head of the pull request (attacker-controlled code) rather than the base branch. pull_request_target runs in the context of the base repository with access to repository secrets. When a workflow in that context checks out and executes code from a fork’s pull request, that untrusted code inherits access to those secrets. This is the “pwn request” pattern.
The risk was known internally before the attack. On April 29, a contributor opened PR/issue #2155 investigating the exposure with a proof-of-concept payload. On May 17, they proposed a fix that split the workflow into two jobs to isolate secret access from untrusted code. That fix was still open and unmerged 58 days later when the attack landed. The specific vulnerable workflow was the Netlify preview job, manual-netlify-preview.yml (linked at the pre-fix commit; the malicious configuration has since been removed).

Exploit path
- Entry point. At 05:08 UTC the attacker opened PR #2155-range activity as part of a burst of 37 pull requests, most adding a decoy charity donation page. One PR carried a markdown file containing obfuscated JavaScript hidden after roughly 1,000 bytes of whitespace. The vulnerable pull_request_target workflow checked out and ran that PR code.
- Vulnerability trigger. Running in the base-repository context, the attacker’s code scanned the GitHub Actions runner environment for secrets and exfiltrated them to a dead-drop URL on the rentry.co pastebin. Automated review flagged the obfuscation and the PR was never merged, but the workflow had already completed at 05:16 UTC and the secrets were retrieved. The stolen credential was a PAT belonging to asyncapi-bot, a service account with organization-wide access.
- Execution impact. With the PAT, the attacker pushed a malicious commit to the next branch of asyncapi/generator at 06:58 UTC (commit 3eab3ec9, unsigned, authored as “Your Name” <you@example.com>, GitHub login invalid-email-address). The release-with-changesets.yml workflow fired twelve seconds later and published the three generator packages via npm OIDC at 07:10 UTC. The attacker then pivoted to asyncapi/spec-json-schemas, pushing commits to master between 07:51 and 08:28 UTC to trigger if-nodejs-release.yml (which fires on fix:/feat: prefixed commits), publishing @asyncapi/specs@6.11.2-alpha.1 at 08:06 UTC and @asyncapi/specs@6.11.2 at 08:30 UTC.
Campaign phases

Phase 1: Initial foothold (05:08–05:16 UTC). PR-based secret exfiltration via the pwn request; asyncapi-bot PAT stolen to rentry.co.
Phase 2: Expansion (06:58–07:10 UTC). Direct commit to asyncapi/generator@next; three packages published with valid OIDC provenance.
Phase 3: Ecosystem targeting (07:51–08:30 UTC). Pivot to spec-json-schemas; two @asyncapi/specs versions published. @asyncapi/specs is a direct dependency of the AsyncAPI parser, so the ^6.11.1 range pulled the malicious build transitively across the tooling ecosystem.
Phase 4: Persistence (post-install, per host). On each machine that loaded a poisoned module, the dropper established persistence and opened C2 channels (detailed below).
Payload: three-stage dropper into Miasma RAT
The dropper is injected as a ~7.7 KB obfuscator.io-obfuscated blob on the first line of legitimate source files, padded with leading whitespace to push it off-screen in editors and diff views. There is no preinstall/postinstall hook in any package.json. The code fires when the poisoned module is require()d during normal use, which is why install-time protections are irrelevant here.
Injected files by package: apps/generator/lib/templates/config/validator.js (generator), packages/helpers/src/utils.js (generator-helpers), packages/components/src/utils/ErrorHandling.js (generator-components), and the ESM-compiled index.js (specs).
Stage 1 fires on require(). It spawns a detached, output-suppressed, window-hidden node -e child process and calls process.exit(0) so the host process exits cleanly with no visible error.
Stage 2 downloads sync.js from a public IPFS gateway and writes it to an OS-specific hidden directory disguised as a Node.js runtime folder:
| OS | Drop path |
|---|---|
| Linux | ~/.local/share/NodeJS/sync.js |
| macOS | ~/Library/Application Support/NodeJS/sync.js |
| Windows | %LOCALAPPDATA%\NodeJS\sync.js |
Stage 3 is sync.js: a 3.08 MB bundled Node.js application self-identified as Miasma v3. Static reconstruction shows AES-256-GCM decryption via HKDF-SHA256, with a baked configuration blob revealing the full C2 infrastructure. Capabilities include six independent C2 channels (HTTP REST, Nostr relay, IPFS, BitTorrent DHT, libp2p GossipSub mesh, Ethereum blockchain dead-drop) coordinated by a failover orchestrator, credential harvesting across 130+ target types, an AI tool poisoner targeting Claude Code, GitHub Copilot, and Cursor, LAN lateral movement via subnet scan and mDNS, a metamorphic mutation engine, worm-propagation modules (disabled in this deployment via propagate.npm: false), multi-platform persistence, and a deadman self-wipe. The baked config names the campaign miasma-train-p1 with a CANARY deployment strategy.
A note on attribution: the payload carries Miasma markers (obfuscator config, miasma-monitor.service, giteaPackagesOrg: miasma-test-org, Miasma-branded Nostr tags, and a M-RED-TEAM v6.4 self-identifier), but the rentry.co dead-drop slug matches naming from the prt-scan campaign, which has not previously been linked to Miasma. Beyond the shared markers and obfuscation method, this payload is a full Trojan command framework rather than the worm seen in prior Miasma waves. No definitive attribution is being made.



http://phxintel.security/malware.html?purl=pkg%3Anpm%2F%40asyncapi%2Fgenerator
Protect yourself with the latest threat intelligence, get access to PHOENIX BLUE Today
Affected Versions
| Package | Vulnerable Versions | Fixed Version | Notes |
|---|---|---|---|
| @asyncapi/generator | 3.3.1 | 3.3.0 | Dropper in validator.js; published via next branch |
| @asyncapi/generator-helpers | 1.1.1 | 1.1.0 | Dropper in utils.js |
| @asyncapi/generator-components | 0.7.1 | 1.0.0 | Dropper in ErrorHandling.js; latest set to pre-existing clean 1.0.0 |
| @asyncapi/specs | 6.11.2 | 6.11.1 | Dropper in ESM index.js; pulled transitively by parser via ^6.11.1 |
| @asyncapi/specs | 6.11.2-alpha.1 | 6.11.1 | Pre-release variant, same payload |
Transitive exposure: @asyncapi/parser (roughly 970K downloads/week) declares @asyncapi/specs: ^6.11.1. That semver range resolves the malicious 6.11.2, so projects that never listed @asyncapi/specs directly could still have pulled it.
Exposure Analysis
| Environment | Risk Level | Reason |
|---|---|---|
| CI/CD pipelines | Critical | Generator and parser run in build and docs jobs; require()-time execution on runners with cloud and registry credentials |
| Developer environments | Critical | Credential harvesting targets SSH keys, npm/GitHub/PyPI tokens, cloud creds, browser stores, macOS Keychain; AI-assistant poisoning targets developer tooling |
| Cloud workloads | High | Harvested AWS/Azure/GCP and kube/config credentials enable lateral movement |
| Internet-exposed services | Medium | Not a network-reachable server flaw; exposure is via build/runtime import of poisoned modules |
Scale: the affected packages see over 3M combined weekly downloads; @asyncapi/specs@6.11.2 alone is cited at 2.66M/week, with the parser adding transitive reach. Exposure is bounded by the publish window (07:10–11:18 UTC on July 14, 2026) rather than by the total install base, since only versions published in that window were malicious.
Real-World Impact
The realistic attack scenarios follow directly from the payload’s confirmed capabilities. On a CI runner, the dropper fires when a build step imports the generator or parser, harvesting the GITHUB_TOKEN, NPM_TOKEN, and any cloud credentials in the runner environment, then exfiltrating over one of six C2 channels. On a developer workstation, it reads browser Login Data and cookies (Chrome, Brave, Edge, Firefox), SSH private keys, ~/.npmrc, ~/.aws/credentials, ~/.config/gh, kube/config, Docker credentials, and macOS Keychain entries.
Two capabilities raise the severity beyond a standard credential stealer. The ai-tool-poisoner.js module can inject instructions into AI coding-assistant sessions, meaning any AI-assisted code produced on an affected workstation during the exposure window should be reviewed independently. The LAN discovery and subnet-scan modules mean a single infected developer machine can attempt to spread across the local network rather than staying contained to the initial host.
StepSecurity confirmed the C2 behavior by running the compromised packages in an isolated, Harden-Runner-monitored GitHub Actions job, observing outbound connection attempts to 85.137.53.71 and the BitTorrent DHT bootstrap nodes directly rather than relying on third-party reporting.
Detection Guidance
Log and network indicators
Look for outbound connections from build steps or developer machines that only expect registry traffic: connections to ipfs.io, to 85.137.53.71 on any port (8080 commands, 8081 upload, 8091 proxy management), to the BitTorrent DHT bootstrap nodes router.bittorrent.com:6881 and dht.transmissionbt.com:6881, and to the Nostr relays relay.damus.io and relay.nostr.com. On the host, watch for a detached node process spawned with hidden or ignored I/O and for the write of an executable script into a hidden NodeJS directory under the user’s home folder.
Indicators of compromise
| Type | Indicator | Detail |
|---|---|---|
| SHA1 | 22bf76fe317ea6769bd38619bd440e42d119bd6b | malicious validator.js (generator) |
| SHA1 | a7e18d96efd3cdb127ef4cdcad9e3ad26c482bf2 | malicious utils.js (generator-helpers) |
| SHA1 | 9890950adcbc2478e7a080234f053214adbad44e | malicious ErrorHandling.js (generator-components) |
| SHA1 | c70e105e212ff3c1daa04bb2a62507717f296b0b | malicious index.js (specs) |
| SHA1 | c8cb3f6d5b90c46686d2bf531dc1a5786e27edc5 | sync.js Stage 2 payload |
| IP | 85.137.53.71 | C2 server (ports 8080, 8081, 8091) |
| IPFS CID | QmQobZSp1wRPrpSEQ56qnyq7ecZh5Bg5k1fnjt4SUwwHb9 | Stage 2 payload (generator packages) |
| IPFS CID | Qmet4fhsAaWMBUxNDfREHwgiyDeSWy4YSYs9wiKUW5jGyf | Stage 2 payload (specs variant) |
| Ethereum | 0x12c37A86a0Ed0beBe5d1d6a43E42f07860eAc710 | C2 dead-drop contract |
| File | ~/.local/share/NodeJS/sync.js | persisted payload (Linux) |
| Service | miasma-monitor.service | systemd persistence |
| Domain | ipfs[.]io | payload delivery |
| Domain | rentry[.]co | PAT exfiltration dead-drop |
Verification steps for teams
- Search lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) across all repositories for the five malicious versions, including transitive resolution of @asyncapi/specs@6.11.2.
- Run SCA and SBOM analysis across every repository, treating a low behavioral score as insufficient evidence of safety; a signature-clean scan will not flag an unpublished version pinned in an old lock file.
- Grep CI logs from 07:10 to 11:18 UTC on July 14, 2026, for outbound connections to the network indicators above.
- Check each developer and build host for the sync.js drop file and for orphaned node processes launched from a NodeJS home-directory folder.
Remediation Guidance
Immediate actions
- Regenerate lock files. All malicious versions are unpublished, so npm install will no longer resolve them, but a lock file captured during the exposure window may still pin them. Delete the lock file and reinstall:
rm package-lock.json # or yarn.lock / pnpm-lock.yaml
npm install
- Pin last-safe versions explicitly where needed:
npm install @asyncapi/generator@3.3.0 @asyncapi/generator-helpers@1.1.0 @asyncapi/generator-components@1.0.0
npm install @asyncapi/specs@6.11.1
For transitive exposure via the parser, force the safe version with an override:
“overrides”: { “@asyncapi/specs”: “6.11.1” }
- Remove the drop file at the OS-appropriate path and kill any orphaned node process launched from a home-directory NodeJS folder.
- Rotate all credentials present on any affected machine or CI runner: npm tokens, GitHub PATs and deploy keys, SSH private keys, AWS/Azure/GCP credentials, macOS Keychain entries, and browser-saved passwords and cookies. Treat the asyncapi-bot PAT and any organization secrets reachable from the compromised workflows as exposed.
- Quarantine AI-assisted output. On any affected developer workstation, review AI coding-assistant output from the exposure window independently, given the ai-tool-poisoner.js module.
Temporary mitigations
Blocking install-time scripts does not help here because the payload fires on require(), not on install. Effective interim controls are network-layer egress control on CI runners (block the C2 IP, IPFS gateways, DHT bootstrap nodes, and Nostr relays), a registry cooldown that withholds newly published versions until they age past the typical detection window, and dependency pinning to the last-safe versions with the parser override in place. Note that –ignore-scripts is incomplete in general for environments that rely on legitimate native addons, and it is entirely ineffective against this require()-time dropper.
Validation
Confirm resolved versions with npm ls @asyncapi/generator @asyncapi/generator-helpers @asyncapi/generator-components @asyncapi/specs, verify no sync.js drop file remains on any host, and confirm CI egress logs show no residual connections to the C2 infrastructure after remediation.
Phoenix Security Recommendations
There is no CVE and no CVSS score here, so any workflow gated on CVE presence or a minimum CVSS threshold had nothing to act on during the four hours the packages were live. CVE-based SCA tooling has no detection surface during active exploitation of a compromise like this, because the malicious artifact is a legitimately-provenanced version of a trusted package with no identifier attached. That is the zero-CVE gap, and it is the reason behavioral detection matters more than feed matching for this class of attack.
Phoenix Security addresses the gap through behavioral and exposure-driven detection rather than CVE matching. The PHX-Neural behavioral scoring engine keys on the payload’s behavior (detached child-process spawn, off-screen obfuscated first-line injection, unexpected egress) rather than on a missing identifier, and Blue Shield’s CI/CD firewall consumes the live IOC feed to block the C2 endpoints and IPFS delivery at the network layer across pipelines without a workflow edit. Reachability analysis then separates repositories that actually import the poisoned modules at build or runtime from those that merely list them, so remediation effort lands where the code is genuinely exercised.
For response, run this as a Phoenix remediation campaign: create the campaign from the IOC set, use contextual deduplication to collapse duplicate findings from multiple scanners into one prioritized backlog, map each affected repository to its owning team through ownership attribution, assign and track fixes, and verify runtime exposure once lock files are regenerated. Attack surface management identifies which build systems and services actually pulled the malicious versions, shrinking the blast radius into an owned, trackable backlog.
The data behind this article is available in full in the Phoenix Security Supply Chain Intelligence Report

Read the Full Supply Chain Intelligence Report
External References
- Wiz Research: AsyncAPI supply chain compromise analysis and IOC set (July 14, 2026).
- StepSecurity: “Harden-Runner Blocked Exfiltration During the AsyncAPI Supply Chain Attack,” including OIDC provenance analysis and confirmed C2 telemetry (July 14, 2026).
- AsyncAPI generator: pwn request investigation and proposed fix
- AsyncAPI generator: vulnerable Netlify preview workflow at pre-fix commit (malicious configuration since removed): npm registry: package provenance attestations for the affected @asyncapi packages (SLSA/sigstore Fulcio SAN records).
- AsyncAPI spec-json-schemas: repository README documenting @asyncapi/specs publishing and consumption by the parser.
- Wiz analysis
- Socket analysis
- Open-source malware