4 min read

Why npm audit fix Isn't Working

Why npm audit fix Isn't Working
npm audit fix issues

You ran npm audit, saw a wall of vulnerabilities, ran npm audit fix, and nothing changed. The same warnings stare back at you. You try again. Same result. If this sounds familiar, you're not alone.

This is one of the most common frustrations in JavaScript development. The command promises to fix vulnerabilities but often leaves you exactly where you started. Understanding why this happens is the first step to actually resolving the problem.

Why npm audit fix Fails

The npm audit fix command only updates packages within your declared semver ranges. If your package.json specifies lodash@^4.17.0 and a vulnerability exists in versions below 4.17.21, npm audit fix will update to 4.17.21. But if the fix requires jumping to version 5.x, npm audit fix won't touch it because that would break your stated dependency range.

This design choice is intentional. Major version updates often include breaking API changes. Automatically upgrading could break your application in ways that are harder to debug than the original vulnerability.

The problem gets worse with transitive dependencies. When a vulnerability exists three levels deep in your dependency tree, you don't control the version directly. You depend on package A, which depends on package B, which depends on vulnerable package C. Even if a fix exists for package C, your update path depends on package B releasing a new version that uses it, and package A updating to use that new version of B.

The --force Flag Trap

When npm audit fix doesn't work, the tempting next step is npm audit fix --force. This tells npm to ignore semver constraints and install whatever version resolves the vulnerability, even if it means jumping major versions.

This is risky. According to npm's own documentation, the --force flag "allows npm audit fix to install modules outside your stated dependency range, including semver-major changes" (npm). In practice, this can introduce breaking changes that don't surface immediately.

Typical developer experience: after running npm audit fix --force, npm downgraded their main dependency to a three-year-old version with actual real vulnerabilities (Overreacted). The "fix" made things worse.

The danger compounds in team environments. One developer runs --force, commits the changes, and suddenly the entire team's local environment breaks. Features that worked yesterday fail mysteriously. Debugging becomes a nightmare because nobody immediately connects the failures to a "security fix" from days earlier.

When Vulnerabilities Don't Actually Matter

Not every reported vulnerability poses a real threat to your application. npm audit doesn't distinguish between production code and build tools.

Consider a ReDoS (Regular Expression Denial of Service) vulnerability in a package that only runs during your build process. An attacker would need to control your build inputs to exploit it. That's a very different risk profile than the same vulnerability in code serving user requests, the scope changes significantly.

Similarly, prototype pollution in a development dependency like a testing framework has minimal real-world impact compared to the same vulnerability in your production API server.

npm audit reports all of these equally, which is why security teams and developers often find themselves chasing fixes that don't actually improve their security posture.

Practical Solutions

1. Identify What Needs Fixing

Before attempting fixes, understand what you're dealing with:

npm audit --production

This shows only vulnerabilities in production dependencies, filtering out devDependencies that don't ship to users. Many "critical" warnings disappear with this flag.

2. Manual Version Resolution

For transitive dependency issues, you can override nested dependency versions using npm's overrides field in package.json:

{
  "overrides": {
    "vulnerable-package": "^2.0.0"
  }
}

For Yarn users, the equivalent is the resolutions field. This forces a specific version regardless of what parent packages request.

3. The Nuclear Option (Use Carefully)

If you're comfortable with potential breaking changes and have good test coverage:

rm -rf node_modules package-lock.json
npm install
npm audit fix

Deleting the lock file forces npm to resolve all dependencies fresh. This sometimes clears conflicts that were blocking fixes. But you lose reproducibility guarantees, so only do this if you can thoroughly test afterward.

4. Accept and Document

Some vulnerabilities genuinely cannot be fixed because:

  • The maintainer hasn't released a patched version
  • A fix would require breaking changes your codebase isn't ready for
  • The vulnerability doesn't apply to your usage pattern

In these cases, document the decision. Create an .nsprc file or use npm audit with --audit-level to suppress known issues you've evaluated and accepted.

Beyond npm audit: Alternative Approaches

npm audit checks against a single vulnerability database after issues are publicly disclosed. It doesn't catch newly compromised packages or evaluate package health signals like maintenance activity or suspicious version releases.

For broader coverage, I built a custom npm vulnerability scanner that queries the deps.dev API. This provides visibility into package age, maintenance status, and vulnerabilities from multiple security databases that npm audit might miss. Feel free to clone the repo and add your own spin.

For context on why supply chain security matters beyond known CVEs, particularly with attacks like the recent Shai-Hulud worm that compromised 796+ packages, see my piece on auditing the npm supply chain.

Preventive Measures

Rather than fighting npm audit after the fact, consider these practices:

  • Pin exact versions in production applications using npm install --save-exact
  • Use npm ci instead of npm install in CI/CD pipelines for reproducible builds
  • Review before updating rather than blindly running fix commands
  • Separate dev and prod dependencies carefully so audit reports are more actionable
  • Consider package age policies where new package versions have a cooling-off period before adoption

Summary

npm audit fix isn't broken, but it's limited by design. It prioritises not breaking your application over fixing every reported vulnerability. When it fails, that's often the system working as intended, refusing to make changes that could cause bigger problems than the vulnerabilities themselves.

The real solution isn't a better command. It's a layered approach: understanding which vulnerabilities actually matter, using manual overrides when automatic fixes can't help, and supplementing npm audit with tools that catch what it misses.

Effective npm security requires attention across four domains:

  1. Governance and Policy: Clear standards for dependency selection and update procedures
  2. Technical Controls: Version pinning, lock files, and multiple scanning tools
  3. Data Security: Understanding what data flows through your dependencies
  4. Human Factors: Developer training on evaluating vulnerability reports critically

Key Resources:

References:

  • npm (2025). "npm-audit" CLI documentation.
  • Overreacted (2025). "npm audit: Broken by Design." Analysis of npm audit limitations by Dan Abramov.
  • InstaTunnel (2025). "Why npm audit fix --force is a Terrible Idea." Medium.
  • Stack Overflow. Multiple community discussions on npm audit fix failures.