In September 2025, a developer named Josh Junon received an email from support@npmjs.help.
It looked official. Urgent 2FA update required. He clicked.
Within hours, 18 of the most popular npm packages, with 2.6 billion weekly downloads, were compromised. Packages like chalk and debug that are in basically every Node.js project.
The malware stole credentials, hijacked cryptocurrency transactions, and spread like a worm to other packages.
This wasn't an isolated incident. It was one of three major npm supply chain attacks in 2025.
Your node_modules folder is a security nightmare. Here's why, and what you can do about it.
The 2025 Attack Wave (Real Numbers)
Attack #1: S1ngularity (August 2025)
Target: Nx packages
Method: Stolen GitHub Actions token
Impact: 2,349 credentials stolen from 1,079 developer machines
Duration: 4-hour window
The malware ran during npm install, searched for credentials, and exfiltrated everything to attacker-controlled GitHub repos.
Attack #2: Chalk/Debug Compromise (September 2025)
Target: 18 core packages (chalk, debug, supports-color, ansi-regex, etc.)
Method: Phishing attack on maintainer
Impact: 2.6 billion weekly downloads exposed
Duration: ~2.5 hours before removal
The phishing email came from npmjs.help (notice the .help TLD). Professional, urgent, AI-generated.
The malware hooked browser APIs to intercept crypto transactions:
- Monitored
window.ethereum,fetch,XMLHttpRequest - Replaced wallet addresses mid-transaction
- Supported ETH, BTC, SOL, TRX, LTC, BCH
- 280+ hardcoded attacker addresses for redundancy
Attack #3: Shai-Hulud 2.0 (November 2025)
Target: 796+ packages
Method: Self-replicating worm
Impact: 132 million monthly downloads
Duration: ~12 hours
This was the nightmare scenario. A true worm.
How it worked:
- Ran during
preinstall(before you even finish typing) - Stole GitHub/npm tokens from your machine
- Used stolen tokens to backdoor OTHER packages you maintain
- Those packages infected downstream users
- Repeat exponentially
Dead man's switch: If it couldn't exfiltrate data, it wiped your entire home directory. Not just project files, everything owned by your user.
Why node_modules Is Uniquely Vulnerable
Size and Complexity
Average project stats:
- 6 direct dependencies → 28 transitive dependencies
- 256MB-700MB node_modules folder (typical)
- Projects regularly exceed 1,000+ packages
You install one package. You get hundreds.
Executable Code During Install
Unlike other ecosystems, npm runs code during installation.
{
"scripts": {
"preinstall": "node setup.js",
"postinstall": "node telemetry.js"
}
}
This runs before you even use the package. On every developer machine. In every CI/CD pipeline.
Malicious packages used:
-
preinstall: Maximum infection surface (Shai-Hulud 2.0) -
postinstall: Classic attack vector (earlier campaigns)
Lack of Standard Library
JavaScript has no mature standard library.
Result: Packages for everything.
Real examples from npm:
-
is-string: 20M+ weekly downloads (literallyreturn typeof value === 'string') -
is-string-and-not-blank: 90K+ weekly downloads -
left-pad: Broke the entire internet in 2016 (11 lines of code)
Every tiny dependency is another attack surface.
Centralized Trust Model
One compromised maintainer = thousands of projects at risk.
In 2025:
- Single phishing email → 2.6 billion downloads compromised
- Single stolen token → worm infection across ecosystem
How npm Finally Responded
After the 2025 attacks, npm made critical changes:
December 9, 2025: Classic tokens killed
Old "classic" npm tokens (never expired, full account access) are now revoked.
New requirements:
- Granular Access Tokens (scoped permissions, 90-day max)
- Session-based Auth (expires quickly)
- Mandatory 2FA for all publish tokens
If you see this banner on npm today (January 2026), that's why.
How to Actually Protect Yourself
1. Dependency Cooldowns (The Nuclear Option)
What it is: Don't install packages newer than X days.
Why it works: Malicious packages are usually detected and removed within 24-48 hours.
Most attacks were removed in 2.5-12 hours. If you wait 3 days, you skip the danger window entirely.
How to enable:
pnpm (v10.16+):
# pnpm-workspace.yaml
minimumReleaseAge: 4320 # 3 days (in minutes)
minimumReleaseAgeExclude:
- "react" # Critical packages you trust
- "next"
- "@my-company/*"
Yarn (v4.10+):
# .yarnrc.yml
minimumReleaseAge: 4320 # 3 days
Trade-off: You're always 3 days behind latest. For most projects, this is fine.
2. Audit Every Dependency
Before installing:
Use Bundlephobia: https://bundlephobia.com
Check:
- Package size (is it reasonable?)
- Transitive dependencies (how many?)
- Last publish date (was it today?)
- Maintainer activity (is it maintained?)
After installing:
# Check for known vulnerabilities
npm audit
# Find unused dependencies
npx depcheck
# Analyze dependency tree
npx madge --image dep-graph.png src/index.js
3. Lock Your Dependencies
Always commit package-lock.json or yarn.lock.
Why: Without lock files, npm install pulls latest versions. If a package was compromised 5 minutes ago, you get the malicious version.
With lock files, you get exact versions you tested.
In CI/CD, use npm ci instead of npm install:
# Bad (installs latest)
npm install
# Good (installs exact versions from lock file)
npm ci
4. Scope Your Tokens
If you publish packages, use granular tokens:
# Create scoped token via npm website
# Permissions: Read & Write for specific package only
# Expiration: 90 days max
Best practice:
- Repository A token → can only publish to package A
- Repository B token → can only publish to package B
Shai-Hulud couldn't have spread if maintainers used scoped tokens.
5. Monitor Your GitHub Account
Check for:
- Unrecognized public repositories (especially named "Shai-Hulud" or "s1ngularity")
- Suspicious commits to your repos
- Unexpected GitHub Actions workflows
- Unknown GitHub Apps or OAuth applications
Real victim pattern:
- Wake up
- Find 20+ new public repos on your account
- Each contains stolen credentials from your machine
- Your packages are backdoored
6. Use SBOM Tools
Software Bill of Materials = inventory of all components.
GitLab Dependency Scanning:
# .gitlab-ci.yml
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
GitHub Dependabot:
Already enabled by default. Check Security tab → Dependabot alerts.
Snyk, Socket, or similar:
Continuous monitoring for new vulnerabilities.
7. The Nuclear Option: Private Registry
Host your own npm mirror:
# Verdaccio (open-source npm proxy)
npm install -g verdaccio
verdaccio
Workflow:
- Packages go to your private registry first
- Security team vets them
- Approved packages become available internally
- Malicious packages never reach developers
Trade-off: Adds process overhead. Only viable for larger teams.
What Actually Worked in 2025
Seattle Times (news organization) shared their approach after Shai-Hulud 2.0:
Their strategy:
- 3-day cooldown on all packages (pnpm config)
-
npm ci in CI/CD (no
npm install) - Dependency scanning in pipelines
- Lock files religiously committed
Result: Zero impact from any 2025 attack.
They got lucky (didn't run npm install during attack windows), but their defenses would have blocked it anyway.
The Uncomfortable Truth
You're trusting hundreds of strangers with root access to your machine.
Every npm install executes code from:
- Maintainers you've never heard of
- Packages you didn't explicitly choose
- Dependencies 5 layers deep
Average project: 28 transitive dependencies per 6 direct dependencies.
That's 28 people who can run arbitrary code on your machine.
Practical Recommendations
For small projects:
- Use pnpm/Yarn with 3-day cooldown
- Commit lock files
- Run
npm auditregularly - Use
npm ciin CI/CD
For production apps:
- Everything above, plus:
- SBOM scanning (Dependabot, Snyk, Socket)
- Scoped npm tokens
- Regular dependency audits
- Monitor GitHub for anomalies
For enterprises:
- Everything above, plus:
- Private npm registry
- Security team vets packages
- Automated supply chain monitoring
The Industry Response
npm (as of January 2026):
- Classic tokens revoked
- Mandatory 2FA for publishing
- Granular access tokens only
- 90-day token expiration
Package managers:
- pnpm/Yarn added
minimumReleaseAge - Improved integrity checks
- Better security defaults
Security vendors:
- Socket, Snyk, GitLab scanning
- Proactive malware detection
- Real-time registry monitoring
Final Thought
2025 was the year supply chain attacks went mainstream for JavaScript.
Three major campaigns. Thousands of packages compromised. Billions of downloads affected.
2026 predictions:
- More attacks (it works)
- Cross-ecosystem spreading (PyPI, Maven, RubyGems)
- Targeting Kubernetes secrets and container registries
- More sophisticated worms
The attacks will get better. Your defenses need to get better too.
Three-day cooldown = free insurance.
Install it now. Before the next attack.
Running Node.js in production? How do you handle dependency security? Drop your setup below.
Resources:
- CISA Alert: https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem
- pnpm security guide: https://pnpm.io/blog/2025/12/05/newsroom-npm-supply-chain-security
- npm security best practices: https://docs.npmjs.com/security
These attacks are real. The numbers are real. The threat is ongoing. Stay safe out there.
Top comments (0)