<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: depproof</title>
    <description>The latest articles on DEV Community by depproof (prasadv-depproof).</description>
    <link>https://dev.to/prasadv-depproof</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14582%2F09e3ee21-09db-49ab-b841-0df9a745abd9.png</url>
      <title>DEV Community: depproof</title>
      <link>https://dev.to/prasadv-depproof</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prasadv-depproof"/>
    <language>en</language>
    <item>
      <title>Why npm &amp; pnpm audit miss vulnerabilities</title>
      <dc:creator>Prasad V</dc:creator>
      <pubDate>Wed, 09 Sep 2026 16:13:55 +0000</pubDate>
      <link>https://dev.to/prasadv-depproof/why-npm-pnpm-audit-miss-vulnerabilities-54j5</link>
      <guid>https://dev.to/prasadv-depproof/why-npm-pnpm-audit-miss-vulnerabilities-54j5</guid>
      <description>&lt;p&gt;You run &lt;code&gt;pnpm audit&lt;/code&gt; in CI. It prints &lt;code&gt;No known vulnerabilities found&lt;/code&gt;. The build goes green and you move on.&lt;/p&gt;

&lt;p&gt;Then someone runs &lt;code&gt;npm audit&lt;/code&gt; on the same repo, same lockfile, same afternoon — and gets a list of advisories.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;audit&lt;/code&gt; actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;audit&lt;/code&gt; is a network request, not a scanner
&lt;/h2&gt;

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

&lt;p&gt;So &lt;code&gt;audit&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;That one fact explains nearly every way &lt;code&gt;audit&lt;/code&gt; quietly under-delivers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four structural gaps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. It is only as broad as one source.&lt;/strong&gt; 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&lt;br&gt;
appear no matter how carefully you run the command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. No network, no results — silently.&lt;/strong&gt; 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 &lt;code&gt;audit&lt;/code&gt; into a command that returns &lt;em&gt;nothing&lt;/em&gt;. On a terminal, "didn't actually check" and "all clear" look identical.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;4. Presence, not reachability — and no inventory.&lt;/strong&gt; Audit tells you a vulnerable version is &lt;em&gt;present&lt;/em&gt;. 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 &lt;em&gt;next&lt;/em&gt; advisory drops.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why do the two disagree?
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how each resolves and deduplicates the dependency tree&lt;/li&gt;
&lt;li&gt;whether &lt;code&gt;devDependencies&lt;/code&gt; are included in what gets described&lt;/li&gt;
&lt;li&gt;the exact state of the advisory data at the moment of each call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different views of the tree, and different timing, produce different counts.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What a more complete check looks like
&lt;/h2&gt;

&lt;p&gt;None of this makes &lt;code&gt;audit&lt;/code&gt; useless. It makes it a &lt;strong&gt;floor&lt;/strong&gt;. If you want something sturdier, the shape matters more than the specific tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the lockfile, not a service.&lt;/strong&gt; Resolving &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; or &lt;code&gt;package-lock.json&lt;/code&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check open, aggregated advisory data.&lt;/strong&gt; &lt;a href="https://osv.dev" rel="noopener noreferrer"&gt;OSV&lt;/a&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Produce an inventory.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 —&lt;br&gt;
whichever tool you get it from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

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

&lt;p&gt;That's a floor. Treat it like one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on &lt;a href="https://depproof.com" rel="noopener noreferrer"&gt;depproof&lt;/a&gt;, 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 &lt;code&gt;audit&lt;/code&gt; commands are a reasonable first check.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not affiliated with npm, pnpm, or GitHub; product names are used nominatively.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>security</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
