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.json and a lock file (package-lock.json, pnpm-lock.yaml, or yarn.lock). Audits run against resolved versions, not just declared ranges.
  • npm 7+ or pnpm 8+ installed.
  • gh CLI 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=high

The 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.

SeverityAction
CriticalFix before the next deployment.
HighFix in the current sprint.
ModerateFix if it is in a direct dependency; defer if deeply transitive.
LowDefer 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 --latest

4. 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=high

The 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

  • ENOAUDIT or “No packages”: the audit requires a lock file. Run npm install first to generate package-lock.json.
  • audit fix --force breaks 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=dev to 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 audit makes a network request to the npm registry. On restricted CI networks, add the registry URL to the allowlist or cache the audit database.