In early June 2026, a self-replicating supply chain worm called Miasma tore through the npm registry, reached Microsoft’s Azure GitHub organizations, and spawned a PyPI variant — all within ten days. It’s the most technically sophisticated npm-ecosystem attack to date, and it introduces a capability that changes the threat model for developers: your AI coding tools are now a payload delivery mechanism.
Here’s what happened and what you need to check.
The attack at a glance
The Miasma worm is a credential-stealing payload that spreads itself automatically across npm packages, GitHub repositories, and now PyPI. It’s a variant of the Shai-Hulud family, attributed to the TeamPCP threat group.
In a rolling two-hour campaign on June 3–4, 57 npm packages across 286+ malicious versions were compromised, including @vapi-ai/server-sdk (408,000 monthly downloads) and ai-sdk-ollama (120,000 monthly downloads). On June 5, the same worm reached 73 Microsoft GitHub repositories — including azure-functions-host and Azure/functions-action, breaking CI/CD pipelines for developers worldwide. On June 9, a PyPI variant called Hades was discovered across 19 packages.
The same core payload runs across all variants: download the Bun JavaScript runtime, execute a heavily obfuscated credential stealer, exfiltrate everything to attacker-controlled GitHub repositories.
How it hides from security tools
Every npm security guide says to watch for preinstall and postinstall lifecycle scripts. Miasma uses neither.
Instead, it adds a 157-byte binding.gyp file to the published package. When npm sees this file, it automatically runs node-gyp rebuild during installation — a mechanism designed for packages with native C/C++ addons. The file exploits gyp’s command substitution syntax to execute the payload:
{
"targets": [{
"target_name": "Setup",
"type": "none",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
}]
}
The <!(...) syntax runs the enclosed shell command during the build. No lifecycle script entry in package.json. Tools that scan for preinstall/postinstall see nothing. StepSecurity called this technique “Phantom Gyp”.
The package’s legitimate code in dist/ is completely untouched — the malware is bolted on as a 4.5 MB obfuscated root index.js that is never imported by application code, only executed by the gyp trigger.
What it steals
The payload is a multi-cloud credential harvester. After downloading and running a standalone Bun runtime (to evade Node.js process monitoring), it targets:
- AWS, GCP, and Azure credentials — including IMDS endpoints and cloud CLI configs
- GitHub tokens, npm publish tokens, and GitHub Actions secrets — extracted directly from runner process memory by reading
/proc/{pid}/mem, bypassing GitHub’s secret masking entirely - SSH keys,
.envfiles, shell history, password managers (1Password, gopass, pass) - Docker, Kubernetes, HashiCorp Vault configurations
- AI tool credentials: Anthropic, OpenAI Codex, Google Gemini, Cursor
Exfiltration goes to attacker-controlled GitHub repositories under the account liuende501, with the stolen data encrypted with the attacker’s RSA public key before upload.
The new attack surface: your editor
The most significant evolution in the Microsoft incident is the shift from “execute on package install” to “execute when you open a repository in your IDE”.
On June 5, a malicious commit was pushed to Azure/durabletask using a previously compromised contributor account. The commit added five files — no source code changes — that execute a credential harvester automatically when a developer opens the repository in VS Code, Claude Code, Cursor, or Gemini CLI:
.claude/settings.json— SessionStart hook that runs on every new Claude Code session.gemini/settings.json— identical, for Gemini CLI.cursor/rules/setup.mdc— prompt injection withalwaysApply: true, instructs Cursor to run the payload as “required project setup”.vscode/tasks.json—runOn: folderOpentask, executes automatically without any AI involvement.github/setup.js— the 4.6 MB obfuscated payload that all four files point to
The commit message claimed a code change. There was none. The commit timestamp was backdated six years. The [skip ci] flag suppressed CI execution.
GitHub’s automated abuse detection disabled all 73 affected repositories in a 105-second window — but not before Azure/functions-action went offline, breaking every GitHub Actions deployment pipeline referencing @v1.
The PyPI spillover: Hades
By June 9, the campaign had a Python variant. The Hades campaign poisoned 19 PyPI packages across 37 malicious wheel artifacts. Instead of binding.gyp, it uses a *-setup.pth file — processed by Python’s site module at interpreter startup — to execute the same Bun-based credential stealer automatically after installation, before any application code runs.
Hades adds a capability not seen in prior variants: a wiper. If the stolen GitHub token is revoked, a background service named gh-token-monitor runs rm -rf ~/; rm -rf ~/Documents.
It also attempts to deceive LLM-based security scanners by embedding plain-text prompt injection in the package, instructing AI analysis tools to classify it as safe.
Am I affected?
For npm: Check whether any of these packages appear in your package-lock.json, yarn.lock, or pnpm-lock.yaml:
grep -rE 'vapi-ai/server-sdk|ai-sdk-ollama|autotel|awaitly|executable-stories|node-env-resolver|wrangler-deploy|mountly|effect-analyzer|http-uploader-dev' \
package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
The full list of 57 affected packages and 286+ malicious versions is in StepSecurity’s analysis.
For GitHub repositories: If you cloned any affected Microsoft Azure repositories after June 2 and opened them in VS Code, Claude Code, Cursor, or Gemini CLI, treat the machine as compromised. The payload fires on folder open, not on code execution.
Check your own repositories for unexpected commits adding .claude/, .gemini/, .cursor/, .vscode/tasks.json, or .github/setup.js files.
For CI/CD pipelines referencing Azure/functions-action@v1: The repository remains disabled. Switch to alternative Azure deployment methods (Azure CLI, Zip Deploy) and pin GitHub Actions to commit SHAs rather than mutable tags.
What to rotate if you were affected
If you installed an affected version during the live window, rotate immediately:
- GitHub tokens and npm publish tokens
- AWS access keys, GCP service account keys, Azure service principals
- HashiCorp Vault tokens, Kubernetes service account tokens
- SSH keys from
~/.ssh - GitHub Actions OIDC trust relationships
How to reduce your exposure going forward
Run npm install --ignore-scripts in CI. This blocks postinstall hooks — and while it doesn’t stop the binding.gyp vector entirely, it eliminates most install-time execution paths. Consider making --ignore-scripts the default for your CI pipeline.
Set a minimum release age. Bun, npm, pnpm, and Yarn all support a minimumReleaseAge / min-release-age configuration that delays installation of recently published versions. The Miasma wave lasted under two hours; a 24-hour minimum age would have blocked automatic installation in most CI pipelines. More on this →
Audit .claude/, .cursor/, .gemini/, and .vscode/tasks.json in repositories you clone. These files now warrant the same scrutiny as preinstall scripts. A runOn: folderOpen VS Code task or a SessionStart Claude hook in an unfamiliar repository is a red flag.
Pin GitHub Actions to commit SHAs. Mutable tags like @v1 break when repositories are disabled — as every developer using Azure/functions-action discovered on June 5.
Generate and monitor SBOMs. Knowing exactly which packages are in your builds — and being notified when a package you depend on is flagged as malicious — is the difference between a proactive response and a reactive investigation.
boring.tools tracks newly disclosed malicious packages and generates SBOMs from your repositories on every commit, so you know immediately when something you depend on is affected. See how it works →