The package really was Red Hat's
Between late May and June 1, 2026, a worm called Miasma pushed credential-stealing code into 32 packages under the @redhat-cloud-services npm scope — Red Hat's own namespace, ~117,000 weekly downloads, signed by Red Hat's real publishing pipeline. There was no typosquat to catch and no unknown maintainer to flag. The trust signal was the vendor's name on the scope, and the vendor's name is exactly what the attacker rode in on. Here is why 'prefer reputable publishers' stopped being a defense, and what changes when the agent running the install lives in a per-profile Bromure VM.
The supply-chain attacks we wrote up in May had a tell. A Git URL
where a version range should be. A Bun runtime appearing out of
nowhere. An optional dependency that failed on purpose. Miasma has
no tell. The packages were @redhat-cloud-services/sources-client
and thirty-one of its siblings — genuinely Red Hat's, under Red
Hat's own npm scope, published by Red Hat's own pipeline with a
valid signature. Nothing was faked. The attacker did not need to
fake anything. The name on the scope was the whole exploit.
A coding agent resolving @redhat-cloud-services/vulnerabilities-client
does not hesitate, and neither would you. It is a first-party
dependency from one of the largest enterprise vendors in the
industry. There is no maintainer to vet, because the maintainer is
Red Hat. There is no name to squint at for a transposed letter,
because the name is spelled exactly the way it should be. Every
heuristic a careful developer or a careful agent applies before
running npm install returns green. So the install runs, and a
preinstall hook fires, and the hook is a 4.2-megabyte blob of
obfuscated JavaScript that starts reading the filesystem for keys.
The whole incident is a demonstration of one uncomfortable fact: reputable publisher is not a security control. It feels like one. Most of the supply-chain advice of the last three years leans on it. And on May 29 it bought exactly nothing.
What Miasma did.
The campaign — the string Miasma: The Spreading Blight first
appears in a commit dated May 29, 2026, per
OX Security —
was caught by Aikido and OX Security and
later analyzed by Socket, JFrog, Wiz, ReversingLabs, Microsoft and
others. BleepingComputer
and The Hacker News
both covered it on June 1.
The shape, stripped to its mechanics:
- The entry point was a compromised Red Hat employee GitHub
account, used to push malicious commits into the
@redhat-cloud-servicessource repositories. - A GitHub Actions workflow carried an
_index.jsscript that authenticated to npm's trusted-publishing endpoint using an OIDC token — the same keyless mechanism npm now recommends over long-lived publish tokens. From npm's side, Red Hat's CI published Red Hat's packages. The signature was real. - The published packages carried a
"preinstall": "node index.js"hook and an obfuscated payload of roughly 4.2 MB. - On install, the payload swept for GitHub Actions secrets, AWS
credentials, Google Cloud credentials, Azure service principals,
HashiCorp Vault tokens, Kubernetes service-account tokens, npm and
PyPI publishing tokens, SSH keys, Docker credentials, GPG keys,
and
.envfiles — then encrypted and exfiltrated whatever it found. - It self-propagated by using the stolen access to commit through
the GitHub API, reading
action.ymlfiles over GraphQL and writing new workflows back through mutations so the changes appeared, in Red Hat's own words on the commit log, verified and signed.
In total, 32 packages across 96 versions were hit, packages with
roughly 117,000 weekly downloads, and the broader campaign
touched 309 GitHub repositories. Socket's assessment was that
this is "effectively a Mini Shai-Hulud campaign: it uses the same
core tactics of install-time execution, credential harvesting, CI/CD
targeting, encrypted exfiltration, and potential downstream
propagation." Red Hat's statement was that "the packages are
strictly limited to internal development, and the malicious code was
never published for customer consumption" — which is true, and also
not much comfort to the developers and CI runners that pulled
@redhat-cloud-services/* as a transitive dependency in the window
before the packages were yanked.
The defense everyone recommends is the one that broke.
We wrote about a similar worm
three weeks ago — the TanStack
compromise, where the giveaway was a prepare script hanging off a
pinned Git URL and a Bun runtime that appeared from nowhere. The
honest lesson from that post was: don't trust the lockfile, don't
trust the signature. Miasma is the next turn of the same screw, and
it is worth being precise about what is different, because the
difference is the whole point.
The standard supply-chain hygiene stack has three rungs. Pin your
versions. Verify provenance. Prefer reputable publishers. Miasma
walks straight through all three. Pinning does nothing, because the
malicious versions are the published versions. Provenance does
nothing, because the provenance is valid — the OIDC trusted-publish
flow genuinely was Red Hat's CI, and downstream the worm minted
workflow commits that GitHub itself marked verified and signed. And
the third rung, prefer reputable publishers, is not just defeated
here — it is the attack surface. The reputation of the
@redhat-cloud-services scope is the reason the packages get pulled
without a second look. The more trusted the namespace, the more
useful it is to whoever takes it over.
There is no version of "read the package more carefully" that catches this. The package is fine. The package is Red Hat's. The problem is that install-time code execution with the developer's ambient credentials is the contract, and a trusted name does nothing to change what that code can touch once it runs.
The same install inside Bromure Agentic Coding.
Bromure Agentic Coding runs your coding agent
inside a per-profile Linux VM — its own kernel, its own
filesystem, its own network stack, on Apple's Virtualization
framework. A profile is a coherent scope of work: this client,
this internal product, this open-source library. The agent does
its npm install-ing in there, and the host — your real keychain,
your real cloud credentials, your real SSH keys — is on the other
side of a hardware-enforced boundary the preinstall hook cannot
cross.
The credentials do not live in the profile. They live on the host,
behind a credential broker.
When the agent needs to push a commit or publish a package, it does
not read a token off the guest filesystem — there is none to read.
It asks the broker, over a Unix domain socket, to use a credential
on its behalf. The broker holds the real GitHub App private key,
mints a short-lived installation token scoped to the repo the agent
was already working in, and — for a profile configured to require it —
surfaces an authorization prompt the developer answers on the host
before the request goes out. The token is minted and attached to the
outbound request entirely on the host side; it never enters the
guest's memory or disk. The principle, which is as old as
ssh-agent: broker the credential's use, never its value.
So walk Miasma's sweep through that boundary. node index.js reads
~/.aws/credentials and finds a stub or nothing. It reads ~/.npmrc
and finds no publish token. It reads the environment for
$GITHUB_TOKEN and finds nothing to steal — the installation token
is minted and spent on the host, never written into the guest, and
the broker only mints one at all when the developer answers the
authorization prompt. The GPG keyring,
the SSH private key, the Vault token, the kubeconfig: host-side,
brokered if exposed at all, absent if not. The 4.2 MB payload runs
to completion exactly as designed. It just exfiltrates a guest that
was never holding the developer's keys.
The push the proxy refuses to forward.
Stealing the key is only half of Miasma. The other half is propagation: it used stolen GitHub access to commit poisoned workflows back through the API, and that is what turned one bad install into 309 repositories. The broker handles the theft. Guardrails, added in Bromure Agentic Coding 2.0, handle the misuse.
Guardrails are a host-side policy engine that lives inside the same
MITM proxy the brokered traffic already flows through, so a
compromised agent in the guest cannot route around them. Each request
is classified by what it actually does to the resource, and each
resource — GitHub, AWS, Kubernetes, Docker registries, DigitalOcean,
GitLab, Bitbucket, hosted databases — can be set to off, block
destructive, or read-only. Put a profile's GitHub guardrail in
read-only mode and a git push — the git-receive-pack the worm
needs to write its workflow back — returns a hard 403, while
git fetch keeps working. A DELETE against the Kubernetes API, a
manifest deletion in a registry, a Terminate* call to EC2: same
treatment. The agent just sees a normal API failure. Miasma's
propagation step is a write the proxy declines to forward, whether or
not the agent ever got near a real credential.
What about the persistence?
This is where the per-profile model is honest about a cost. Miasma is a worm; its whole ambition is to come back. On a disposable-disk fantasy, you wave that away — the disk is gone after the task. A Bromure profile is long-lived, so a payload that writes itself into a startup script inside the profile can survive into the next agent session in that profile. We are not going to pretend otherwise.
What that persistence inherits, though, is a guest with no host keys and a broker that only speaks in short-lived, scope-limited tokens. The worm wakes up in the same sandbox it died in. It can read the same stubs. It can ask the broker to use a credential for the one repo this profile is authorized for — and the developer still has to answer the authorization prompt for that to go anywhere, with Guardrails free to refuse the push outright. It cannot reach the host keychain, the other profiles, or the developer's cloud credentials, because those were never inside the boundary to begin with. Persistence buys continued presence in a box with nothing in it.
And every bit of that — the preinstall firing, node index.js
loading a multi-megabyte blob, the file written into a startup path,
the egress attempt — lands in the hypervisor-level session
trace. When Aikido publishes
the indicators the next morning, the question "did this profile ever
run Miasma?" is a grep, not an incident-response engagement.
Where this does not save you.
The broker scope is the whole game.
If a profile is provisioned to publish to your npm scope today, and that profile installs Miasma today, the broker will let it publish. Brokering works because the grant is narrow and short-lived. A profile that does not need to publish should not be able to. Scope it on purpose.
It does not review the diff.
Miasma propagated by committing workflows that appeared verified and signed. A read-only GitHub guardrail blocks the push outright — but a profile that legitimately needs to push runs in block-destructive mode, and Guardrails classify by method, not by what is in the diff. Neither isolation nor a method-level guardrail stops an agent from being talked into committing a poisoned workflow it is allowed to push. Read the diff. The trace tells you which diffs to read.
The clipboard is shared by default.
Bromure ships with host/guest clipboard sharing on, because pasting a stack trace into a chat is something humans do all day. For a sensitive profile, isolate the clipboard. The control exists; it is just not the default.
The trace is an audit log, not an IDS.
The session trace records the preinstall, the blob, the egress. It does not, on its own, decide the destination is hostile. It captures enough that once someone names the indicator, your answer is two seconds away.
The next scope is already trusted.
The lesson of the TanStack worm was that the lockfile and the
signature are not defenses. Miasma adds the uncomfortable corollary:
neither is the publisher. The @redhat-cloud-services scope did
nothing wrong by being trusted — being trusted is the entire purpose
of a vendor namespace, and it is exactly what made it worth
attacking. The next campaign will ride in on a scope you trust just
as much, signed just as validly, by a pipeline that was genuinely the
vendor's right up until it wasn't.
You cannot fix that by trusting more carefully. You fix it by
arranging things so that "which scope published this" stops being the
question your keychain depends on. Bromure Agentic
Coding is the configuration where the agent does
its installing inside a per-profile VM, the real credentials stay on
the host behind a broker, and the worst a preinstall hook can do is
exfiltrate a box that was never holding your keys. It is free,
open-source, and shipped today.