Supply Chain Security

After the Worms: A CI/CD Security Playbook for Developer Credentials in 2026

The 2026 npm and PyPI worms proved that a trusted release pipeline is a credential vault. Here is what IronWorm and Mini Shai-Hulud actually exploited, and how to harden CI/CD before the next one lands.

Nayan Dey
Senior Security Engineer
8 min read

For most of the last decade, the threat model for a build pipeline was theft. Someone phishes a maintainer, grabs a long-lived publish token, and pushes a poisoned release. The fix was obvious in hindsight: rotate the token, add 2FA, move to short-lived credentials. We told ourselves OIDC and trusted publishing closed the door.

The 2026 worms walked through a different door. They did not steal a maintainer's token from a laptop. They hijacked the pipeline itself, mid-run, and used its own trusted identity to publish. That is a fundamentally harder problem, and most CI/CD setups are still built for the old one. This is a look at what actually happened, what it means for how you treat developer credentials, and the hardening that holds up.

What the worms actually did

The headline event was the TanStack compromise, tracked as CVE-2026-45321 with a CVSS score of 9.6. On May 11, 2026, within a roughly six-minute window, 84 malicious package artifacts were published across 42 packages in the @tanstack namespace. The detail that matters: those packages were not pushed by an attacker holding a stolen token. They were published by TanStack's own legitimate release pipeline, using its trusted OIDC identity, after attacker-controlled code took over the runner mid-workflow.

The attack chain, attributed to the threat actor group TeamPCP and dubbed "Mini Shai-Hulud," chained three GitHub Actions weaknesses in sequence. An attacker forked the repository and opened a pull request that triggered a pull_request_target workflow, the so-called "Pwn Request." That workflow restored a poisoned Actions cache containing a malicious pnpm store. When a maintainer later merged and the release workflow ran, the poisoned binaries executed inside the trusted runner and read OIDC tokens directly out of the runner's process memory. According to OX Security, the broader campaign reached more than 170 packages across npm and PyPI, with hundreds of millions of cumulative downloads.

It gets worse on the propagation side. Mini Shai-Hulud is a true worm. After stealing credentials from one pipeline, it locates a publishable npm token with bypass_2fa set to true, enumerates every package that maintainer controls, and exchanges a GitHub OIDC token for a per-package publish token to push infected versions of each. It was, reportedly, the first documented npm worm whose malicious packages carried valid SLSA Build Level 3 provenance attestations, because the legitimate pipeline produced them.

In June 2026, a separate Rust-written worm called IronWorm surfaced. Researchers at OX Security described it as affecting at least 36 npm packages with around 32,000 combined monthly downloads, using Tor-based command-and-control and abusing npm's own Trusted Publishing OIDC flow to mint publish credentials from CI runners and self-replicate. TeamPCP activity later extended to PyPI as well, including a worm seeded through the durabletask package.

The uncomfortable lesson: OIDC is not a finish line

A lot of teams adopted OIDC and trusted publishing believing it solved credential theft. In one sense it did. There is no long-lived secret sitting in a CI variable to leak. But OIDC moves the trust, it does not remove it. The runner still ends up holding a short-lived, fully privileged token in memory for the duration of the job. If an attacker is executing code inside that job, OIDC hands them exactly what a stolen static token would have, with the bonus that the resulting publish looks perfectly legitimate, provenance and all.

So the real lesson is not "OIDC is broken." It is that the boundary you have to defend is the runner's execution context during the publish step. Anything that can run code in that context, a poisoned cache, a malicious dependency installed during the build, a compromised Action, a Pwn Request, can borrow your identity. Short-lived credentials limit the blast radius in time. They do nothing about an attacker who is already inside the window.

Isolate the install step from the publish step

The single most consequential pattern in these incidents is that untrusted code ran in the same context that later held publish credentials. Package installation runs arbitrary lifecycle scripts. Treat npm install, pip install, and their cousins as untrusted code execution, because that is what they are.

Practically: split your pipeline so dependency installation and the build of untrusted inputs happen in a job that has no path to a publish token. Run installs with scripts disabled where you can, for example npm install --ignore-scripts, and vet the packages that genuinely need lifecycle hooks. The job that actually mints the OIDC token and publishes should do almost nothing else: take a verified, already-built artifact and ship it. The fewer lines of code that execute while a publish credential is reachable, the smaller the target.

Be especially careful with pull_request_target and equivalents in other systems. That trigger exists to give workflows write access on pull requests, which is precisely why it is dangerous on untrusted forks. If you do not have a hard reason to run privileged logic against fork contents, do not.

Scope tokens down and keep them short

Even granting that an attacker may reach the runner, the damage depends on what the credential can do. The worms thrived on broad, reusable permissions: a single maintainer token that could publish every package they owned, and tokens that bypassed 2FA so automation could run unattended.

The defensive posture is the opposite of convenience:

  • Prefer per-package or per-repository scoped credentials over account-wide tokens. A worm that enumerates "every package this identity can publish" should find a list of one.
  • Keep credential lifetimes as short as the job allows. OIDC-minted tokens that live only for the publish step are far better than granular tokens parked in a vault.
  • Eliminate bypass-2FA tokens for anything that touches a registry. After the May waves, npm invalidated granular access tokens that bypass 2FA precisely because they were the worm's propagation vector.
  • Inventory your long-lived tokens and treat each one as a finding, not a fact of life. The ones nobody remembers creating are the ones that hurt.

Put a human gate in front of publish

The most important change to come out of these incidents is structural. In response, GitHub moved npm toward a staged publishing model, merged into the npm CLI in version 11.15.0. Under it, an automated publish does not go live immediately. The tarball lands in a staging queue, and a human maintainer must approve the release with an MFA-verified step, via npm stage approve or through the website, before it becomes installable. Automated pipelines and OIDC tokens cannot complete that approval.

This is the right shape of defense. It accepts that the runner may be compromised and inserts a control the runner cannot satisfy on its own. Even if an attacker drives your pipeline to publish, the release stalls at a gate that requires a person and a second factor. If your ecosystem offers a staged or hold-and-approve publishing mode, turn it on. If it does not, approximate it: require a manual approval environment on the publish job, with the approvers being people who are not the automation.

Control egress, because the payload has to phone home

Credential theft is only useful if the stolen secrets get out, and self-propagation requires reaching a registry and a command-and-control endpoint. IronWorm leaned on Tor for C2; Mini Shai-Hulud exfiltrated to attacker infrastructure and republished to public registries. A build runner almost never has a legitimate reason to make arbitrary outbound connections.

Apply default-deny egress on CI runners. Allow the package registries, your artifact store, and your source host, and block the rest. Anomalous outbound traffic from a build, a connection to an unknown host, a Tor entry node, a paste service, is one of the clearest signals you will get that a job has been hijacked, and an egress allowlist turns that signal into a hard stop rather than an after-the-fact alert.

How Safeguard Helps

Safeguard treats the pipeline as part of the supply chain, not a trusted black box. Our policy gates can hold a release until provenance and attestation checks pass, so a publish that carries valid-looking SLSA provenance but originated from a hijacked runner still has to clear a gate the runner cannot satisfy. Our AIBOM and provenance tooling track what actually went into a build, our Multi-Agent TAOR Deep Think AI Engine verifies findings so the few alerts you act on are real ones rather than a wall of noise, and the platform is model-agnostic, so engines like Anthropic Mythos or OpenAI Daybreak plug in as components beneath the verification layer. If you want a hard look at where your CI/CD credentials and publish paths are exposed, reach out.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.