npm Package Security: Best Practices for 2026
Supply chain attacks on npm packages grew 150% between 2023 and 2025. The event-stream incident, ua-parser-js hijack, and colors.js sabotage proved that a single compromised package can affect millions of projects.
In 2026, npm security isn't optional — it's survival. Here's a practical guide to protecting your project.
The Threat Landscape
Common Attack Vectors
- Typosquatting — Malicious packages with names similar to popular ones (
lodasinstead oflodash) - Account takeover — Attackers compromise a maintainer's npm account and publish malicious versions
- Dependency confusion — Internal package names that overlap with public npm packages
- Malicious install scripts — Packages that run code during
npm installviapostinstallscripts - Protestware — Maintainers intentionally adding destructive code to their own packages
Real Impact
- event-stream (2018): Backdoor targeting a Bitcoin wallet — 8M weekly downloads affected
- ua-parser-js (2021): Cryptominer injected into a package with 8M weekly downloads
- colors + faker (2022): Maintainer sabotaged their own packages used by thousands of projects
- @rspack/core (2025): Supply chain attack via compromised npm tokens
Essential Security Practices
1. Audit Dependencies Regularly
Run npm audit on every CI build. Don't just check — enforce it.
# Fail CI on high/critical vulnerabilities
npm audit --audit-level=high
# Or with pnpm
pnpm audit --audit-level high
Better yet, use automated tools:
- Socket.dev — Detects supply chain risks that
npm auditmisses (typosquatting, install scripts, network access) - Snyk — Deep vulnerability scanning with fix suggestions
- GitHub Dependabot — Automated PRs for vulnerable dependencies
2. Lock Your Dependencies
Always commit your lock file (package-lock.json, yarn.lock, or pnpm-lock.yaml). In CI, use the exact versions from the lock file:
# npm — uses package-lock.json exactly
npm ci
# pnpm — uses pnpm-lock.yaml exactly
pnpm install --frozen-lockfile
# yarn — uses yarn.lock exactly
yarn install --immutable
Never run npm install in CI. Use npm ci — it's faster and ensures reproducible builds.
3. Pin Exact Versions
Semver ranges (^1.2.3, ~1.2.3) allow automatic updates that could introduce malicious code. For critical dependencies, pin exact versions:
{
"dependencies": {
"express": "4.21.2",
"next": "15.1.4"
}
}
Use .npmrc to make exact versions the default:
# .npmrc
save-exact=true
4. Disable Install Scripts for Unknown Packages
Many supply chain attacks use postinstall scripts to run malicious code during installation. Block them by default:
# .npmrc
ignore-scripts=true
Then whitelist trusted packages that need install scripts:
// package.json (pnpm)
{
"pnpm": {
"onlyBuiltDependencies": ["sharp", "bcrypt", "esbuild"]
}
}
5. Use npm Provenance
npm now supports package provenance — cryptographic proof that a package was built from a specific source repository and commit. Look for the provenance badge on npmjs.com.
# Publish with provenance (in GitHub Actions)
npm publish --provenance
When evaluating packages, prefer those with provenance attestations. Check this on PkgPulse — our health scores factor in security practices.
6. Review New Dependencies Before Installing
Before running npm install some-package, check:
- Source code — Is the repository linked and does the code match what's published?
- Maintainers — Who publishes this package? Are they known?
- Install scripts — Does it run scripts during install? (
npm show some-package scripts) - Dependencies — How many transitive dependencies does it pull in?
- Health score — Check PkgPulse for a health assessment
# Check package info before installing
npm info some-package
# Check for install scripts
npm show some-package scripts
# See all transitive dependencies
npm explain some-package
7. Use Scoped Packages for Internal Code
Prevent dependency confusion attacks by using scoped packages for internal/private code:
{
"name": "@mycompany/auth-utils",
"version": "1.0.0",
"private": true
}
Configure your .npmrc to route scoped packages to your private registry:
# .npmrc
@mycompany:registry=https://npm.mycompany.com
8. Enable 2FA on npm Accounts
If you publish packages, enable two-factor authentication on your npm account. Require it for publishing:
npm profile enable-2fa auth-and-writes
This prevents account takeover attacks — one of the most common vectors for injecting malicious code into popular packages.
9. Monitor for Vulnerability Disclosures
Set up automated monitoring:
- GitHub Dependabot alerts — Enable on every repository
- Snyk monitoring — Continuous scanning of your deployed dependencies
- Socket.dev GitHub App — Alerts on suspicious dependency changes in PRs
- npm audit signatures — Verify package integrity
10. Have an Incident Response Plan
When (not if) a dependency is compromised:
- Identify affected projects —
npm ls vulnerable-packageacross all repos - Pin to last known good version — Immediately update lock files
- Check for data exfiltration — Review network logs from CI/CD
- Rotate secrets — Any environment variables or tokens accessible during builds
- Communicate — Notify your team and users if applicable
Automated Security Pipeline
Here's a GitHub Actions workflow that enforces security on every PR:
name: Security Check
on: [pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: npm audit
run: npm audit --audit-level=high
- name: Check for known malicious packages
uses: nicolo-ribaudo/checklocks@v1
- name: License compliance
run: npx license-checker --failOn "GPL-3.0;AGPL-3.0"
Evaluating Package Security on PkgPulse
When comparing packages on PkgPulse, look for these security indicators:
- Maintenance frequency — Regular updates indicate active vulnerability patching
- Dependency count — Fewer dependencies = smaller attack surface
- Health score — Factors in security practices, maintenance, and community size
- Download trends — Sudden spikes or drops can indicate issues
Conclusion
npm security in 2026 requires active defense — auditing, pinning, monitoring, and reviewing. The JavaScript ecosystem's strength (massive package variety) is also its vulnerability (massive attack surface).
Start with the basics: npm ci in CI, npm audit on every build, and save-exact=true in .npmrc. Then layer on Socket.dev or Snyk for deeper analysis. Your future self will thank you.