The problem with npm audit

Security matters. Nobody wants to argue for less of it, which is exactly why the flaws in npm audit have gone so quietly unchallenged. But the tool as it stands today is fundamentally broken, and its default-on rollout after every npm install was premature and poorly suited to front-end tooling.

The core issue is a classic boy-who-cried-wolf scenario. npm audit reports so many irrelevant warnings that developers learn to ignore them entirely. That's worse than no warnings at all — it trains people to dismiss security alerts, even the ones that might matter.

How npm audit is supposed to work

Your Node.js application has a dependency tree. When a vulnerability is discovered in a package like [email protected], that information gets published to a registry that npm checks during npm audit:

your-app
  - [email protected]
  - [email protected]
  - [email protected]
    - [email protected]
      - [email protected]

When the vulnerability is announced, running npm audit surfaces it:

your-app
  - [email protected]
  - [email protected]
  - [email protected]
    - [email protected]
      - [email protected] (Vulnerable!)

The fix path is straightforward: npm audit fix attempts to install a patched version. If database-layer depends on a range that includes 1.0.1, the update applies cleanly. If it pins exactly to 1.0.0, the maintainer needs to release a new version that accepts the patch:

1 vulnerabilities (0 moderate, 1 high)
 
To address issues that do not require attention, run:
  npm audit fix
 
To address all issues (including breaking changes), run:
  npm audit fix --force

If neither is possible, npm audit fix --force lets you take matters into your own hands, risking breaking changes. In theory, this all makes sense.

In practice, it falls apart quickly.

What npm audit reports in a real project

Consider Create React App. It's a build-time tool that compiles JavaScript source into static HTML, JS, and CSS files. It does not produce a Node.js server. Yet when you create a new project:

your-app
  - [email protected]
  - [email protected]
  - [email protected]
    - [email protected]
      - [email protected] (Fixed!)

The output immediately tells you your fresh project has vulnerabilities:

your-app
  - [email protected]
  - [email protected]
  - [email protected]
    - [email protected] (Updated to allow the fix.)
      - [email protected] (Fixed!)

Running npm audit reveals the details. Here's what the tool flags, and why each report is meaningless in this context.

First report: browserslist ReDoS

browserslist parses the browser-targeting configuration used across build tools. The reported vulnerability is a "Regular Expression Denial of Service" — a crafted regex input could cause exponential slowdown. But who supplies that input? Your configuration file, which lives on your own machine and is processed only during your local build. There's no way for a user of your static site to influence it. If an attacker can modify your config files, you have far bigger problems than a slow regex.

Verdict: absurd in this context.

Second report: glob-parent through webpack-dev-server

This one comes through the chain webpack-dev-server > chokidar > glob-parent. The dev server runs locally during development. chokidar watches your filesystem, and glob-parent extracts path segments from watch patterns. The vulnerability is again a ReDoS — a crafted filepath could cause exponential slowdown. The filepath would have to come from someone with access to your development machine. At that point, they'd steal your secrets, not waste time constructing long file paths.

Verdict: absurd in this context.

Third report: duplicate

The same chain reported again through another dependency path.

Verdict: absurd in this context.

Fourth report: css-what "high" severity

This one is displayed in red. The vulnerability is in css-what, a CSS selector parser used by an SVG-to-React plugin. It's a denial-of-service issue — specially crafted CSS selectors could slow processing. The input in question is an SVG file in your source code. An attacker who can add files to your source can simply plant a bitcoin miner. There's no threat model where crafty SVG files are the winning move.

Verdict: absurd in this context.

Fifth report: another duplicate

The same css-what issue flagged again through a different chain.

Verdict: absurd in this context.

The scope of the problem

Five warnings so far, all false alarms. That alone would be tolerable. But these are just the first few in hundreds of similar reports. A review of every npm audit issue filed against Create React App over many months found that all of them were false positives for a build tool context.

The fixes might be possible — adjusting dependency ranges or shipping releases to stay ahead of the noise — but that would be treating symptoms rather than the underlying problem. Imagine a test suite failing 99% of the time for bogus reasons.

The consequences cascade across every level of experience:

  • Beginners are greeted with alarming "high severity" warnings the day they create their first project, with no context to distinguish real threats from noise.
  • Experienced developers waste time on unnecessary work or fight with security teams over false positives produced by a broken tool.
  • Maintainers burn time on bogus vulnerability fixes pulled in to keep users from panicking.
  • Users ultimately learn to ignore the warnings entirely, because the experienced developers are right about them being meaningless.

In a test today, npm audit fix --force went a step further: it downgraded the main dependency to a three-year-old version containing real, actual vulnerabilities. The recommended fix actively made the situation worse.

The message to npm is clear: the tool needs to be context-aware, distinguishing between packages used at runtime versus build-time tooling. And it cannot ship as a default block on every install until the false-positive rate is addressed. The best time to have fixed this was before making it a standard part of every workflow. The next best time is now.

The Failure Mode

npm audit was supposed to be a safety net, a default-for-on behavior that alerts developers to known vulnerabilities in their dependency trees. In practice, it has become a generator of high-volume, low-signal warnings that obscure the real risks it was designed to surface. The false positive rate in many projects exceeds 99%, and those false positives do not merely clutter a report — they actively train developers to ignore the tool entirely, which is precisely when a genuine, exploitable vulnerability can slip through unnoticed.

Part of the problem is the tool's failure to respect the distinction between runtime and development concerns. The ecosystem has mechanisms like devDependencies to denote packages that don't ship to production. Yet npm audit still flags issues in those packages by default, requiring users to know about and run the npm audit --production flag to get a relevant report. Even when that flag is used, plain npm install still consults the full audit data, so the noise persists for anyone who installs dependencies often, whether they're a beginner or a developer under pressure from a security team that demands a clean audit.

The assumption that development dependencies are safe to ignore is itself misguided in a supply-chain era. Those packages run during build and test phases with elevated trust and often broader access than production runtime code, making them a prime and dangerous attack vector. The compounding effect is that maintainers and users see dozens of dead-end advisories for every legitimate one, and the effort required to triage that noise leads to the entire tool being treated as a nuisance.

Bad Incentives, Worse Responses

The industry hasn't stood still in the face of this problem, but several apparent workarounds reveal how severely the tool's incentives have skewed behavior. One prominent approach is inline bundling, as seen in projects like Vite and Next.js, which now ship their dependencies bundled directly rather than relying on the npm node_modules maze. The upside for maintainers is tangible — faster boot times, smaller download sizes, and a fortuitous side effect of avoiding the barrage of vulnerability reports from users who run npm audit on their published artifacts. But inlining dependencies is, at its core, a way to evade the system, not fix it, and it chips away at the package management model that makes npm's registry valuable in the first place.

Other proposals target the vulnerability reporting mechanism itself. One npm RFC suggests giving developers a way to manually mark an advisory as resolved so it stops re-appearing in future runs. That shifts the burden wholesale onto the file's consumers — the developers installing a package — who rarely have the context to know whether an advisory deep in their dependency tree is real or theoretical. A more maintainer-centric view argues for the inverse: let the publisher state which advisories cannot materially affect users of that package. The logic there is straightforward: if you don't trust a maintainer's judgment on a vulnerability, you probably shouldn't be running their code at all. Other paths are possible, but the conversation has remained stuck on the same themes because the default behavior itself is the root defect.

Bundling and counter-claims are both attempts to patch a routing problem: audit data is surfaced to whoever runs the command, but only the person or team that wrote the code can determine whether an advisory applies to how that code is used in any real scenario. The burden currently sits exactly where it should not, and the system at some point will yield a high-profile compromise that was drowned out by the noise it generated.

When Tools Train Users To Ignore Them

The question of what to do next is less about the technical details of dependency resolution and more about trust. It yields an unusually acute failure for newcomers: being greeted with a wall of red warnings that are all false positives their very first time out gives npm audit the same credibility as a pop-up ad. For those with experience, it leads to tunnel vision toward the handful of real issues that manage to rise above the noise. For maintainers, it turns every GitHub issue filed from an audit scan into a triage chore that has more to do with denying bogus warnings than with fixing code.

Many maintainers are running out of patience. It will be the default to close audit-referencing issues outright unless a report demonstrably identifies a real, exploitable vulnerability — a response that will frustrate users but that’s directed at bringing pressure on the tool’s design, not the report’s author. This is being framed as a refusal to participate in security theater any longer.

The phrase by design is not hyperbole here: a 99% false positive rate and an inability to correlate reported issues with actual risk isn't an accident, but a structural fault, and the result at the ecosystem level is a generation of developers trained to dismiss their own security tooling.

That makes the price of a real vulnerability arriving unnoticed not a possibility, but an eventuality — unless the underlying assumptions of how npm handles security advisories get revisited and prioritized above the status quo of noisy defaults.