The fastest way to check npm vulnerabilities is npm audit, which compares your installed package versions against the GitHub Advisory Database — but the output is a starting list, not a to-do list. A fresh create-next-app project can report a dozen advisories before you have written a line of code, and most of them sit in build-time tooling that never ships to production. Learning to check for vulnerabilities is easy; learning to read the results without drowning in false urgency is the part that matters.
Running the built-in audit
Every modern npm install already knows how to do this. From a directory with a package-lock.json:
npm audit
This prints each advisory, its severity, the affected package, and the dependency path that pulled it in. For a machine-readable version you can pipe into other tools:
npm audit --json
If you only care about serious issues, gate on severity:
npm audit --audit-level=high
That last flag is what you want in CI. It exits non-zero only when a high or critical advisory is present, so your pipeline fails on the things that actually merit blocking a merge instead of on every low-severity note in a transitive dev dependency.
Reading the output honestly
When you npm check for vulnerabilities and get 40 results, the instinct is to treat all 40 as work. Resist it. Three questions cut the list down fast.
First, is it a production dependency or a dev dependency? A vulnerability in eslint's dependency tree does not run in your deployed app. npm audit --omit=dev restricts the report to what actually ships.
Second, is the vulnerable code path reachable from your app? npm audit matches versions, not usage. It will flag a ReDoS bug in a regex function of a library even if your code never calls that function. Version-based scanners are conservative by design — they would rather over-report than miss something — which is exactly why a raw count overstates real exposure.
Third, is there a fix available at all? Some advisories have no patched version yet. Those go on a watch list, not a sprint.
Fixing without breaking the build
npm can apply fixes automatically:
npm audit fix
This bumps dependencies to patched versions within the semver ranges your package.json already allows, so it rarely breaks anything. When the only fix requires a major version bump, npm will not apply it unless you force it:
npm audit fix --force
Be careful with --force. It ignores semver constraints and can install breaking major versions of your direct dependencies. Run your test suite immediately after, and read the diff on package-lock.json before committing. On more than one occasion I have watched --force "fix" three advisories by dragging a project from a library's v4 to v6, breaking the app in the process.
For a transitive dependency that has a fix upstream but hasn't been picked up yet, overrides in package.json lets you pin a nested package to a safe version without waiting for the intermediate maintainer:
{
"overrides": {
"minimist": "1.2.8"
}
}
Use overrides sparingly and document why each one exists — they are easy to forget and can silently mask a later legitimate update.
Making it reproducible in CI
Ad hoc audits on a developer laptop miss regressions. Wire the check into your pipeline so a newly disclosed advisory or a risky dependency bump fails the build. A minimal GitHub Actions step:
- run: npm ci
- run: npm audit --audit-level=high --omit=dev
npm ci installs strictly from the lockfile, so the audit runs against exactly what you will deploy. Pairing a reproducible install with a severity gate turns "someone should check dependencies sometime" into an automatic control.
Where npm audit stops and SCA begins
npm audit is genuinely useful and free, but it has hard limits. It only knows about advisories in the GitHub Advisory Database, it has no concept of whether a vulnerable function is reachable, and it says nothing about license risk, package provenance, or install-script behavior — the vector behind several 2024 and 2025 npm supply-chain compromises where a legitimate package published a malicious version.
This is where dedicated software composition analysis earns its place. Reachability analysis answers the question npm audit can't: does your code path actually invoke the vulnerable function? A tool such as Safeguard combines the advisory match with call-graph analysis so the "critical" list you triage is the subset that is genuinely exploitable in your app, not the raw version-match count. If you are weighing options, our SCA product page and the Snyk comparison lay out the tradeoffs without hand-waving.
FAQ
What's the difference between npm audit and npm audit fix?
npm audit reports vulnerabilities. npm audit fix attempts to resolve them by upgrading dependencies within your allowed semver ranges. Adding --force lets it apply breaking major upgrades, which you should only do with tests running.
Why does npm audit report vulnerabilities in packages I don't use directly?
Most reported advisories are in transitive dependencies — packages pulled in by your direct dependencies. That is normal; the average project has far more indirect dependencies than direct ones. Use --omit=dev and reachability analysis to tell which ones actually matter.
Can I check npm vulnerabilities without a package-lock.json?
npm audit needs a lockfile to know exact installed versions. Run npm install (or npm ci if a lockfile exists) first. Without a lockfile the audit can't reliably map your tree to advisories.
Is a clean npm audit enough for a security sign-off?
No. A clean audit means no known advisories match your version tree in the GitHub database. It says nothing about zero-days, malicious package versions, license compliance, or unreachable-but-flagged code. Treat it as one signal among several.