DEV Community

Prasad V for depproof

Posted on Originally published at depproof.com AI-assisted

Why npm & pnpm audit miss vulnerabilities

You run pnpm audit in CI. It prints No known vulnerabilities found. The build goes green and you move on.

Then someone runs npm audit on the same repo, same lockfile, same afternoon — and gets a list of advisories.

Neither command is broken. They disagree for the same reason a clean result from either one proves less than most teams assume, and it comes down to a single design fact about what audit actually is.

audit is a network request, not a scanner

There is no local analysis happening. When you run npm audit or pnpm audit, the client sends a description of your resolved dependency tree to your configured registry's audit endpoint, and prints the advisories that come back. That advisory data traces to the GitHub Advisory Database, which curates known vulnerabilities for the npm ecosystem.

So audit is a question you ask a service about a tree you describe to it. It is not a tool reasoning over your code, and it keeps no database of its own.

That one fact explains nearly every way audit quietly under-delivers.

The four structural gaps

1. It is only as broad as one source. Audit reflects the GitHub Advisory Database for npm. A vulnerability not recorded there yet, or one that lives primarily in a different database, will not
appear no matter how carefully you run the command.

2. No network, no results — silently. Because it depends on a reachable audit endpoint, a private registry that doesn't implement one, an air-gapped runner, or a blocking proxy can turn audit into a command that returns nothing. On a terminal, "didn't actually check" and "all clear" look identical.

3. It is npm-ecosystem only. Audit sees your JavaScript dependencies and nothing else. If the same repo also ships Java, Go, Python, or containers, audit is silent on all of it — not because those are clean, but because they were never in scope.

4. Presence, not reachability — and no inventory. Audit tells you a vulnerable version is present. It can't tell you whether the vulnerable code path is ever executed, and it doesn't leave behind a machine-readable component inventory. You get a console dump, not an artifact you can re-check when the next advisory drops.

So why do the two disagree?

Both hit a registry endpoint backed by the same advisory data, so the difference isn't in the answer — it's in the question. The two differ in:

  • how each resolves and deduplicates the dependency tree
  • whether devDependencies are included in what gets described
  • the exact state of the advisory data at the moment of each call

Different views of the tree, and different timing, produce different counts.

The useful conclusion is not "trust the stricter one." It's that a single audit number is a fragile measure of dependency risk — fragile enough that two well-behaved tools reading the same
lockfile can hand you two different stories.

What a more complete check looks like

None of this makes audit useless. It makes it a floor. If you want something sturdier, the shape matters more than the specific tool:

  • Read the lockfile, not a service. Resolving pnpm-lock.yaml or package-lock.json locally means the check works offline, behind a private registry, and in air-gapped CI. No audit endpoint required, and no silent empty result when the network says no.
  • Check open, aggregated advisory data. OSV pulls from many sources — including GitHub Advisories and OpenSSF malicious-package data — and is built to be queried directly against a lockfile. Broader than a single source, and portable rather than proprietary.
  • Produce an inventory. Emitting a CycloneDX SBOM alongside the findings gives you something durable to re-check later. That's the difference between a one-off console dump and coverage that survives to next month.

Lockfile-based, open advisory data, full transitive tree, real artifact. That shape removes the network dependency, the single-source blind spot, and the missing-inventory gap in one move —
whichever tool you get it from.

The takeaway

A clean npm audit is worth having. It just isn't the sentence people read it as. It says: one advisory source, reachable at one moment, found nothing in the view of the tree it was handed.

That's a floor. Treat it like one.


Disclosure: I work on depproof, a dependency scanner that runs on your own infrastructure. Nothing above depends on using it — the OSV-and-lockfile approach works with several tools, and the built-in audit commands are a reasonable first check.

Not affiliated with npm, pnpm, or GitHub; product names are used nominatively.

Top comments (0)