Miasma in the @redhat-cloud-services npm scope: a worm with valid provenance

First, kill the reflex. If you run RHEL and your job is dnf update and Satellite content, you are not in scope for this and you should stop reading the parts of your feed that implied otherwise. Miasma did not touch RPMs, the RHEL package set, dnf, yum, or any signed system content. It compromised the @redhat-cloud-services npm scope — the JavaScript/TypeScript frontend packages (frontend-components, chrome, javascript-clients, that family) used to build Red Hat’s cloud console and Insights web UIs. The people exposed are developers and CI runners that npm install those packages. An admin patching a box is not.

Second reflex to kill: there is no CVE here, no CVSS, no fixed version to bump to. This is malware in a published artifact, not a vulnerability in code. “Remediation” is not npm update. It’s purge the bad versions, rebuild clean, and rotate every credential the build could reach. If you go looking for a patch you will waste the afternoon.

With that out of the way — the interesting part of this incident isn’t the worm. It’s that the worm shipped with honest paperwork.

What landed, and when

The campaign names itself: “Miasma: The Spreading Blight.” Per Wiz Research, who found it, it’s a fork of the Mini Shai-Hulud npm worm — same DNA, Greek-myth reskin instead of the Dune theming. Roughly 96 malicious versions across 32 packages in the @redhat-cloud-services scope.

Download volume is where the sources diverge, so I’ll give you the spread rather than the scary number: somewhere in the range of 80,000 to 117,000 weekly downloads depending on who’s counting and how. Wiz cites ~80,000 in one place and ~116,991 elsewhere; BleepingComputer says ~117,000; The Register’s headline rounds to 80K. The point isn’t the exact figure. The point is that these are public packages with real pull, which matters a lot when you get to Red Hat’s framing later.

Injection happened June 1, 2026, in two waves — roughly 10:53 and 13:44 UTC — into three RedHatInsights repos. Wiz flagged it the same day, around 13:00 UTC. So the window between first poison and detection was measured in hours, not weeks. Good. But “detected fast” and “every poisoned version pulled from every cache” are different claims, and per Wiz, two malicious versions were still live at the time of their writeup. Don’t tell your team it’s all clear because a vendor blog said “removed.”

How it runs before you see anything

The trigger is the npm preinstall lifecycle hook. The poisoned package.json carries "preinstall": "node index.js", which means the payload fires on npm install — before any of your application code require()s the package, before the dev sees a single thing in their editor. JFrog and Aikido both put the execution there.

The payload itself is a large obfuscated index.js (BleepingComputer puts it around 4.2 MB), a loader that decrypts AES-128-GCM blobs and drops working code to /tmp to execute. I’m not going to walk the deobfuscation — that’s not useful to a defender and the vendor writeups have it if you want depth.

The defense that falls straight out of the trigger is --ignore-scripts:

npm ci --ignore-scripts
# or, persistently:
npm config set ignore-scripts true

Do it in CI. But be honest about what it buys you. It stops the lifecycle-hook path, which is this worm’s entry. It does not stop a malicious module that runs when your code actually imports it at runtime. So ignore-scripts is necessary, it is not sufficient, and anyone selling it as the fix is overselling. Maps cleanly to CM-7 — least functionality, turn off the thing you don’t need, and almost nobody genuinely needs arbitrary preinstall script execution against the public registry in a build pipeline.

The spreading part

This is what earns the “blight” in the name. After it lands, the worm harvests npm tokens, then — per JFrog, with Aikido and Semgrep concurring — works out which packages the stolen token can write to by reading token scopes and package ownership, downloads those tarballs, injects a wrapped copy of itself, adds "preinstall": "bun run index.js" plus a Bun dependency, bumps the patch version, and republishes. Every compromised maintainer account becomes an automated launch point for the next round.

This variant also reads GitHub Actions Runner memory for environment variables and added GCP and Azure identity collectors that the original Mini Shai-Hulud didn’t carry (Semgrep, Wiz). One note on attribution of the mechanics: Wiz’s own writeup foregrounded the Red Hat injection vector and didn’t dwell on the worming. The self-propagation detail is best attributed to JFrog, Aikido, and Semgrep, not Wiz. Sources disagreeing on emphasis is normal; just cite the one that actually carried the claim.

The worst outcome here isn’t the credential the worm grabbed off your runner. It’s the npm or CI token that lets it jump to the next set of packages — yours, maybe — plus the cloud keys that let an operator move laterally off the build host into your account. The credential theft is the enabling step. The propagation is the payload.

The sharp detail: it published with valid provenance

Here’s the part worth slowing down for.

Patient zero was a compromised Red Hat employee GitHub account (Wiz). The attacker pushed malicious orphan commits — commits with no parent, which sidestep the normal review path — into the RedHatInsights repos. Then a minimal malicious GitHub Actions workflow requested an OIDC token with id-token: write and published through npm trusted publishing.

Sit with the consequence. Because publishing went through the legitimate OIDC pipeline, the malicious packages came out the other end carrying valid SLSA provenance attestations (Wiz). The provenance was real. The build genuinely happened in the place and the way the attestation says it did.

So if your supply-chain posture is “we only install packages with provenance,” this walks straight past you. Provenance attests to where and how an artifact was built. It says nothing about whether the build inputs were trustworthy. A compromised pipeline produces honest provenance for malicious output, every time, and the attestation will verify green. That’s not a knock on SLSA — it’s doing exactly what it claims. It’s a knock on anyone who sold provenance as the thing that stops poisoned dependencies. It’s a necessary control. It is not that control.

This is the SR story (SR-3, SR-4, SR-11). Provenance and component authenticity sit in that family, and this incident is a clean demonstration of their boundary. The signature was valid. The supplier was, briefly, the adversary.

What it steals

The credential sweep is broad and consistent across Wiz, JFrog, Aikido, and BleepingComputer. GitHub Actions secrets including GITHUB_TOKEN and ACTIONS_RUNTIME_TOKEN. AWS access keys and session tokens. GCP application-default credentials and service-account key files. Azure service-principal creds and managed-identity tokens. HashiCorp Vault tokens. Kubernetes service-account tokens and kubeconfig. npm and PyPI publish tokens. SSH private keys, Docker registry creds, GPG keys, and any .env files lying around the build.

Exfil is a dead-drop: per JFrog, the malware can create a public GitHub repo under the victim’s own account and commit results to results/results-<timestamp>-<counter>.json, with the repo description set to “Miasma: The Spreading Blight.” That’s sloppy of them and useful to you — it’s a free hunt signal. Search your org’s GitHub for unexpected public repos matching that description or file path. BleepingComputer cites over 309 GitHub repositories created or compromised; treat that as the count of exfil/propagation repos observed, not a clean victim tally.

How bad, and for whom

No CVSS, so reason about it instead of reading a number off a badge. For a host that installed an affected version inside the exposure window with install scripts enabled: critical. Credential theft across cloud, CI, and dev tooling, plus self-propagation. There’s no softening that.

But scope it honestly, because the panic-radius is wider than the blast radius. Exposed means: you pulled a poisoned version between roughly 10:53 UTC June 1 and revocation. Not “all Red Hat customers.” A dnf-only RHEL shop with no Node build is not exposed at all. The risk concentrates on CI/CD runners and developer laptops that build against these frontend packages — and runners are the worse case, because that’s where the cloud OIDC tokens and the npm publish creds live within arm’s reach of a preinstall hook.

On Red Hat’s statement

Red Hat told BleepingComputer they removed the packages and that the affected packages “are strictly limited to internal development, and the malicious code was never published for customer consumption.”

Both halves of that can be true and still leave you exposed. “Never bundled into a shipped, signed customer RHEL product” is a real and meaningful claim. “Never consumed” is not the claim it sounds like, because these were public npm packages pulling 80,000-plus downloads a week. Anyone — a Red Hat dev, a contributor, a downstream project depending on @redhat-cloud-services/frontend-components, a CI job that resolved it transitively — could and did npm install them. “Internal development” describes Red Hat’s intent for the packages. It does not describe who actually ran npm install. If you built against these, the question of what Red Hat intended the package for does not rotate your AWS keys for you.

What to do if you might have installed

No patch, so this is the work.

Start with inventory, and look in the right place. Did any runner, build host, or laptop install an affected @redhat-cloud-services version on or after 2026-06-01? Check lockfiles and the npm cache, not just package.json — transitive resolution and a populated cache will hide an install that the manifest doesn’t obviously show. This is CM-8 territory: you cannot purge what you haven’t enumerated, and an SBOM that’s a quarter stale won’t catch a version that published yesterday.

If a host ran a poisoned install with scripts enabled, assume credential exposure and rotate, don’t audit. The worm already read the secrets; reviewing access logs tells you what happened, it doesn’t close the hole. Rotate CI secrets, cloud keys across AWS/GCP/Azure, npm and PyPI tokens, SSH keys, Vault tokens, kubeconfig — everything the build could touch. That’s IA-5, and it’s the expensive, unglamorous core of the response.

Purge the bad versions and rebuild from a known-good one. I’m deliberately not printing a “safe version” string, because the only trustworthy source for which exact versions are bad is the affected-version tables in the Wiz, JFrog, and Aikido writeups. Versions not on those lists are presumed clean; check against the table, don’t trust a number from a blog post (including this one).

Then hunt. Search GitHub for public repos described “Miasma: The Spreading Blight” or carrying that results-<timestamp>.json path, and review npm publish logs for unexpected patch bumps on packages your org owns — that’s the worm’s republish signature, and it’s how you find out you became a vector rather than just a victim.

Forward fix is the boring list that actually holds: ignore-scripts on by default in CI (CM-7), pinned and locked dependencies, an allowlist gate on what the build is allowed to resolve, dependency scanning that flags known-bad versions (RA-5), and SLSA provenance kept in the toolbox as a necessary-not-sufficient control rather than the thing you lean the whole posture against.

Attribution, hedged

The payload strongly resembles TeamPCP’s Mini Shai-Hulud. TeamPCP open-sourced that framework in May 2026, which means a copycat building on the leaked code cannot be ruled out. Wiz states this caveat plainly and so will I: treat the resemblance as TTP overlap, not a name on the incident. When the framework is public, code similarity is evidence of a framework, not of an author.

The durable lesson isn’t “don’t trust Red Hat’s npm packages.” It’s that a green provenance check and a same-day vendor takedown are both real and both insufficient, and the only control that was going to save you here was the unsexy one — scripts off, creds scoped tight, and a component inventory current enough to tell you what your runners actually pulled on the morning of June 1.

Sources