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
- @solana/web3.js (2025): Malicious versions 1.95.6 and 1.95.7 published via a phishing attack on a maintainer, stealing private keys from Solana users
Why 2025-2026 Was a Turning Point
The @solana/web3.js attack in December 2025 changed the industry's perception of npm security. Unlike earlier incidents that merely exfiltrated environment variables, this attack targeted cryptographic keys in a high-value ecosystem — demonstrating that npm supply chain attacks had moved beyond opportunistic to targeted.
The attack exposed a gap: even well-maintained packages with millions of downloads could be compromised through social engineering. The maintainer's credentials were phished, giving attackers a 5-hour window to publish malicious versions before the compromise was detected.
npm responded by making granular token scoping easier, and the broader ecosystem accelerated adoption of provenance attestations and SLSA (Supply chain Levels for Software Artifacts) frameworks.
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.
How provenance works: When you publish with --provenance, npm generates a signed attestation linking the package to a specific commit, workflow run, and repository. Anyone can verify this with npm audit signatures or by inspecting the attestation on npmjs.com. This makes it impossible for an attacker to publish a tampered package without the attestation mismatch being detectable.
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
Lockfile Integrity and Verification
Lock files do more than pin versions — they store cryptographic hashes (sha512 or sha1) of every downloaded tarball. npm verifies these on every install when using npm ci.
# Verify the integrity of your lock file against installed packages
npm audit signatures
# Force re-verification of all packages
npm ci --ignore-scripts --audit
What to look for in package-lock.json:
{
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZboOuXNqhmHdjkQEpg=="
}
}
The integrity field is a Subresource Integrity (SRI) hash. If an attacker publishes a tampered version of lodash@4.17.21 to a mirror, the hash won't match and the install will fail.
Lock file tampering is a real attack vector. Run git diff package-lock.json after any dependency update and review all hash changes carefully in code review. Automated tools like lockfile-lint can enforce that all packages resolve to a trusted registry:
# Install lockfile-lint
npm install -g lockfile-lint
# Run in CI
lockfile-lint --path package-lock.json \
--validate-https \
--allowed-hosts npm registry.npmjs.org
Automating Dependency Updates with Dependabot and Renovate
Manual dependency auditing doesn't scale. Two tools automate this at the PR level.
GitHub Dependabot
Create .github/dependabot.yml in your repository:
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
groups:
# Group patch updates together to reduce PR noise
minor-and-patch:
update-types:
- "minor"
- "patch"
ignore:
# Skip major version bumps — review manually
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 10
Key Dependabot options for security:
security-advisories— Automatically create PRs for known vulnerabilities regardless of schedulegroups— Bundle related updates to reduce review fatigueignore— Skip major updates that require manual migration
Renovate
Renovate is more configurable than Dependabot. Add renovate.json to your repo root:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"schedule": ["before 6am on Monday"],
"lockFileMaintenance": {
"enabled": true,
"schedule": ["before 5am on the first day of the month"]
},
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentAge": "> 3 days",
"automerge": true,
"automergeType": "pr",
"automergeStrategy": "squash"
},
{
"matchPackageNames": ["next", "react", "react-dom"],
"automerge": false,
"reviewers": ["team:frontend"]
}
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"],
"automerge": true
}
}
Renovate vs Dependabot: Renovate supports auto-merge for patch updates (saving review time), has better monorepo support, and handles more package ecosystems. Dependabot is simpler to set up and is natively integrated into GitHub Security Advisories. For most projects, either works; for monorepos or polyglot stacks, Renovate wins.
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: Verify package signatures
run: npm audit signatures
- name: Lint lockfile
run: npx lockfile-lint --path package-lock.json --validate-https --allowed-hosts npm
- name: License compliance
run: npx license-checker --failOn "GPL-3.0;AGPL-3.0"
- name: Socket.dev security check
uses: nicolo-ribaudo/checklocks@v1
Add a separate job for Snyk if you have a paid plan:
snyk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
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
- Provenance attestations — Whether the package publishes with npm provenance
The npm supply chain security guide covers the threat landscape in more depth, including typosquatting detection, dependency confusion attacks, and real-time scanning tools.
When evaluating two competing packages, security posture should be a primary decision factor — not just features and downloads. A package with 10 direct dependencies that are regularly audited beats one with 80 transitive dependencies pulling in unmaintained code from 2018.
Specific signals to check on PkgPulse and npmjs.com:
- Publish frequency: Packages last published 2+ years ago rarely receive security patches. Check the "last published" date against the date of the most recent CVE in the category.
- Maintained forks: If the original package is abandoned, is there an active fork?
@fastify/busboyreplacedbusboyafter the original stalled, for example. - Provenance attestation: Click the "Attestations" tab on npmjs.com. If provenance is present, the build is auditable back to its source commit.
- Token scoping: Check whether the publisher is using granular access tokens (impossible to see directly, but you can infer from publishing consistency on npm's provenance page).
Compare packages like npm vs pnpm to see which package managers have better built-in security defaults, or read our hidden cost of npm dependencies guide for dependency risk analysis.
The Quick Security Checklist
Before shipping a new dependency:
- Ran
npm info <package>and reviewed maintainers + publish date - Checked for install scripts with
npm show <package> scripts - Verified provenance attestation on npmjs.com (if available)
- Added the package to your
.npmrcexact-version config - Added lock file entry reviewed in code review
- Dependabot/Renovate configured for automated update PRs
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, and automate routine updates with Dependabot or Renovate. Finally, enable provenance attestations on packages you publish so the ecosystem can verify your artifacts.
The @solana/web3.js attack of 2025 was a watershed moment — it showed that even major, well-funded ecosystems are vulnerable to a single phished maintainer credential. Defense in depth across every layer of the supply chain is the only answer.
Related: npm Dependency Trees: Most Nested Packages 2026, The Hidden Cost of npm Dependencies, How Long Until npm Packages Get Updates? 2026.