On September 15, 2025, ReversingLabs identified Shai-Hulud, a self-replicating npm worm that stole maintainer publishing tokens through phishing emails spoofing npm's MFA-update flow, then used those tokens to auto-publish trojanized versions of every package the compromised account owned. The initial wave hit roughly 187 packages; within days, researchers had confirmed infections across more than 500 packages, including some tied to the CrowdStrike npm ecosystem. CISA issued a public alert on September 23, 2025. A second wave, documented by Microsoft and Datadog in November and December 2025 and referred to as "Shai-Hulud 2.0," expanded beyond npm into other package ecosystems entirely. The operational problem for defenders was never just "we installed a bad package" — the worm harvested developer and CI credentials sitting on disk at install time, which means any secret reachable from a build agent that ran an affected version has to be treated as burned, not just the npm token that got the org into this in the first place. This post is a runbook, not a post-mortem: it walks through what to rotate, in what order, and how to confirm each rotation actually worked before you close the incident.
What should you rotate first?
npm publishing tokens come first, because they are both the confirmed initial-access vector and the credential that lets an attacker keep re-infecting your packages even after cleanup. Revoke every classic and granular npm token tied to affected maintainer accounts immediately, force a password reset, and re-enroll two-factor authentication rather than trusting an existing 2FA state that could itself have been phished. Do this org-wide, not just for accounts you can confirm were hit — Shai-Hulud's phishing lure specifically targeted npm's MFA-update workflow, so any maintainer who interacted with that email is a suspect until proven otherwise. Only after npm-side access is cut off should you move to downstream secrets, because a live npm token can let an attacker republish a "cleaned" package the moment you turn your back. Document the exact revocation timestamp for each token; you'll need it later to determine which CI runs after that point can be trusted and which still ran against a compromised credential set.
What counts as "exposed" and needs rotating?
Treat every secret that was readable from a machine that installed an affected package version as exposed, not just secrets you can prove were exfiltrated. That means CI runner environment variables, cloud IAM access keys (AWS, GCP, Azure), container registry credentials, and any tokens cached by build tools on disk. Because the worm's install-time behavior ran with whatever privileges the build process had, a CI job with broad AWS permissions is a bigger rotation priority than a laptop with none. Pull your build logs and package-lock or SBOM diffs to identify every pipeline that resolved a trojanized version, then cross-reference which secrets those specific pipelines had access to — this is what turns "rotate everything everywhere" into a prioritized, finishable list instead of an open-ended panic. Err toward over-inclusion: a secret that's cheap to rotate and turns out to have been safe costs you far less than a live one you left standing.
In what order do you rotate downstream secrets?
Work outward from blast radius, not from convenience. Cloud IAM keys and service-account credentials with write access to production infrastructure go first, followed by container registry push credentials — both let an attacker re-establish persistence somewhere you might not be watching. Next, rotate CI/CD platform secrets (GitHub Actions secrets, GitLab CI variables) and any third-party API keys build agents could reach, such as Slack webhooks or Stripe keys used in test suites. Last, rotate lower-risk developer-tooling tokens. Alongside rotation, invalidate CI runner caches and re-image build agents rather than trusting a "clean" scan of a machine that already ran attacker-controlled postinstall scripts — a runner's disk state is not a system you patch, it's one you replace. Keep a rotation ledger with old-key deactivation time and new-key first-use time for every credential; that ledger is what you hand to auditors and what lets you answer "are we done yet" with evidence instead of confidence.
How do you verify a rotated credential is actually dead?
Rotation isn't complete until you've confirmed the old credential fails a real call, not just that you clicked "revoke" in a console. For cloud keys, that means attempting a low-privilege authenticated call — an AWS sts:GetCallerIdentity, a GCP projects.get — with the old key and confirming it's rejected, then repeating the call with the new key to confirm it works. Safeguard's secrets scanning does exactly this verification step at scale: it doesn't just pattern-match a string that looks like an AWS key or a GitHub PAT, it makes a live, low-privilege call to the actual issuer to confirm whether a discovered credential is currently valid, across source code, git history, container image layers, and CI build logs. During a worm-scale incident, that's the difference between a spreadsheet of "credentials we think we rotated" and a verified list of which secrets were live, which were already dead, and which rotations still need to happen.
What does npm's trusted publishing change about this playbook?
npm's Trusted Publishing, which went generally available on July 31, 2025, removes long-lived npm tokens from CI entirely by having GitHub Actions or GitLab CI authenticate via OIDC and receive a short-lived, workflow-scoped credential at publish time, with automatic provenance attestation attached to the release. Separately, npm completed the deprecation of non-expiring classic tokens on December 9, 2025; replacement granular access tokens default to a 7-day expiry, cap out at 90 days, and require 2FA. For a team that had already migrated to trusted publishing before Shai-Hulud, there was no standing npm token for the worm's phishing flow to steal in the first place — the credential simply didn't exist between publishes. Migrating your remaining publish workflows to OIDC-based trusted publishing is the single highest-leverage step you can take coming out of this incident, because it converts "remember to rotate this token" into a problem that no longer needs solving.
How Safeguard helps
Safeguard's secrets scanning is built for exactly this triage problem: distinguishing verified, live credentials from unverified pattern matches across source, git history, container layers, and CI build logs, so an incident-response team isn't rotating blind or, worse, marking a still-live key as handled because a scanner flagged it once and moved on. During a worm-scale exposure event, that verification step lets you produce an evidence-backed rotation ledger — which secrets were confirmed live, when they were killed, and when the replacement started working — instead of a best-effort checklist. Combined with SBOM generation and package-lock diffing, Safeguard also helps identify which build pipelines actually resolved a compromised package version, so rotation effort goes to the credentials with real exposure first.