· 7 min read

An npm Worm Just Backdoored 32 Red Hat Packages by Abusing Trusted Publishing — Not a Stolen Token. If You Use GitHub Actions OIDC, Read This.

On June 1, 32 packages under the @redhat-cloud-services npm scope shipped a credential-stealing worm. Microsoft Threat Intelligence tracked it as "Miasma," a variant of the Mini Shai-Hulud campaign that's been grinding through npm all spring. Across those 32 packages, 96 poisoned versions went out, with a combined download volume around 117,000 per week.

If you read my note on the open-sourced npm worm two weeks ago, your first instinct is "another supply-chain attack, I've patched my mental model." Patch it again. This one is different in the way that matters, and the difference is the whole lesson: the attackers never stole an npm token. They published through Red Hat's legitimate, signed, trusted-publishing pipeline: the GitHub Actions OIDC workflow that the entire ecosystem, including me, has been telling you to adopt because it kills token theft.

What actually happened

The compromise started upstream, in the RedHatInsights/javascript-clients CI/CD pipeline. Once the attackers had a foothold there, they didn't need to phish a maintainer or leak a token from a .env file. They used the pipeline's own GitHub Actions OIDC trust relationship with npm to publish trojanized versions. Those packages carried authentic provenance signatures. They looked, by every automated check, like real Red Hat releases, because in the cryptographic sense, they were.

The payload ran through an npm preinstall hook: an obfuscated dropper, about 4.29 MB, that executes during npm install, before your application code runs and without any visible warning. On a developer machine it goes after SSH keys, CLI credentials, and browser and wallet data. In a CI environment it does something nastier: it scrapes the GitHub Actions runner's memory for secrets, escalates privileges through passwordless sudo, and then republishes more backdoored packages with forged SLSA provenance to keep spreading. That's the "worm" part. It uses what it steals from one victim's pipeline to poison the next.

Why trusted publishing made this worse, not better

Here's the uncomfortable bit. Trusted publishing exists to solve a real problem. Long-lived npm tokens sit in CI configs, get leaked in logs, get committed to repos, and get stolen. OIDC replaces them with short-lived, workload-scoped credentials minted at publish time, so there's no static secret to steal. That's a genuine improvement, and I'm not telling you to turn it off.

But it doesn't reduce risk. It moves risk. With token-based publishing, the thing an attacker needs is the token. With trusted publishing, the thing an attacker needs is control of the workflow that's trusted to publish, which usually means control of the repository, a branch, or the CI environment itself. You traded a credential you could rotate for an identity baked into your pipeline's trust policy. When that identity is compromised, the attacker doesn't just get to publish. They get to publish with valid provenance, which defeats exactly the signing checks that were supposed to catch a fake.

So the security industry's standard advice, "stop using tokens, use OIDC," is correct and also incomplete. The new sentence is: your CI identity is now production credential surface. Treat the GitHub Actions workflow that can publish to npm with the same paranoia you'd treat a production database password, because in this model, it is one.

The 10-minute Saturday response

This is the part to act on, and it splits into "are you a victim" and "are you exposed."

If your project pulled any @redhat-cloud-services package since June 1, assume compromise. Not "probably fine." Compromise. Rotate every secret that machine or pipeline could see: npm tokens, cloud credentials, SSH keys, GitHub PATs, anything in the CI environment. The worm scrapes runner memory, so secrets that were merely present during a poisoned install are exposed, not just secrets you explicitly used. Pin your dependencies to known-good versions from before June 1 and rebuild from a clean state.

If you weren't hit but you publish via GitHub Actions OIDC (which, if you maintain anything on npm, you increasingly do), spend ten minutes hardening the trust itself. Scope your OIDC trust policy as tightly as npm allows: bind it to a specific repository, a specific workflow file, and ideally a protected environment that requires a manual gate or a specific branch. Make sure a pull request from a fork or a random branch cannot trigger the publish workflow. Turn on required reviewers for the environment that holds publish rights. Add --ignore-scripts to your CI install step so a preinstall hook in a dependency can't execute during your build. None of this is exotic. All of it would have narrowed the blast radius here.

What I'd actually do

Beyond the immediate cleanup, change one habit: stop treating "we switched to trusted publishing" as a box you ticked. It's not a destination. It's a different threat model, and the new threat is your own CI identity. Audit which workflows in your repos can publish, who can trigger them, and what they can reach. For a solo operator that audit takes one afternoon, and the failure mode it prevents (shipping a backdoor to your users under your own valid signature) is about the worst thing that can happen to a one-person reputation.

The honest counter-take: this does not mean tokens were better. They weren't. A token-based Red Hat would likely have been compromised too, and arguably more easily, since the attackers already had pipeline access. Trusted publishing isn't the villain here, and rolling back to long-lived tokens would trade a known-bad failure mode for a worse one. The correct read isn't "OIDC failed," it's "OIDC moved the lock, and most teams adopted it without moving their attention to the new door." Move your attention. The packages will keep coming: Mini Shai-Hulud has been iterating for weeks, and Miasma is just the version that proved enterprise pipelines with perfect provenance are in scope.

Author

Sources

Stay in the Loop

Get new posts delivered to your inbox. No spam, unsubscribe anytime.

Newsletter coming soon. Set PUBLIC_CONVERTKIT_FORM_ID in .env to activate.

Related Posts