Skip to content
guide

Miasma worm hit Red Hat's npm packages: detect, rotate, replace

| 8 min read

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-client
  • tsc-transform-imports
  • topological-inventory-client
  • sources-client
  • rule-components
  • remediations-client
  • rbac-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.json and .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.