Thirty-two packages inside Red Hat’s official @redhat-cloud-services npm scope shipped malicious versions on June 1, 2026, each embedding a credential-stealing worm in a preinstall hook. The packages were published through Red Hat’s own GitHub Actions OIDC trusted-publisher pipeline. A single compromised GitHub account was enough to inherit that workflow’s publishing authority and push weaponized builds to an enterprise vendor namespace that downstream teams had every reason to treat as trusted.
What happened
On June 1, 2026, malicious versions of 32 packages under the @redhat-cloud-services npm scope appeared on the npm registry. The affected packages include patch-client, frontend-components, rbac-client, compliance-client, host-inventory-client, insights-client, and vulnerabilities-client, plus 25 others. Red Hat issued advisory RHSB-2026-006 the same day, confirming the scope of the compromise.
The final count came to 96 malicious versions across those 32 packages. [Updated June 2026] Estimates of the exposed install base vary by source: Wiz put the affected scope at roughly 80,000 weekly downloads, while other trackers counted closer to 117,000. The discrepancy is a snapshot-timing artifact, not a contradiction; either way the number is large enough that “nobody installs these” is not a defense. Security researchers named the campaign Miasma, after the string Miasma: The Spreading Blight planted in the attacker-controlled repository descriptions.
These are frontend JavaScript libraries consumed by Red Hat’s Hybrid Cloud Console at console.redhat.com. They are not the managed OpenShift or Ansible Automation Platform cloud services; the blast radius is the npm build toolchain of any team pulling these clients directly into its own JavaScript build, not the hosted console itself.
The version-numbering pattern
Each compromised package received two or three malicious versions following a numbering gap pattern: X.0.N, X.0.N+1, X.0.N+3, with the X.0.N+2 version never published. For patch-client, the compromised versions were 4.0.4, 4.0.5, and 4.0.7, according to the GitHub issue tracking the scope-wide compromise. The gap at X.0.N+2 makes the sequence harder to spot in changelogs.
The last known clean version of patch-client was 4.0.3. Version 4.0.4, published at approximately 10:55 UTC on June 1, contained the payload.
The payload
The malicious version of patch-client added a preinstall hook that ran node index.js. The file ballooned from 7,926 bytes to 4,294,136 bytes, roughly a 540x increase. The technical analysis in issue #493 describes a multi-stage execution chain: a numeric character array run through ROT decoding to reveal the next stage, then two AES-128-GCM payloads (one a Bun bootstrapper, one the main worm), then a download of the Bun runtime, then execution from transient /tmp/p*.js files. JFrog’s teardown places Miasma in the Shai-Hulud lineage: it is a derivative of the open-sourced Mini Shai-Hulud code, not the original September 2025 worm, with the Dune naming swapped for Greek-myth themes. [Updated June 2026]
The worm harvested SSH keys from ~/.ssh, registry credentials from ~/.npmrc and ~/.docker/config.json, AWS keys matching the AKIA and AWS_ prefixes, GitHub tokens (gh_token), npm publish credentials, gists, and roughly 129 environment variables, according to the LinuxSecurity feature on the Miasma campaign. It then self-propagated by publishing to any npm packages the infected victim’s credentials could reach.
The notable change from prior Shai-Hulud variants is reach into cloud identity, not just static secrets. Wiz’s analysis reports new collectors for GCP application-default credentials and service-account key files, plus Azure service-principal credentials and managed-identity tokens, alongside the AWS keys earlier strains already grabbed. On the CI side it reads GitHub Actions secrets including GITHUB_TOKEN and ACTIONS_RUNTIME_TOKEN. The earlier strains focused on extracting secrets from those environments; this one enumerates the identities the host can assume, which points the attacker at the cloud account itself rather than at whatever happened to be in a .env file. [Updated June 2026]
OIDC trusted publishing was the attack vector
The malicious packages were not published with a stolen npm token. They were published through the same GitHub Actions OIDC trusted-publisher configuration that legitimate releases use. Red Hat’s advisory states that a compromised GitHub account was used to push unauthorized commits to RedHatInsights repositories. The workflow was already authorized to publish to the official namespace, so the attacker inherited that authority by controlling the account that could merge into the publishing branch.
This is a different threat model from token theft. OIDC trusted publishing was designed to eliminate long-lived secrets from CI pipelines. But the trust relationship itself became the credential. An attacker who controls a contributor account on a repo with OIDC publishing does not need to find, steal, or rotate a token. The pipeline is already authorized. The attack surface shifted from “protect the token” to “protect every account with push access,” which is a substantially larger perimeter.
The provenance signal made it worse, not better. Because the release ran through the genuine workflow, the workflow requested an OIDC token with id-token: write and the resulting npm artifacts shipped with valid SLSA provenance attestations signed through Sigstore. [Updated June 2026] Provenance verification is the control teams are told to add precisely to catch tampered builds. Here it would have passed: the build really did originate from the named repository’s named workflow. Groundy covered the same failure when Mini Shai-Hulud shipped the first malicious npm package with valid SLSA provenance, and the TanStack incident showed OIDC trusted publishing turning into the attack vector itself. Provenance proves where a build came from. It does not prove the build is benign, and a compromised source repo satisfies it cleanly.
Vendor namespaces are not a trust boundary
The practical damage here is not just the 32 packages. It is the assumption that pulling a dependency from a vendor’s official npm scope is safe by default. Many organizations maintain SBOM policies and CI pinning rules that exempt vendor namespaces from full review. The logic is sound in theory: Red Hat has a security team, a release process, and a reputation. Auditing every version of every @redhat-cloud-services package seems like overhead that a vendor-published scope should let you skip.
That assumption does not survive this incident. The packages came from the vendor’s own namespace, published through the vendor’s own CI pipeline, signed with the vendor’s own OIDC trust. Every structural indicator of legitimacy was present. And the payload was a credential-stealing worm.
What Red Hat says vs. what downstream consumers should check
As of late June 2026, Red Hat’s advisory still states that no customer action is required based on current findings, with its last substantive update dated June 3 and the investigation listed as ongoing. [Updated June 2026] Red Hat and npm have removed the compromised versions from the registry and applied namespace protections. The advisory’s posture has not changed in the weeks since: Red Hat’s position is that no Hybrid Cloud Console build shipped a compromised version, so no product-side action is owed to customers. That holds for the hosted service. It says nothing about the developer workstations and CI runners that ran npm install against the bad versions before they were pulled.
The IOC hashes for patch-client@4.0.4 are tarball SHA256 031BA872D5A84BFB18115F432811E4B45180346A1BAE653F7FD85F918E7BB3A3 and index.js SHA256 DF1732F5BFEC12E066BE44DEE02EC8A243E4868D38672C1B1D065359DD735A14, per the technical breakdown in issue #493.
Treat those hashes as proof of a specific bad artifact, not as a detection strategy. Wiz reports that Miasma generates a uniquely encrypted payload per infection, so each package the worm republishes carries a different hash. [Updated June 2026] The reliable signal is the version, not the digest: any @redhat-cloud-services release published on or after June 1, 2026 should be treated as suspect until proven clean, and the same goes for any internal package a worm-infected runner could have touched. Other campaign markers are more stable than file hashes, including the Miasma: The Spreading Blight repository description and a spoofed google-api-nodejs-client/7.0.0 user-agent string on exfiltration traffic.
“Customer action is not required” is scoped to Red Hat’s own hosted services. Teams that vendored or pinned these npm packages in their own builds are outside that scope and need to verify independently.
What to check today
Lockfiles. Search your lockfiles for any @redhat-cloud-services package version published on or after June 1, 2026. The version-numbering gap pattern means you need to check every version, not just the latest.
Scope whitelists. If your CI configuration or SBOM policy contains a blanket exemption for @redhat-cloud-services or any other vendor namespace, remove it. Vendor scopes can be compromised through the same account-takeover and CI-pipeline attacks as any other namespace.
Runner credentials. If any CI runner installed one of the compromised versions, rotate every credential that runner had access to: npm tokens, GitHub tokens, SSH keys, and AWS keys. The worm’s self-propagation step means the blast radius extends to every package your credentials could publish.
OIDC trust boundaries. If you maintain GitHub Actions workflows with OIDC trusted publishing, audit which accounts have push access to the publishing branch. That access list is now your entire publishing attack surface. Protect it accordingly.
How the campaign moved after disclosure
Miasma did not stop at the June 1 push. Researchers tracking the operation describe it arriving in waves and mutating its execution method each time. JFrog’s analysis documents two bursts on June 1, at roughly 10:53 and 13:44 UTC, followed by a June 4 variant that moved the trigger out of the preinstall script entirely.
That June 4 change is the one worth internalizing. Instead of a preinstall hook, the later payload hid inside a binding.gyp file using node-gyp’s <!(...) command-expansion syntax, which runs shell commands during native-module configuration. The point was evasion: a scanner that inspects package.json for suspicious preinstall/postinstall scripts sees nothing, because the execution lives in a build-config file most tooling does not parse. Blocking lifecycle scripts still helps, since npm’s automatic node-gyp build is itself a lifecycle step, but the move shows the attacker watching which defense everyone reached for first and routing around it.
By June 6 the same operators had branched into a wave that JFrog tracks as Hades, after its Hades - The End for the Damned marker. Hades pushed the technique past npm into PyPI, RubyGems, and Artifactory, validating stolen registry credentials against /api/system/ping and testing write access before republishing. It also added prompt-injection payloads written into AI coding-assistant rule files, aimed at Claude, Codex, Gemini, and Copilot, an escalation Groundy has tracked since the 2026 npm attacks turned AI coding assistants into a supply-chain target. For persistence the worm installs a kitty-monitor systemd unit on Linux and a matching launch agent on macOS. One reported detail is more theater than threat: exfiltration traffic is dressed up to look like calls to api.anthropic.com, hitting a path that returns a plain 404. The infrastructure is not compromised; the hostname is camouflage to blend into outbound logs.
Where this sits in the 2026 npm worm season
This is not an isolated Red Hat problem. The same self-propagating pattern ran through npm repeatedly across late 2025 and 2026, from the original outbreak through Shai-Hulud’s return across more than 300 packages. The throughline is consistent: steal publish credentials at install time, then use them to publish the worm again, so the registry does the distribution. Attribution stays soft. Researchers note technique overlap with the publicly released Mini Shai-Hulud tooling associated with the group calling itself TeamPCP, but because that code was open-sourced, a copycat is at least as likely as the original authors. Treat the lineage as a shared playbook rather than a single actor, and assume the next campaign reuses the parts that worked here: a real vendor namespace, a real CI pipeline, valid provenance, and install-time execution.
Frequently Asked Questions
Does this affect Red Hat managed services like ARO, ROSA, or OpenShift Dedicated?
No. The compromised packages are frontend JavaScript clients consumed by the Hybrid Cloud Console (console.redhat.com) only. Red Hat’s managed cloud services, including ARO, OSD, ROSA, ACS Cloud Service, and AAP Managed, do not pull these npm packages into their build pipelines.
How does this differ from dependency-confusion or typosquatting attacks?
Dependency-confusion exploits registry priority to inject a package that never existed at the internal name. Typosquatting relies on mistyped names. Here, the attacker published malicious versions of the real packages, in the real namespace, through the real CI pipeline. Namespace allowlists and registry pinning, the standard defenses against those other vectors, would not have caught it.
Would npm —ignore-scripts have prevented the worm from executing?
Mostly. The initial payload relied on a preinstall hook running node index.js, and passing —ignore-scripts or setting ignore-scripts=true in .npmrc would have blocked it. That flag also suppresses npm’s automatic node-gyp build, so it covers the June 4 variant that moved execution into a binding.gyp command-expansion. The caveat is detection, not prevention: a scanner that only inspects package.json scripts will not flag the binding.gyp technique even though —ignore-scripts neutralizes it. The tradeoff with the flag is that any legitimate dependency requiring install-time scripts for native module compilation (node-gyp, esbuild, sharp) would also break, so this works as a targeted override rather than a blanket policy.
Were Kubernetes credentials in the worm’s harvest scope?
Yes. The worm read Kubernetes cluster configs from ~/.kube/config alongside SSH keys, Docker credentials, and cloud keys. Teams running CI inside Kubernetes clusters should check whether an infected runner held a kubeconfig with cluster-admin or write-scoped permissions. Those credentials could have been exfiltrated and reused against the cluster independently of the npm publishing chain.
What OIDC publishing controls would have prevented this?
The attack exploited the fact that any account with push access to the publishing branch could trigger an OIDC-authorized release. Requiring signed commits, mandatory review approvals on the publishing branch, and restricting OIDC token claims to specific workflow files and actor identities would narrow the surface from every contributor with push access to approved reviewers on release PRs. GitHub environment protection rules with required reviewers can enforce this today without custom tooling.