The second wave of the Shai‑Hulud campaign hit the npm ecosystem on November 24, 2025, and it’s not subtle. Popular packages were briefly trojanized with preinstall scripts that steal developer and CI/CD credentials, dump data into attacker‑controlled GitHub repos, and in some cases attempt cross‑victim replication. If you maintain JavaScript or run Node in production, treat this as an active npm supply chain attack incident: verify exposure, rotate secrets, and harden your publishing path now.
Below is a concise, field‑tested response you can run in hours, not days. I’ll also show how to move your organization to npm Trusted Publishing (OIDC), which cuts the blast radius by eliminating long‑lived tokens entirely.
What actually happened this week?
Between November 21–23, malicious versions of several npm packages were published. On November 24 (UTC), security teams observed a coordinated second wave (“Shai‑Hulud 2.0”) using new payload files (for example, filenames like setup_bun.js and bun_environment.js) to harvest tokens and environment data during the preinstall lifecycle. Stolen material was then pushed to quickly created GitHub repositories labeled with the campaign’s name, while some victims saw malicious workflows injected to exfiltrate additional secrets.
This builds on the first wave disclosed in mid‑September 2025 and the September 23 CISA alert. The tradecraft hasn’t changed in spirit—compromised maintainer credentials, malicious install scripts, secrets harvesting—but the targets broadened and the automation is faster. If you think “we only use these packages in dev,” remember that developer machines and build agents often hold cloud keys, npm publish tokens, and GitHub PATs. That’s why the attacker goes after you, not just your servers.
Fast triage: a 90‑minute checklist your team can run today
Here’s how I’d drive an on‑call response if your org uses npm at any meaningful scale. Timeboxes are aggressive but realistic for a coordinated team.
0–15 minutes: Freeze and scope
- Announce a temporary internal freeze on publishing npm packages and rotating production credentials without coordination. You want controlled change, not chaos.
- Lock down CI secrets editing to a small response group. Disable self‑hosted runners that don’t enforce ephemeral containers.
- Turn on organization audit logging for GitHub and your cloud accounts if it’s not already enabled.
15–45 minutes: Hunt the obvious IOCs
Run these quick checks across developer machines and build agents:
- Search repos and
package-lock.jsonfor suspicious versions published between Nov 21–23 if they map to packages named in current advisories. - Scan the dependency tree for packages containing unexpected
preinstall/installscripts. In monorepos, don’t forget workspace packages. - On GitHub, search your org and user accounts for repos named with the campaign label, branches named after it, or recently added workflows with exfiltration‑like names. Review Actions usage spikes from Nov 21 onward.
- On build agents, look for stray files commonly associated with the malware (for example,
cloud.json,contents.json,environment.json, or similar dumps under temp directories). Review shell history around package installation events.
45–75 minutes: Contain and rotate
- Revoke any npm classic tokens still lingering. As of November 19, classic tokens are revoked platform‑wide; if you still rely on them locally, you’ve already broken builds and increased risk.
- Invalidate GitHub PATs used in automation. Replace with repository‑ or environment‑scoped short‑lived tokens, or better: remove them by moving publishes to OIDC (below).
- Rotate cloud access keys for build identities touched by Node projects. Prioritize keys with broad IAM roles and any keys used by self‑hosted runners.
75–90 minutes: Verify and communicate
- Confirm that malicious package versions are removed or pinned away in
package-lock.json/pnpm-lock.yaml/yarn.lock. - Ship a short internal note: what happened, what you rotated, what’s frozen, and what’s next.
Need a deeper, step‑by‑step remediation mindset? See our earlier rapid incident guidance for library bugs like expr‑eval: the rapid fix playbook. The muscles are the same: fast scoping, decisive rotates, then hardening.
Why this npm supply chain attack spreads so well
Three things make Shai‑Hulud particularly costly:
- Install‑time execution: The npm lifecycle runs installer scripts by default. If a package you trust turns, you inherit its scripts. CI amplifies the blast radius.
- Secrets everywhere: Modern builds touch multiple SaaS and clouds. A single developer machine can hold npm publish tokens, GitHub PATs, and AWS keys via local caches and CLIs.
- Automation speed: Once tokens leak, automated workflows can open PRs, inject Actions, republish trojanized packages, or flip private repos public in minutes.
The fix isn’t “audit harder” or “patch faster.” It’s removing long‑lived secrets from the path and making your CI untrusting by default.
Move to npm Trusted Publishing (OIDC) in 30 minutes
Trusted Publishing (general availability as of July 31, 2025) lets npm accept publishes only from CI workflows you authorize, issued via short‑lived OIDC credentials—no persistent npm tokens to steal. It also adds provenance attestations by default.
Minimal GitHub Actions setup
Here’s a hardened pattern we’ve rolled out for clients. It keeps the workflow permissions tight and forces manual approval in a protected environment.
name: release
on:
workflow_dispatch:
push:
tags:
- 'v*.*.*'
permissions:
id-token: write # for OIDC
contents: read
packages: write
env:
NODE_VERSION: 20
jobs:
publish:
runs-on: ubuntu-latest
environment: npm-publish
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
- run: npm ci --ignore-scripts
- run: npm audit --omit=dev --audit-level=high || true
- run: npm test --ignore-scripts
- name: Build
run: npm run build --ignore-scripts
- name: Publish to npm with OIDC
run: npm publish --provenance --access public
Key points:
--ignore-scriptsduring install and build to neuter malicious lifecycle hooks.- OIDC only: No
NPM_TOKENsecret. Your package must be configured as a trusted publisher in npm settings for this repo/workflow/environment. - Manual approval: Put
environmentprotections onnpm-publish. Require review from maintainers and limit who can trigger.
If you’ve been following our coverage of npm token policy shifts, you saw this coming. Our earlier guides on post‑Nov 19 npm token migration and the subsequent CI fixes for the date change walk through the breaking deadlines that removed classic tokens and shortened granular token lifetimes. Shai‑Hulud just turned “we should do this” into “we must.”
Harden your build: six controls that stop the next wave
You can’t prevent a maintainer compromise upstream, but you can make your environment a dead end.
- Disable install scripts by default in CI. Use
npm ci --ignore-scriptsor the equivalent for Yarn/pnpm. Re‑enable only for packages you explicitly allow. - Pin transitives via lockfiles and audits. Commit and continuously verify lockfiles. Fail builds when lockfile drifts off approved ranges.
- Adopt scoped, short‑lived credentials. Prefer OIDC‑based publishing. If you must keep tokens, make them granular, 2FA‑enforced, and expiring in days—not months.
- Run CI in ephemeral, egress‑restricted sandboxes. For self‑hosted runners, isolate per‑job with clean workspaces and block metadata endpoints by default.
- Monitor for IOC names and suspicious workflows. Alert on unexpected repo creation, branch names tied to known campaigns, and newly added workflow files that run on
pushtomain. - Produce and verify provenance. Require
--provenanceon publishes and verify Sigstore attestations in downstream deployments.
People also ask
How do I check if my org was hit without sifting every repo?
Start with your package registries and CI logs between November 21–24. Look for install attempts of the suspect versions, any failed or unusual preinstall output, and sudden spikes in workflow runs. In GitHub, search across your org for recent repo creations you didn’t expect and for workflow files added in the last 72 hours. If you find even one IOC, treat all builders that touched Node as potentially exposed and rotate credentials.
Does rotating npm tokens alone fix it?
No. If a machine or runner is still compromised—or if you keep long‑lived tokens—attackers can just steal the new ones. That’s why the end‑state is OIDC with trusted publishers and ephemeral credentials, plus --ignore-scripts during installs.
Can I keep using popular packages safely?
Yes, with guardrails. Pin versions, block lifecycle scripts in CI, and verify integrity with provenance. Most maintainers fix fast; our job is to assume occasional compromise and make it a non‑event.
Data points and dates that matter
- September 15–23, 2025: First wave analyzed publicly; CISA issued an alert on September 23.
- November 21–23, 2025: Malicious trojanized versions for the second wave appeared in the registry.
- November 24, 2025: Second‑wave activity confirmed, with new payload filenames seen in the wild and mass creation of attacker‑labeled GitHub repositories.
- November 5 & 19, 2025: npm tightened token policies and revoked classic tokens, pushing maintainers toward granular tokens and OIDC‑based Trusted Publishing.
A practical IOC search kit
Drop these into your shell and SIEM to cover 80% of quick wins:
# 1) Find lifecycle scripts in the current repo
jq '.. | objects | select(has("scripts")) | .scripts | to_entries[] | select(.key|test("install|preinstall|postinstall"))' package.json 2>/dev/null
# 2) Scan lockfiles for suspect date ranges (approximate)
grep -R "2025-11-2[1-3]" node_modules/**/package.json 2>/dev/null || true
# 3) Org-wide: find newly created GitHub repos in the last 5 days
# (Use the GitHub API or your SIEM connector; pseudo-call below)
# gh api orgs/ORGNAME/repos --paginate | jq 'select(.created_at > "2025-11-21T00:00:00Z")'
# 4) Look for unexpected workflow additions on main/default branches
# (Audit via GitHub logs or a code search for *.yml added recently.)
# 5) Hunt common dump artifacts on runners (Linux)
sudo find /tmp -maxdepth 2 -type f -name '*environment*.json' -o -name 'cloud.json' -o -name 'contents.json'
Risk tradeoffs and edge cases
Two gotchas we keep seeing:
- Dev “convenience scripts” in CI. Teams often allow
postinstallto run docs generation or dev‑only tooling on shared runners. That’s effectively running unvetted code on your production build fabric. Move those to local dev or gated jobs. - Monorepo leakage. A compromised package in one workspace can touch shared caches, shared credentials, or sibling apps if your pipeline reuses the same runner or network namespace. Enforce per‑job isolation and wipe workspaces.
What to do next (today and this week)
Today:
- Freeze publishing, run the 90‑minute triage, and rotate any credential that touched Node builds since Nov 21.
- Switch CI installs to
--ignore-scriptsand pin lockfiles. - Stand up Trusted Publishing for your top three packages and prove you can publish without a token.
This week:
- Roll out OIDC publishing org‑wide with environment approvals and provenance verification.
- Move self‑hosted runners to ephemeral containers/VMs with blocked metadata endpoints.
- Codify an SBOM‑driven dependency review in PR checks and add an allow‑list for packages permitted to run lifecycle scripts.
Zooming out
Supply‑chain attacks aren’t going away. But the path to resilience is clear: ephemeral credentials, provenance, strict CI execution, and quick rotations. If you’re not there yet, Shai‑Hulud 2.0 is your forcing function. If you want help pressure‑testing your pipeline or rolling out OIDC across repos, our team does this weekly. Start at software supply‑chain hardening and incident readiness, browse relevant case studies in our portfolio, and subscribe on the blog for follow‑ups as the situation evolves.
One last reminder: this campaign thrives on speed and default settings. Change both, and you turn a headline‑grabbing npm incident into a minor internal postmortem.
