Antoine Duno
Founder of ZeriFlow · 10 years fullstack engineering · About the author
Key Takeaways
- Vulnerable npm packages are responsible for some of the most damaging supply chain attacks in recent years. This guide explains how CVEs get into your dependencies, how to use npm audit effectively, when upgrading is the right call, and how to automate security scanning so you're not caught off guard.
- Includes copy-paste code examples and step-by-step instructions.
- Free automated scan available to verify your implementation.
CVE Vulnerabilities in npm Packages: How to Scan and Fix Them
Your application is only as secure as its dependencies. With the average Node.js project depending on hundreds — sometimes thousands — of packages, the npm ecosystem is one of the most significant security attack surfaces in modern web development.
This guide covers how npm vulnerabilities work, how to use npm audit effectively (and interpret its output without panicking), when to upgrade versus patch, how different tools compare, and how to build a dependency security workflow that scales.
How npm Vulnerabilities Work
CVEs and the National Vulnerability Database
A CVE (Common Vulnerabilities and Exposures) is a publicly disclosed security vulnerability with a standardised identifier (e.g., CVE-2021-44228). Once a vulnerability is discovered and responsibly disclosed, it is assigned a CVE ID, given a CVSS score (0–10 for severity), and published in the National Vulnerability Database (NVD).
When a vulnerability is found in an npm package, it gets a CVE, and tools like npm audit, Snyk, and the GitHub Advisory Database cross-reference your installed packages against known CVEs.
Direct vs Transitive Dependencies
Your package.json lists direct dependencies. But each of those packages has its own dependencies (transitive dependencies), and those have further dependencies. A typical React application might have 20 direct dependencies but 800+ total packages in node_modules.
A vulnerability in a package you have never heard of can still be in your dependency tree:
your-app
└── some-library@1.2.0
└── vulnerable-package@2.0.1 ← CVE-2024-XXXXX hereThis is why npm audit sometimes reports vulnerabilities in packages that do not appear in your package.json — they are deep in the transitive tree.
The Severity Levels
| CVSS Score | Severity | npm audit label |
|---|---|---|
| 9.0–10.0 | Critical | critical |
| 7.0–8.9 | High | high |
| 4.0–6.9 | Medium | moderate |
| 0.1–3.9 | Low | low |
Using npm audit
Basic Audit
npm auditThis outputs a table of vulnerabilities found in your dependency tree, grouped by severity. A typical output looks like:
# npm audit report
lodash <4.17.21
Severity: high
Prototype Pollution - https://github.com/advisories/GHSA-xxxx
fix available via `npm audit fix`
node_modules/lodash
some-library >=1.0.0
node_modules/some-library
7 vulnerabilities (3 moderate, 3 high, 1 critical)Filtering by Severity
# Only show high and critical vulnerabilities
npm audit --audit-level=high
# Exit with non-zero code if any high/critical found (useful for CI)
npm audit --audit-level=high && echo "No high/critical vulnerabilities"JSON Output for Processing
npm audit --json > audit-report.json
# Extract just the critical and high vulnerabilities
npm audit --json | jq ''.vulnerabilities | to_entries[] | select(.value.severity == "critical" or .value.severity == "high") | .key''Checking for Production-Only Dependencies
Many vulnerabilities are in development dependencies (testing tools, linters) that never run in production. You can audit only production dependencies:
npm audit --omit=devThis significantly reduces noise in most projects — a testing framework with a prototype pollution vulnerability does not pose a production risk.
npm audit fix: When to Use It and When to Be Careful
Safe Fixes
npm audit fixThis automatically updates vulnerable packages to the nearest version that resolves the vulnerability while respecting your package.json semver ranges. It only makes changes that are backwards-compatible (patch and minor updates).
This is generally safe to run. Review the changes it makes in package-lock.json and test your application.
Force Fixes (Use With Caution)
npm audit fix --forceThis can make major version updates and override semver ranges — changes that may introduce breaking changes. Do not run this without understanding what it will change:
# Check what would change before applying
npm audit fix --force --dry-runRead the advisory before force-fixing. Sometimes a "fix" means downgrading to an older version that is itself insecure in other ways, or it means a major version bump with a completely different API.
Manual Updates
For vulnerabilities in transitive dependencies that npm cannot auto-fix, you may need to manually override the version:
// package.json — override a transitive dependency version
{
"overrides": {
"vulnerable-package": ">=2.0.2"
}
}The overrides field (npm v8.3+) forces all instances of a package in the dependency tree to use the specified version.
When to Upgrade vs When to Patch
Not every vulnerability requires immediate action. The right response depends on:
Exploitability in Your Context
A prototype pollution vulnerability in a utility library is only dangerous if: 1. Untrusted user input reaches the affected code path 2. The affected code path is actually called in your application
Many npm audit findings are in packages used only during development (Webpack, Jest, ESLint plugins) or in code paths your application never exercises. Read the advisory and understand the attack vector before treating every finding as a five-alarm fire.
Asking the Right Questions
- Is the vulnerable package installed in production (
dependencies) or development (devDependencies)? - Does user-controlled input reach the vulnerable code?
- Is a fix available? If yes, what is the upgrade path?
- If no fix is available, what is the exposure window?
Priority Matrix
| Scenario | Action |
|---|---|
| Critical CVE, fix available, production dependency | Update immediately |
| High CVE, fix available, production dependency | Update within 24–72 hours |
| High CVE, no fix available, production | Evaluate mitigations; monitor for fix |
| Critical CVE in devDependency only | Update at next opportunity, low urgency |
| Moderate/Low CVE in production | Plan for next maintenance window |
Tools Comparison: npm audit vs Snyk vs ZeriFlow
npm audit
Pros: Built-in to npm, no setup required, free Cons: Only checks against the GitHub Advisory Database; no prioritisation based on exploitability; cannot scan git history; no IDE integration in community version
npm audit --audit-level=highBest for: quick checks during development, CI/CD gate
Snyk
Pros: More comprehensive vulnerability database, exploitability scores, licence scanning, container scanning, fix PRs Cons: Free tier is limited; the proprietary vulnerability database has more coverage but costs money at scale
# Install Snyk CLI
npm install -g snyk
# Authenticate
snyk auth
# Test for vulnerabilities
snyk test
# Monitor project (sends to Snyk dashboard)
snyk monitorBest for: teams wanting a dedicated dependency security platform with dashboards
Dependabot (GitHub)
Dependabot is GitHub''s built-in dependency update service. It automatically opens pull requests to update vulnerable dependencies when a CVE is disclosed:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "security"
- "dependencies"Best for: automated PRs for dependency updates with no developer effort
ZeriFlow Advanced Scan
ZeriFlow''s Advanced Scan analyses your package.json and package-lock.json as part of a comprehensive codebase audit that also covers hardcoded secrets and insecure API patterns. You upload a GitHub repo URL or ZIP file, and the scan runs against the CVE database alongside all other checks.
Best for: developers who want a single tool covering both infrastructure security (headers, TLS, DNS) and codebase security (secrets, CVEs, patterns) in one pass
Automating Dependency Scanning in CI/CD
Adding a vulnerability gate to your pipeline ensures that critical CVEs cannot slip into production unnoticed.
GitHub Actions with npm audit
# .github/workflows/security.yml
name: Security Checks
on:
push:
branches: [main, develop]
pull_request:
jobs:
dependency-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ''20''
- name: Install dependencies
run: npm ci
- name: Run security audit
run: npm audit --audit-level=high --omit=dev
# Fails the build if any high or critical vulnerabilities are found
# in production dependenciesWith Snyk in CI/CD
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high --productionBlocking PRs with Score Thresholds
If you use ZeriFlow for infrastructure scanning, you can also block deploys based on the overall security score:
# In your deploy script
SCORE=$(curl -s "https://api.zeriflow.com/v1/scan?url=$STAGING_URL&token=$ZERIFLOW_TOKEN" | jq ''.score'')
if [ "$SCORE" -lt 75 ]; then
echo "Security score $SCORE/100 is below threshold. Deploy blocked."
exit 1
fiDealing with Unresolvable Vulnerabilities
Sometimes a fix does not exist — the package maintainer has not released a patch, or the vulnerability is in a deeply nested transitive dependency you cannot easily override.
Options When No Fix Is Available
1. Add an override temporarily
{
"overrides": {
"affected-package": "1.2.3"
}
}Pin the affected package to a specific version while you wait for an upstream fix.
2. Replace the dependency
If a direct dependency has a persistent vulnerability and the maintainer is unresponsive, evaluate alternatives. The npm ecosystem almost always has multiple packages providing the same functionality.
3. Accept and document the risk
If the vulnerability is not exploitable in your context (e.g., it is in a devDependency or an unused code path), document your reasoning and suppress the alert:
# npm suppress a specific advisory (use with care)
# In package.json:
{
"auditConfig": {
"ignoredAdvisories": ["1234567"]
}
}Document why each suppressed advisory is acceptable risk. Revisit them quarterly.
4. Isolate the vulnerability
For high-risk packages that cannot be updated, consider running them in a sandboxed environment or behind a validation layer that sanitises all input before it reaches the vulnerable code.
The Bigger Picture: Supply Chain Security
Individual CVEs are one dimension of dependency security. The broader threat is supply chain attacks — malicious actors compromising legitimate npm packages to inject malicious code.
Notable examples: - event-stream (2018): A popular package had a new maintainer add code that stole Bitcoin wallet credentials - ua-parser-js (2021): The package was temporarily hijacked and published versions that installed crypto miners and credential stealers - node-ipc (2022): A maintainer intentionally added code that wiped files on machines in Russia and Belarus
Mitigations:
- Lock exact versions in package-lock.json or yarn.lock — do not allow * or loose version ranges for security-sensitive packages
- Use npm ci in CI/CD instead of npm install — it installs exact versions from the lock file
- Review new dependencies carefully before adding them: check the package''s GitHub activity, download counts, and maintenance status
- Enable 2FA on your npm account to prevent account takeover attacks
Summary
Keeping your npm dependencies secure requires a layered approach:
- 1Run
npm audit --omit=devregularly and in CI/CD - 2Use
npm audit fixfor safe, automatic patches - 3Use
overridesinpackage.jsonfor transitive dependency fixes - 4Add Dependabot or Renovate for automated update PRs
- 5Gate production deploys on vulnerability severity thresholds
- 6Read each advisory — not every
npm auditfinding is an actionable production risk
For a comprehensive codebase security check that covers CVEs alongside hardcoded secrets and infrastructure configuration, try ZeriFlow''s Advanced Scan.