Miasma Worm Poisons 20+ Leo Platform npm Packages, Harvests CI Runner Secrets
Miasma/Shai-Hulud trojanized 20+ Leo Platform and RStreams npm packages in a sub-three-second burst on June 24, hiding its install hook in binding.gyp. It reads CI runner memory for masked secrets and exfiltrates via the victim's own GitHub token, beating egress blocklists.
The Miasma supply chain worm - also tracked as Shai-Hulud, Mini Shai-Hulud, and Hades - has claimed another set of victims, trojanizing more than 20 npm packages in the Leo Platform and RStreams ecosystems. Microsoft Threat Intelligence reports the attack began late on June 24, 2026, after operators compromised the npm maintainer account "czirker" and used it to publish poisoned package versions in a coordinated, fully automated operation completed in under three seconds at 23:04:55 UTC.
These are not throwaway typosquats. The affected packages cover SDK, CLI, AWS, cron, logging, connector, and serverless components used in real data-pipeline and cloud-integration workloads, so the exposure reaches developer workstations, CI/CD runners, build caches, container images, and production-adjacent systems.
The install hook hides in binding.gyp
Earlier Miasma waves leaned on npm preinstall/postinstall lifecycle scripts. This version moves the trigger somewhere defenders are less likely to look. Each malicious package ships a tiny binding.gyp and a large index.js with no postinstall script. The hook is buried in node-gyp's command expansion: the binding.gyp sources array contains <!(node index.js > /dev/null 2>&1 && echo stub.c), so npm install executes index.js at build time. These are plain JavaScript libraries with no need for a native build, so the binding.gyp is pure attack surface - and a metadata-only scan sees nothing wrong.
index.js is a three-layer dropper: a ROT character-code cipher, then AES-128-GCM (two encrypted blobs), then an obfuscator[.]io toolkit. The loader writes the decoded toolkit to /tmp/p.js and runs it under the Bun runtime - downloaded fresh as v1.3.13 - rather than Node. Running under Bun sidesteps Node-based instrumentation and EDR module-load detection, which is the entire point of the swap.
It reads CI runner memory
On a CI runner or workstation, the toolkit does five things:
- Reads runner memory. It locates the GitHub Actions
Runner.Workerprocess and reads/proc/{pid}/memto lift secrets that CI masks in its logs. Log masking does not protect process memory. - Sweeps credentials. AWS, GCP, Azure, HashiCorp Vault, and Kubernetes, plus npm, PyPI, RubyGems, and JFrog tokens, GitHub PATs, and 1Password.
- Exfiltrates with no C2 domain. It commits stolen secrets to an attacker-controlled GitHub repository using the victim's own GitHub token - a dead drop that defeats egress domain blocklists, because the traffic goes to github.com.
- Self-propagates. It republishes any package the victim is allowed to maintain, bypassing npm two-factor (
bypass_2fa) to open another spread path. - Escalates and persists. On GitHub-hosted runners it writes
runner ALL=(ALL) NOPASSWD:ALLto sudoers and injects workflows requestingid-token: write.

The campaign has expanded beyond npm: researchers tracked the same execution pattern into a compromised Go module (the Verana Blockchain project) and a second npm publisher account, llxlr, shipping hexo-deployer-wrangler, hexo-shoka-swiper, and prism-silq. The Mini Shai-Hulud toolkit went open source on GitHub earlier this month, so the operator pool is no longer a single actor.
Confirmed malicious versions
leo-logger@1.0.8, leo-sdk@6.0.19, leo-aws@2.0.4, leo-config@1.1.1, leo-streams@2.0.1, serverless-leo@3.0.14, leo-connector-mongo@3.0.8, serverless-convention@2.0.4, rstreams-metrics@2.0.2, leo-connector-elasticsearch@2.0.6, leo-auth@4.0.6, leo-cache@1.0.2, leo-cli@3.0.3, leo-cron@2.0.2, leo-connector-redshift@3.0.6, leo-connector-oracle@2.0.1, rstreams-shard-util@1.0.1, leo-connector-mysql@3.0.3, leo-cdk-lib@0.0.2, solo-nav@1.0.1.
Detection and mitigation
Hunt for these IOCs across runners and workstations:
binding.gypfiles containing<!(node index.js- An
index.jscarrying a char-code array of length 1,566,023 - Stray
/tmp/p*.jsfiles and a freshly downloaded Bun binary appearing duringnpm install - Outbound connections to
github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/ runner ALL=(ALL) NOPASSWD:ALLwritten to sudoers on a runner
Microsoft Defender for Endpoint flags this as Trojan:JS/MiniShaiHrd.ZA!MTB (index.js) and Trojan:JS/PhantomWorm.DA!MTB (binding.gyp), alongside suspicious Bun runtime install/execution and credential-access alerts. Defender for Cloud raises "Suspicious npm supply-chain compromise activity detected."
Pin to known-good versions and enforce lockfiles. Check dependency lockfiles, internal package mirrors, build caches, container images, and CI runners for lingering copies of the malicious releases. Review GitHub Actions for unexpected id-token: write requests or sudoers changes. Critically, sequence the cleanup - remove the persistence first, then rotate. Rotate secrets while the worm still has a foothold and it simply steals the replacements.
The Bigger Picture
Two design choices make Miasma hard to stamp out. The dead-drop exfiltration - pushing stolen data to a victim-owned GitHub repo instead of a command-and-control server - means egress filtering and C2 blocklists never fire, because the destination is a domain every developer org already trusts. And reading /proc/{pid}/mem off the Actions runner defeats the log-masking that teams assume protects their CI secrets. Combined with an open-sourced toolkit and 2FA-bypassing self-propagation, this is a worm built to live inside the trusted developer supply chain, not outside it. Treat any environment that installed an affected version as compromised until proven otherwise.