Supply Chain Security

Miasma: The npm Worm That Poisoned Red Hat's Own Packages, and the Small-Business Dependency Playbook

Dark cyberpunk illustration of a shadowy dockyard conveyor of identical sealed cargo crates, one cracked open and glowing molten orange with blight-like tendrils spreading to the neighboring crates along cyan network light-traces.

For two years, the standard advice after every npm supply chain attack has come down to three words: verify the provenance. Sign your packages, check the signatures, and trust the builds that came from a known pipeline. In the first days of June, an attacker poisoned 32 packages published under Red Hat's official @redhat-cloud-services namespace, and every malicious version carried a valid provenance signature. Nothing was forged. The attacker pushed the trojanized releases through Red Hat's own legitimate publishing pipeline, so the packages were signed exactly the way a clean release would be.

Microsoft Threat Intelligence, which named the campaign Miasma, counted more than 90 poisoned versions across that scope, with roughly 80,000 to 117,000 weekly downloads sitting inside the affected packages. Once installed, the malware stole cloud credentials, then republished itself into other packages it could reach. Red Hat has issued its own advisory with the full list of affected versions.

Here is why this matters even if you have never installed a Red Hat npm package on purpose. npm packages pull in other packages, which pull in still more, and a malicious dependency three layers down runs on your machine exactly like one you chose. So the real test has nothing to do with whether you trust Red Hat. It is whether anything you build - a product, a website, an internal tool, a contractor's project - ran npm install in the first week of June and happened to pull a poisoned version somewhere in its tree. If your business runs no Node.js or npm anywhere, you can close this tab; this one genuinely is not yours. If you ship any JavaScript, or pay someone who does, the defensive lesson here outlasts this particular worm.

The myth: a signed package is a safe package

After the Shai-Hulud worms tore through npm earlier this year, the industry converged on a fix: package provenance. npm provenance and the broader SLSA framework attach a signed, verifiable record to each release that ties it back to the source commit and the CI/CD workflow that built it. The promise is that you can check where a package came from and refuse anything that did not come from the expected place. It is a genuinely good control, and it stops a large class of attacks: typosquatting, dependency confusion, and a stranger publishing under a name they do not own.

Miasma walked straight through it. According to Microsoft, the attackers compromised the upstream RedHatInsights/javascript-clients CI/CD pipeline and published the trojanized packages through the legitimate GitHub Actions OIDC workflow. The malicious versions therefore carried authentic provenance, signed by the real pipeline and pointing at the real repository. A verifier checking those signatures would have returned a green check. A valid signature proves a package came from the expected pipeline; it says nothing about whether the pipeline was honest at the moment it signed. Wiz, which tracked the same campaign, traced the initial access to a compromised maintainer account whose commits bypassed code review. Own the pipeline and you inherit its trust.

What Miasma actually did once it ran

The entry point is the one we keep coming back to. Each poisoned package.json carried a preinstall hook, the lifecycle script npm runs automatically during installation, before any of your own code executes. It fires on direct installs and on transitive ones alike, so a developer who never typed the package name still triggered it. The hook unpacked a 4.29 MB obfuscated dropper, pulled down the Bun JavaScript runtime into a temporary directory, and ran the real payload from there. What happened next depended on where it landed.

On a developer laptop

  • It harvested SSH keys, cloud CLI credentials, and browser and crypto-wallet data, specifically hunting tokens for GitHub, npm, AWS, Azure, GCP, HashiCorp Vault, and Kubernetes.
  • It exfiltrated the loot to an attacker-controlled public GitHub repository, hidden in plain sight among ordinary-looking files.
  • It planted a destructive failsafe: if the malware detected interaction with a decoy token of the kind analysts use, it ran rm -rf ~/ to wipe the user's home directory.

On a CI/CD runner

  • It scraped the GitHub Actions runner's memory for secrets that never touch disk.
  • It escalated with passwordless sudo where the build runner allowed it.
  • It republished poisoned versions of other packages the compromised account could reach, attaching forged SLSA provenance so the next victim's verifier would pass too. That self-propagation is what makes Miasma a worm, and it is how the campaign spread past Red Hat, reaching a widely used voice-AI SDK with hundreds of thousands of monthly downloads.

Whether this one is yours

For the owner reading this, the verdict is simple: if no system in your business installs npm packages, you are finished here. For everyone else, the practitioner check takes a few minutes and is worth running on every repository and every build image, because the dangerous version is usually buried in a lockfile rather than your package.json.

#!/usr/bin/env bash
# Run from each repo root. Check the lockfile, not just direct deps,
# because the poisoned package is usually a transitive dependency.
grep -nE "@redhat-cloud-services/" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null

# Show every resolved version of the scope across a monorepo / workspace:
npm ls --all 2>/dev/null | grep "@redhat-cloud-services"

If a line comes back inside the version ranges Red Hat lists in RHSB-2026-006 - for example rbac-client 9.0.3 through 9.0.6, or chrome 2.3.1 through 2.3.4 - and that install ran between June 1 and June 5, treat the machine or runner as compromised and work through the next two sections. A clean grep across all of your repositories is the answer you want, and most teams will get it. The few that do not just learned something important before an attacker taught it to them.

Rotate first, then close the install step

If you did pull a poisoned version, the order of operations matters. Credentials first, because they may already be sitting in someone else's GitHub repository, then the install pipeline so the same thing cannot happen twice.

Rotate everything the build could see

Assume every secret reachable from the affected machine or runner is burned: GitHub and npm tokens, AWS, Azure, and GCP keys, Vault tokens, and Kubernetes credentials. Rotate them, then review those accounts for activity you did not initiate - new tokens, fresh SSH keys, package publishes you cannot account for. Microsoft and Red Hat both put this first, not last.

Make install scripts opt-in, not automatic

The preinstall hook only ran because npm executes lifecycle scripts by default. Turn that off and the whole class of attack loses its trigger:

# Make ignore-scripts the default for every install on this machine or runner:
npm config set ignore-scripts true          # writes to your user ~/.npmrc

# Commit it per project so CI and teammates inherit the same default:
echo "ignore-scripts=true" >> .npmrc

# Install only what the lockfile pins, with no lifecycle scripts, in CI:
npm ci --ignore-scripts

Then pin dependencies to known-good versions, commit your lockfiles, and pause automatic upgrades until you have validated them. A week ago we covered a 500-package install-script wave that used this same entry point; the controls have not changed, and they are cheap. The reason they keep working is that they strip out the automatic execution attackers depend on, whether or not the package was signed.

Hunt for it if you ran the bad versions

Rotation closes the front door. Hunting tells you whether someone already walked through it. If you have endpoint or build telemetry in Microsoft Defender or Sentinel, Microsoft published detections for the behavior the malware struggles to hide. The clearest signal is the Bun runtime launching out of a temporary directory, which almost nothing legitimate does on a build host:

// Microsoft Defender / Sentinel: the Bun runtime launching from a temp
// directory is the clearest Miasma stage-two signal on a build machine.
DeviceProcessEvents
| where FileName == "bun" or ProcessCommandLine has "bun run"
| where FolderPath startswith "/tmp/"
    or FolderPath has @"AppData\Local\Temp"
| project Timestamp, DeviceName, AccountName, FolderPath, ProcessCommandLine

Also watch for outbound requests to npm's token endpoints (registry.npmjs.org/-/npm/v1/tokens and /-/whoami) coming from a build context, and for new public repositories in your GitHub organization carrying the campaign's calling card - the description "Miasma: The Spreading Blight." The published indicators include file hashes for several of the poisoned releases; load them into your EDR and your CI scanning so a re-pull trips an alert instead of a credential theft.

Stop trusting the install step to behave

The lasting lesson of Miasma is small and uncomfortable. A signature tells you where a package came from, not whether it is safe, and the moment an attacker owns the pipeline, those two stop being the same fact. You cannot verify your way out of that. What you can do is make the install step boring: scripts off by default, versions pinned, lockfiles committed, secrets rotated on a schedule rather than after a breach. Do that and a poisoned dependency arrives with nothing to execute and nothing worth stealing. For a small business that is a weekend of work on the build pipeline, and it is the same weekend whether the next worm is signed or not. If your software, or your contractors' code, runs in places you cannot fully see, that is the review we would start with.

Not sure what your build pipeline can reach?

We review software supply chains and CI/CD pipelines for small businesses and the dev teams they rely on: which secrets a build can touch, where install scripts run unchecked, and what an attacker inherits if a single dependency goes bad. Book a session and we will map your exposure and the cheapest way to close it.