Miasma worm hit Red Hat's npm packages: detect, rotate, replace
On June 1, 2026, researchers caught a self-spreading npm worm that had been live since May 29.
Its calling card is a commit message: "Miasma: The Spreading Blight." A compromised Red Hat
employee GitHub account pushed malicious orphan commits straight into RedHatInsights
repositories, bypassing code review, and seven @redhat-cloud-services packages
shipped a credential-stealing payload to everyone who installed them.
Two things make Miasma worth a fresh runbook. It runs in a preinstall hook, so it
fires before postinstall and even a cancelled install can trip it. And it plants
persistence inside your developer tools: a SessionStart hook in Claude Code and a
tasks.json with "runOn": "folderOpen" in VS Code projects. Deleting
node_modules does nothing to those. Here is the detect, rotate, replace playbook.
The seven compromised packages
Every one is under the @redhat-cloud-services scope:
vulnerabilities-clienttsc-transform-importstopological-inventory-clientsources-clientrule-componentsremediations-clientrbac-client
Step 1: detect in under 60 seconds
Three signals: a compromised package in your tree, a persistence artifact in Claude Code or VS
Code, and exfil traffic disguised as calls to api.anthropic[.]com:443/v1/api. Run
this from any host with npm and git:
# 1. Did any compromised @redhat-cloud-services package land in your tree?
COMPROMISED="vulnerabilities-client tsc-transform-imports topological-inventory-client \
sources-client rule-components remediations-client rbac-client"
for pkg in $COMPROMISED; do
npm ls "@redhat-cloud-services/$pkg" 2>/dev/null | grep -v empty && echo "REVIEW: $pkg"
done
# 2. Check the dev-tool persistence artifacts Miasma plants
grep -l "SessionStart" ~/.claude/settings.json 2>/dev/null
find . -path "*/.vscode/tasks.json" -exec grep -l "folderOpen" {} \;
git log --all --oneline | grep -i "Miasma"
# 3. Look for exfil disguised as Anthropic API traffic in egress logs
grep -E "api\.anthropic\.com:443/v1/api" /var/log/egress.log Any hit means you skip to step 2. The worm finishes harvesting before you finish reading the advisory, so a single signal is enough to start rotating.
The persistence trick is the part that bites teams who clean up too fast. You can remove the
bad package, rebuild node_modules, and still have a SessionStart hook in
~/.claude/settings.json that re-runs the payload the next time you open Claude
Code. Audit ~/.claude/settings.json, .vscode/tasks.json, and
.github/workflows by hand before you call a host clean.
Step 2: confirm the publish window with the npm API
Before you pin a "safe" version, check when it was published. The malicious versions landed on
May 29, so any release dated that day or later is suspect. The botoi npm endpoint returns the
modified timestamp and maintainer list without an npm install:
# Inspect the publish timeline of a suspect package before you trust a pin.
curl https://api.botoi.com/v1/npm/%40redhat-cloud-services%2Frbac-client {
"data": {
"name": "@redhat-cloud-services/rbac-client",
"version": "1.7.0",
"license": "Apache-2.0",
"repository": "https://github.com/RedHatInsights/rbac-client",
"maintainers": [{ "name": "redhat-cloud-services-bot" }],
"modified": "2026-05-29T11:42:00.000Z",
"versions_count": 64
}
}
A modified timestamp on or after 2026-05-29 means pin to the last known
good release published before that date, not the latest. The GET shape caches at the CDN, so you
can fold this into a dependency-approval check that runs on every pull request.
Step 3: rotate, then scrub the logs
Miasma harvests GitHub Actions secrets, npm tokens, cloud credentials, Kubernetes and Vault material, SSH keys, and Git credentials. Rotate in that order: npm tokens stop new publishes, GitHub PATs stop repository writes, cloud IAM keys stop data exfil, then Vault and SSH. Do not investigate first; rotate, then investigate.
The payload dumps those secrets into logs that flow into Datadog, Sentry, and your ticketing system. Pipe high-risk fields through a secret detector and fail closed before they reach a queryable index:
# Scan CI logs for the secrets Miasma dumps before they hit cold storage.
curl -X POST https://api.botoi.com/v1/pii/detect \
-H "Content-Type: application/json" \
-d '{"text": "NPM_TOKEN=npm_a1b2... VAULT_TOKEN=hvs.CAES... id_rsa loaded"}' {
"data": {
"found": true,
"matches": [
{ "type": "npm_token", "value": "npm_a1b2...", "start": 10, "end": 24 },
{ "type": "vault_token", "value": "hvs.CAES...", "start": 38, "end": 52 }
]
}
} Step 4: block the next wave with a CI guard
Miasma needs three things on your runner: a compromised version, a lifecycle hook that runs, and a persistence file it can write. Break any one. This GitHub Actions check disables lifecycle scripts for the job, fails on Miasma persistence markers, flags Red Hat scope packages, and requires signed provenance on runtime deps:
name: block-preinstall-hooks
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Disable lifecycle scripts for the whole job
run: npm config set ignore-scripts true
- name: Fail on Miasma persistence markers
run: |
! grep -rE "Miasma|folderOpen|SessionStart" \
.github/ .vscode/ package.json package-lock.json
- name: Block compromised Red Hat package versions
run: |
BAD=$(node -e "
const lock = require('./package-lock.json');
const hit = Object.keys(lock.packages || {})
.filter(k => k.includes('@redhat-cloud-services/'));
console.log(hit.join('\n'));
")
[ -z "$BAD" ] || (echo "REVIEW: $BAD"; exit 1)
- name: Require signed provenance for runtime deps
run: npm audit signatures --omit=dev
Keep ignore-scripts on by default and maintain a short allowlist for packages that
legitimately build (TypeScript, esbuild, Sharp). A preinstall hook that never runs cannot harvest
anything.
Step 5: shrink the surface that can host a worm
The packages most often poisoned across the 2026 waves wrap a single network call: API clients, validation helpers, and small utilities. Each one is a candidate to delete and replace with a revocable HTTPS call that runs no local code.
| Category | Typical npm dependency | HTTP replacement | Blast radius if breached |
|---|---|---|---|
| Secret and PII scan | secretlint, pii-detector | /v1/pii/detect | Rotate one API key |
| Package metadata | npm registry client wrappers | /v1/npm/{package} | Rotate one API key |
| Email validation | validator, deep-email-validator | /v1/email/validate | Rotate one API key |
| Breach check | hibp client packages | /v1/breach/check | Rotate one API key |
Key takeaways
- Check the dev-tool persistence files. Miasma hides in
~/.claude/settings.jsonand.vscode/tasks.json. Removing the package does not remove the hook. - Pin by publish date. Anything in the seven packages dated on or after
2026-05-29 is suspect. Use
/v1/npm/{package}to confirm. - Rotate before you investigate. npm tokens, GitHub PATs, cloud keys, Vault, SSH. Exfil completes in seconds.
- Disable lifecycle scripts by default. A preinstall hook that never runs cannot steal anything.
- Shrink the surface. Replace single-call npm packages with revocable HTTP endpoints. A breached key dies in one request.
Botoi exposes /v1/npm/{package}, /v1/pii/detect,
/v1/breach/check, and roughly 200 other single-purpose endpoints behind one API key
with 5 req/min free. Wire them into your incident runbook, or connect the
MCP server to Claude
Code so your assistant runs the detection script for you. Start from the
interactive docs.
Frequently asked questions
- What is the Miasma supply chain attack?
- Miasma is a self-spreading npm worm discovered on June 1, 2026, with its first "Miasma: The Spreading Blight" commit dated May 29, 2026. A compromised Red Hat employee GitHub account pushed malicious orphan commits to RedHatInsights repositories, bypassing code review, and seven @redhat-cloud-services npm packages were published with an obfuscated preinstall hook that harvests credentials and spreads to new hosts.
- Which packages were compromised?
- Seven packages under the @redhat-cloud-services scope: vulnerabilities-client, tsc-transform-imports, topological-inventory-client, sources-client, rule-components, remediations-client, and rbac-client. If your lockfile pulled any of these after May 29, 2026, treat the host as compromised and rotate every credential it touched.
- How is Miasma different from earlier npm worms?
- Miasma runs in a preinstall hook, so it fires before postinstall and even a cancelled install can trigger it, and it plants persistence inside developer tools, not just CI. It injects a SessionStart hook into Claude Code, writes a tasks.json with runOn folderOpen into VS Code projects, and generates a uniquely encrypted payload per infection to dodge signature scanning. Deleting node_modules does not remove it.
- What does the worm steal and where does it send it?
- GitHub Actions secrets, npm tokens, cloud credentials, Kubernetes and Vault material, SSH keys, and Git credentials. It disguises exfiltration as traffic to api.anthropic[.]com on port 443 and falls back to GitHub as a secondary channel, so egress filtering on unknown domains alone will not catch it.
- How do single-purpose HTTP APIs reduce this risk?
- A package with a preinstall hook runs arbitrary code on every machine that installs it. An HTTPS call to a single-purpose API runs nothing locally and is gated by a revocable key. If the API provider is breached, you rotate one key. If a package in your tree is breached, the malware already executed. The trade is a small latency cost for a tiny blast radius and instant revocation.
Try this API
NPM Package Info API — interactive playground and code examples
More guide posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.