- The modern npm attack targets publishers and CI, not typos: steal a token, publish malicious versions of every package that token can reach, and repeat.
- In May 2026, packages carried valid SLSA Build Level 2 provenance and were still malicious. Provenance shows where a package was built. It says nothing about whether that build was trustworthy.
- npm v12 (July 2026) no longer runs dependency install scripts or resolves git and URL dependencies by default. That removes the most common execution path.
- Publishers should move to trusted publishing (OIDC), drop long-lived tokens, and treat release workflows, especially
pull_request_target, as production infrastructure.
What happened to the npm ecosystem in 2025 and 2026?
| When | Incident | How it worked |
|---|---|---|
| 8 Sep 2025 | chalk and debug compromise | A maintainer's account was taken over through a phishing "2FA reset" email from a look-alike domain (npmjs.help). Malicious debug@4.4.2 and chalk@5.6.1 swapped crypto wallet addresses in browsers. The debug advisory is CVE-2025-59144. |
| Sep 2025 | Shai-Hulud, first wave | A self-replicating worm stole GitHub tokens and cloud keys, then used stolen npm tokens to inject itself into the victim's other packages and republish them. CISA reported more than 500 affected packages. |
| Nov 2025 | Shai-Hulud 2.0 | A preinstall script ran setup_bun.js and bun_environment.js, harvested secrets with TruffleHog, registered a self-hosted GitHub Actions runner named SHA1HULUD, and fell back to destroying data. |
| May 2026 | Mini Shai-Hulud | It started with a pull_request_target "pwn request", poisoned a pnpm cache, and pulled an OIDC token from runner memory. It hit 42 @tanstack packages and spread to more than 170, all with valid SLSA Build L2 provenance. |
| Aug 2026 | ChainDrop variant | A Shai-Hulud variant hit keyv, flat-cache, cacheable-request, and related packages, more than 1,300 versions in total, according to Singapore CSA advisory AD-2026-009. |
Why did valid provenance not stop the May 2026 wave?
Provenance is a signed statement of which workflow built a package, from which source. In the May 2026 wave, the attacker got code running inside the legitimate release workflow, so the provenance was accurate: the real pipeline built the malicious package.
SLSA's own analysis is precise about the gap. Build Level 2 means a hosted build service signs the provenance. Build Level 3 adds isolation between builds and protects signing material from the build steps, which is the property that would have blocked this attack. Provenance is necessary, but on its own it does not show the build was trustworthy.
What did npm and GitHub change?
- 31 Jul 2025: trusted publishing with OIDC became generally available, with provenance generated by default.
- 29 Sep 2025: the default lifetime of write tokens dropped from 30 to 7 days (maximum 90), classic tokens were revoked by mid-November 2025, and new TOTP 2FA setups were disabled in favour of FIDO-based 2FA.
- 8 Jul 2026, npm v12: dependency lifecycle scripts (
preinstall,install,postinstall) and implicitnode-gypbuilds no longer run unless explicitly allowed throughallowScripts. Git and remote-URL dependencies are no longer resolved by default. - 31 Jul 2026: granular tokens that bypass 2FA now need interactive 2FA for sensitive account changes, and are planned to lose direct publishing rights.
- 3 Sep 2026: a package can have multiple trusted publishing configurations.
How should teams that consume packages harden installs?
Most organisations consume far more packages than they publish, and install time is where these worms execute.
- Upgrade to npm v12 behaviour and keep scripts off. Review which dependencies genuinely need install scripts, and allow only those:
# review pending install scripts and record an explicit allowlist in package.json
npm approve-scripts --allow-scripts-pending
# CI installs should be reproducible and lockfile-only
npm ci
- Install from the lockfile only in CI, and review lockfile changes like code. New transitive dependencies are a signal.
- Add a cool-down period. Many compromised versions were caught and removed within hours to days. A policy that waits a few days before adopting new versions, through your registry proxy or update tooling, filters out most of them.
- Restrict egress from build agents. CISA's guidance for the first wave specifically called out traffic to
webhook.site. Build machines rarely need arbitrary outbound internet access. - Keep secrets out of install steps. Cloud keys and tokens should not be in the environment while dependencies install.
How should maintainers protect publishing?
- Use trusted publishing so releases authenticate with a short-lived OIDC identity from a specific workflow, and delete long-lived automation tokens.
- Require phishing-resistant 2FA (security keys or passkeys) for every maintainer. The first compromise in this series started with a phishing email.
- Isolate the release workflow. Run it only on protected branches or tags, behind a protected environment with required reviewers, and never share caches with workflows that run untrusted pull request code.
- Never check out untrusted code in
pull_request_target. That trigger runs with repository secrets and write permissions.
# Dangerous: privileged trigger + attacker-controlled code
on: pull_request_target
jobs:
test:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # runs fork code with secrets
- run: npm install && npm test
# Safer: untrusted code runs without secrets; publishing is a separate, gated job
on: pull_request
permissions:
contents: read
# Release job using trusted publishing
on:
push:
tags: ['v*']
permissions:
contents: read
id-token: write # OIDC identity for npm trusted publishing
jobs:
publish:
environment: release # protected environment with required reviewers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<pinned-commit-sha>
- uses: actions/setup-node@<pinned-commit-sha>
with:
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm publish
Pin third-party actions to full commit SHAs rather than tags, and give the default GITHUB_TOKEN the smallest permissions each job needs.
How do you check whether you were affected?
- Search lockfiles and artifact caches for the affected package versions in the CISA, Microsoft, and CSA advisories listed below.
- Look for
setup_bun.jsorbun_environment.jsin installed packages, and for any self-hosted runner namedSHA1HULUDin your GitHub organisation. - Review your GitHub organisation for unexpected new repositories or workflows, and for workflow runs you did not trigger.
- If an affected version ever ran on a machine, rotate every credential that machine could reach: npm, GitHub, cloud, and CI secrets. Then check audit logs for their use.
How we test this: our software supply chain security review and DevSecOps review examine your registries, lockfile policy, CI triggers, runner isolation, secret exposure, and release signing, then show you the shortest path from a malicious dependency or pull request to your production credentials.
Sources
- CISA: Widespread Supply Chain Compromise Impacting npm Ecosystem (23 Sep 2025)
- GitHub Advisory: CVE-2025-59144 (debug)
- Microsoft: Shai-Hulud 2.0 guidance
- SLSA: Mini Shai-Hulud, what SLSA can and cannot do
- Cyber Security Agency of Singapore: AD-2026-009
- GitHub: npm trusted publishing with OIDC is generally available
- GitHub: Strengthening npm security (token changes)
- GitHub: npm v12 install-time security
- SLSA specification v1.2
