DEV Community

Cover image for Your node_modules Folder Is a Security Nightmare
Polliog
Polliog

Posted on

Your node_modules Folder Is a Security Nightmare

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:

  1. Ran during preinstall (before you even finish typing)
  2. Stole GitHub/npm tokens from your machine
  3. Used stolen tokens to backdoor OTHER packages you maintain
  4. Those packages infected downstream users
  5. 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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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 (literally return 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/*"
Enter fullscreen mode Exit fullscreen mode

Yarn (v4.10+):

# .yarnrc.yml
minimumReleaseAge: 4320  # 3 days
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Workflow:

  1. Packages go to your private registry first
  2. Security team vets them
  3. Approved packages become available internally
  4. 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:

  1. 3-day cooldown on all packages (pnpm config)
  2. npm ci in CI/CD (no npm install)
  3. Dependency scanning in pipelines
  4. 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:

  1. Use pnpm/Yarn with 3-day cooldown
  2. Commit lock files
  3. Run npm audit regularly
  4. Use npm ci in CI/CD

For production apps:

  1. Everything above, plus:
  2. SBOM scanning (Dependabot, Snyk, Socket)
  3. Scoped npm tokens
  4. Regular dependency audits
  5. Monitor GitHub for anomalies

For enterprises:

  1. Everything above, plus:
  2. Private npm registry
  3. Security team vets packages
  4. 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:


These attacks are real. The numbers are real. The threat is ongoing. Stay safe out there.

Top comments (0)