Overview
Run an audit on every project before deploying and in CI on every pull request. Most vulnerabilities in npm audit output are in development dependencies or deep transitive paths and carry no real production risk. This guide shows how to run the audit, read the output, fix what matters, and silence what does not without suppressing real issues. See audit-dependencies for the broader dependency hygiene process.
Prerequisites
- A
package.jsonand a lock file (package-lock.json,pnpm-lock.yaml, oryarn.lock). Audits run against resolved versions, not just declared ranges. - npm 7+ or pnpm 8+ installed.
ghCLI if adding the audit step to GitHub Actions.
Steps
1. Run the audit
# npm
npm audit
# pnpm
pnpm audit
# Fail only on high or critical severity
npm audit --audit-level=high
pnpm audit --audit-level=highThe output lists each vulnerability with: package name, severity, description, vulnerable version range, and whether a fix is available.
2. Read the severity levels
Focus your triage by severity.
| Severity | Action |
|---|---|
| Critical | Fix before the next deployment. |
| High | Fix in the current sprint. |
| Moderate | Fix if it is in a direct dependency; defer if deeply transitive. |
| Low | Defer unless it affects a production code path. |
A vulnerability in a build-only tool (devDependency) that never runs in production is lower priority than the same vulnerability in a runtime dependency.
3. Fix resolvable vulnerabilities
# Auto-fix patch and minor updates
npm audit fix
# Include semver-major fixes (review the diff carefully)
npm audit fix --force--force may upgrade packages beyond the declared range in package.json. Review the lock file diff before committing.
For pnpm:
pnpm update --latest4. Override unfixable transitive vulnerabilities
When a transitive dependency has a vulnerability but the fix is not available upstream, use overrides (npm) or pnpm.overrides to force a specific version.
// package.json (npm)
{
"overrides": {
"vulnerable-package": ">=2.1.0"
}
}
// package.json (pnpm)
{
"pnpm": {
"overrides": {
"vulnerable-package": ">=2.1.0"
}
}
}Only override when: the fixed version is a drop-in compatible upgrade, and you have tested the override. Document why the override exists.
5. Add audit to CI
# .github/workflows/security.yml
name: Security audit
on:
push:
branches: [main]
pull_request:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm audit --audit-level=highThe step exits non-zero on any vulnerability at or above the specified level, failing the PR check.
6. Review the audit JSON for scripting
npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical")'JSON output is useful in scripts that post audit summaries as PR comments or filter by specific packages.
Verify it worked
npm audit --audit-level=high
# Exit code 0 means no high or critical vulnerabilities found.
echo "Exit code: $?"An exit code of 0 confirms the project is clean at the specified level.
Common errors
ENOAUDITor “No packages”: the audit requires a lock file. Runnpm installfirst to generatepackage-lock.json.audit fix --forcebreaks the build: an upgraded package has a breaking API change. Check the package’s changelog, downgrade the override, and file an issue upstream.- False positives on dev-only packages: use
npm audit --omit=devto audit only production dependencies. Separately audit dev dependencies at a lower severity threshold. - Override causes a different vulnerability: after adding an override, re-run the audit. Overriding one package can expose another vulnerability in the same subtree.
- Audit in CI times out:
npm auditmakes a network request to the npm registry. On restricted CI networks, add the registry URL to the allowlist or cache the audit database.