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
- npm 7 or later. Earlier versions use a different audit endpoint and a different report shape. The npm audit documentation describes the current one.
- A lockfile.
npm auditreads the resolved tree, not the ranges in package.json. - To reproduce the outputs on this page, create a directory and put this in
package.json, then runnpm install.
{
"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
- 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=1The 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. - 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=1The lodash block is gone because lodash is a devDependency. The critical one stayed, so the exit code is still 1.
- 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. - 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=0That 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
What to check next
- How to check outdated npm packages: the other half of dependency health, and the reason a zero audit is not a clean bill.
- How to check if package lock is in sync: an audit reads the lockfile, so a stale lockfile audits a tree nobody installs.
- How to check npm package licenses: the other property of a dependency that a code review will not surface.
- How to check why a package is installed: what to run when the flagged package is not in your package.json.
- Pull request checklist: where this check sits among the others on a dependency change.
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.
Related on this site
basic5 minpublished updated Maks Verny