npm audit — what it catches and what it misses
npm audit is the most widely-used npm security command. It comes built into npm, it's easy to run, and it produces a report that looks thorough. It is also widely misunderstood in terms of what it actually protects against.
What npm audit does
When you run npm audit, npm sends your package list to the npm registry and receives back a list of known vulnerabilities for those packages. The vulnerability data comes from the npm security advisory database, which aggregates CVEs from the National Vulnerability Database, GitHub Advisory Database, and reports submitted directly to npm.
For each vulnerability found, npm audit reports: the severity (critical, high, moderate, low), the affected package, the vulnerable version range, and the recommended fix (usually an update to a patched version).
What npm audit is good at
npm audit is genuinely useful for a specific class of problem: known vulnerabilities in packages you already have installed. If express@4.17.1 has a known path traversal vulnerability (and it did, CVE-2022-24999), npm audit will catch it and tell you to upgrade.
This is real value. Keeping up with known vulnerabilities in direct and transitive dependencies is a legitimate security task, and npm audit automates it.
What npm audit misses
Zero-day supply chain attacks. When a package is first compromised — the moment a malicious version is published — no CVE exists. The npm security advisory database has nothing to say about it. npm audit returns clean results for a malicious package that was published five minutes ago.
This is not a failure of npm audit. It's an architectural constraint. CVE-based scanners can only detect threats that have already been discovered and catalogued. The gap between publication and catalogue entry is typically 2–12 hours. During that window, npm audit provides zero protection.
Intentionally malicious packages. A package that is entirely malicious — not a vulnerability in a legitimate package, but a package designed from the start to steal credentials — often has no CVE at all. Researchers report it. The package is removed from npm. But it may never get a formal CVE entry. If you installed it during the window it was available, npm audit after the fact may not tell you anything useful.
New packages with no history. npm audit only flags packages that match its vulnerability database. A brand-new package with no CVE history, no download count, no community trust — npm audit has nothing to say about it.
The false positive problem
npm audit is also known for high false positive rates in certain codebases. A common pattern: a known vulnerability exists in a package that is only used in development tooling, not in production code. Or a vulnerability is in a code path that your application never exercises.
npm has improved its audit tooling with npm audit --omit=dev (to exclude dev dependencies) and severity filtering (npm audit --audit-level=high), but the fundamental issue — that npm audit reports vulnerabilities without context about whether you're actually exploitable — remains.
The correct mental model
npm audit is a retrospective scanner. It tells you about problems in what you already have. It is necessary but not sufficient.
Supply chain attacks that happen at the moment of installation — when a new malicious version is published — are invisible to retrospective scanners. They require prospective verification: analysis at the moment of install, before the package is executed.
That's what Veln provides. Run npm audit to keep known vulnerabilities addressed. Run Veln to catch attacks at the moment they happen.
Using both together
# In CI: run both
- run: npm ci
- run: npm audit --audit-level=high
- uses: veln-sh/setup-action@v1 # wraps the npm ci above
npm audit should run after install as a CVE check. Veln runs during install as a behavioral and consensus check. They're complementary and address different threat classes.
npm audit catches yesterday's attacks. Veln catches today's.