# AsyncAPI npm packages backdoored via GitHub Actions

Attacker abused a pull_request_target workflow in asyncapi/generator to steal an npm publish token, then backdoored five @asyncapi packages with an IPFS-delivered RAT.

- Payout: Real-world hack
- Severity: critical
- Program: AsyncAPI
- Analysis by: Aikido Security
- Disclosed: 2026-07-14
- Write-up quality: 8/10
- Original: https://www.aikido.dev/blog/asyncapi-npm-packages-backdoored-via-github-actions
- Library page: https://bugbountylibrary.com/r/blog%3Aaikido-dev-blog-asyncapi-npm-packages-backdoored-via-github-actions
- Tags: area:oss domain:oss impact:data-leak impact:rce kind:incident platform:threat-intel sev:critical tech:github-actions tech:nodejs technique:reverse-engineering technique:source-review vuln:secrets-leak

**Target:** GitHub Actions workflow and npm packages (@asyncapi/specs, generator, generator-helpers, generator-components)
**Root cause:** A pull_request_target workflow ran with repository secrets while checking out untrusted PR code, letting a contributor exfiltrate the npm publish token. The unmerged fix PR (#2092) left the window open.
**Steps:**
1. Open 37 PRs against asyncapi/generator; PR #2155 contains obfuscated JS that exfiltrates the npm publish token to `rentry[.]co`
2. pull_request_target workflow runs with secrets and executes the PR code, sending the token out
3. Use token to push malicious commits to `next` branch and publish @asyncapi/specs@6.11.2, generator@3.3.1, generator-helpers@1.1.1, generator-components@0.7.1
4. Pivot to asyncapi/spec-json-schemas, push 11 commits, publish specs alpha
5. Malicious code prepended to runtime modules spawns detached `node -e` downloader fetching IPFS CID `Qmet4fhsAaWMBUxNDfREHwgiyDeSWy4YSYs9wiKUW5jGyf` (specs) or `QmQobZSp1wRPrpSEQ56qnyq7ecZh5Bg5k1fnjt4SUwwHb9` (generator family)
6. Loader writes `sync.js` to per-user NodeJS data dir, decrypts AES-256-GCM vault via HKDF key `rt-file-key-material-v1`, reverses ASCII rotation, evals stage-3
7. Implant generates secp256k1 keypair, writes lock `~/.config/.miasma/run/node.lock`, installs persistence (zshrc/bashrc, HKCU Run `miasma-monitor`, systemd user unit)
8. Beacons every ~30s to `http://85.137.53.71:8080`; ShellExec command 11 runs `child_process.exec` with only `killall` blacklisted
**How found:** Researcher identified the pull_request_target footgun in the generator workflow (fix PR #2092 unmerged), then analyzed published tarballs, extracted the inline `node -e` string table revealing IPFS URLs, decrypted the AES-GCM loaders with the hard-coded HKDF master key, and did call-graph analysis showing safeMode/actualPersist/canaryPercent config fields were misleading.
**Exploitation:** Token theft via PR flood against pull_request_target gave publish rights; trojanized packages execute on plain require(), spawn detached downloader, fetch encrypted loader from IPFS, decrypt and eval stage-3 implant that beacons to HTTP C2 and accepts shell commands, giving persistent remote shell on developer/build hosts.
**Escalation:** Pivoted from generator repo to spec-json-schemas to widen package coverage; implant supports operator-supplied CID updates and unsigned Ethereum-based update records for persistence beyond takedown.
**Insight:** When auditing a malicious npm package, the obfuscated `node -e` string lookup table often leaks the C2/IPFS URL and drop filename in plaintext; and config flags like safeMode/canaryPercent may be dead code — verify by call-graph, not field values.
