How to check npm packages for vulnerabilities

Run npm audit in the directory that holds package-lock.json. It prints one block per advisory with the affected range, the severity and a GHSA link, then a count line, and exits 1 when anything is found. On a clean project it prints found 0 vulnerabilities and exits 0.

Why check this

This runs on every pull request that touches package.json or the lockfile, and again before a release build is tagged. A dependency bump is the one change that alters running code without a diff anyone reads, so the audit is the only gate between a transitive package and production.

The failure it catches is a known-exploitable version arriving through a package nobody chose. In the demo project below, minimist@1.2.0 is a direct dependency with two published prototype-pollution advisories. Nothing in the application code changed to introduce it.

Prerequisites

{
  "name": "audit-demo",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "minimist": "1.2.0"
  },
  "devDependencies": {
    "lodash": "4.17.15"
  }
}

Advisory data is served live, so a run on a later date reports more than this one did on 2026-09-12.

Steps

  1. Step 1.

    Run the audit in the demo project and read the exit code, not the text.

    npm audit; echo "exit=$?"
    
    # npm audit report
    
    lodash  <=4.17.23
    Severity: high
    Command Injection in lodash - https://github.com/advisories/GHSA-35jh-r3h4-6jhm
    Prototype Pollution in lodash - https://github.com/advisories/GHSA-p6mc-m468-83gw
    …
    node_modules/lodash
    
    minimist  1.0.0 - 1.2.5
    Severity: critical
    Prototype Pollution in minimist - https://github.com/advisories/GHSA-vh95-rmgr-6w4m
    Prototype Pollution in minimist - https://github.com/advisories/GHSA-xvch-5gv4-984h
    …
    node_modules/minimist
    
    2 vulnerabilities (1 high, 1 critical)
    …
    exit=1

    The header line of each block is a range, lodash <=4.17.23, not the version you installed. The last line of each block is the path of the copy that matched.

  2. Step 2.

    Restrict the audit to what ships, and compare the count.

    npm audit --omit=dev; echo "exit=$?"
    
    # npm audit report
    
    minimist  1.0.0 - 1.2.5
    Severity: critical
    Prototype Pollution in minimist - https://github.com/advisories/GHSA-vh95-rmgr-6w4m
    Prototype Pollution in minimist - https://github.com/advisories/GHSA-xvch-5gv4-984h
    …
    node_modules/minimist
    
    1 critical severity vulnerability
    …
    exit=1

    The lodash block is gone because lodash is a devDependency. The critical one stayed, so the exit code is still 1.

  3. Step 3.

    Read the machine-readable counts, which is what a CI step should assert on.

    npm audit --json | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.stringify(JSON.parse(s).metadata,null,2)))"
    
    {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 0,
      "high": 1,
      "critical": 1,
      "total": 2
    },
    "dependencies": {
      "prod": 2,
      "dev": 1,
      "optional": 0,
      "peer": 0,
      "peerOptional": 0,
      "total": 2
    }
    }

    The severity object sums to total. The dependency object does not: 2 prod plus 1 dev against a total of 2.

  4. Step 4.

    Run the same command in a project you believe is clean, to see what a pass looks like.

    npm audit; echo "exit=$?"
    
    found 0 vulnerabilities
    exit=0

    That capture is from this site's own repository on 2026-09-12, a tree of 380 packages. The same repository reports nine packages behind their latest release, which is a different check.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | found 0 vulnerabilities, exit 0 | No advisory matches any resolved version today | Record the date. The tree did not change; the advisory database will. | | A block with Severity: critical | A published advisory covers the installed version | Read the GHSA page before upgrading. Some advisories need a config change, not a bump. | | Will install X, which is outside the stated dependency range | The fix is a major bump | This is not a patch. Plan it as a code change with tests. | | npm error code ENOLOCK | There is no lockfile in this directory | Run npm install or npm i --package-lock-only first. The audit ran against nothing. | | Fewer blocks under --omit=dev than without it | The missing advisories are in devDependencies | Decide whether that tool runs on a machine with secrets on it before you drop the rows. |

Common mistakes

Sign: npm audit reports zero, and the same project has nine packages behind their latest release.Cause: Those are two different questions. This site's repository printed found 0 vulnerabilities and, in the same session, nine rows from npm outdated. An audit matches resolved versions against published advisories. Being current is a separate check with a separate command.
Sign: The CI audit passes with --omit=dev while a full audit reports a critical.Cause: A build tool runs on the CI machine with repository credentials in the environment, and --omit=dev removes it from the report. In the demo above the flag turned 2 vulnerabilities into 1. Audit the full tree in CI and use the flag only to triage what reaches a user.
Sign: npm audit in a freshly cloned repository fails without naming a single package.Cause: It exits 1 with npm error code ENOLOCK and the message that the command requires an existing lockfile. In a CI step written as npm audit || true that failure is indistinguishable from a pass, because both produce a zero status and no advisory.
Sign: The dependency counts in --json output are added together and reported as a total.Cause: The keys overlap and the arithmetic does not work. The demo project reported prod 2, dev 1 and total 2. This site's repository reported prod 139, dev 204, optional 140, peer 58 and total 380. Quote the total field, never a sum of the others.

What to check next

FAQ

How do I fix npm vulnerabilities?

npm audit fix upgrades within the ranges in package.json. When the report says the fix is outside the stated range, that command does nothing and npm audit fix --force performs a major bump. Run the test suite after either one. Add --dry-run to see the plan without touching the tree.

Does npm audit check only production dependencies?

No, it audits the whole resolved tree by default. Pass --omit=dev to drop devDependencies from the report. That flag changed a 2-vulnerability report into a 1-vulnerability report in step 2.

Why does npm audit need network access?

It sends the resolved package names and versions to the registry advisory endpoint and receives the matches. There is no local copy of the advisory database, so an offline run cannot answer the question.

Can npm audit fail a build?

It already does. npm audit exits 1 when it finds anything, so an unguarded step fails the job. Use --audit-level=critical to exit 1 only on critical findings; on the demo project that still exited 1.

Why do the same packages report differently next week?

The advisory database changes, the installed tree does not. A package audited clean on 2026-09-12 can be flagged the next day by an advisory published against a version that was already on disk.

Verified

Verified by Maks Vernynpm 10.9.8Node.js 22.23.2

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic5 minpublished updated Maks Verny