Vibe Coding, Security
Why npm audit fix Isn't Working (and How to Actually Fix the Vulnerabilities)

If you ran
This post explains exactly why the command stalls, why
npm audit fix, watched it churn, and got fixed 0 of 14 vulnerabilities — or worse, it "fixed" them and broke your build — you're not doing anything wrong. npm audit fix is conservative by design, and the vulnerabilities it leaves behind are almost always transitive dependencies: packages you never installed directly, pulled in three or four levels deep by something you did install.This post explains exactly why the command stalls, why
--force is a trap, and the three reliable ways to fix vulnerable dependencies without blowing up your app.Join the Vibe Coding Community
Swap builds, trade feedback, and ship faster alongside other people vibe coding.
The One-Line Answer
npm audit fix only upgrades a package when it can do so without violating the semver range another package depends on. When the only available patch requires a major-version bump somewhere in the chain, npm refuses to touch it and reports it as unfixed.To resolve those, you either upgrade the parent that pulls in the vulnerable package, or override the transitive dependency directly with an
overrides block in package.json.Why npm audit fix Reports "Fixed 0 Vulnerabilities"
Here's the mechanic. Your
When a vulnerable package is buried in the tree, npm can only auto-fix it if a patched version exists that still satisfies every range pointing at it. If
This is why the usual panic moves don't help:
Deleting node_modules and reinstalling rebuilds the exact same tree from your lockfile. Same versions, same vulnerabilities.
Deleting package-lock.json re-resolves ranges but lands on the same constrained versions, because the ranges in
Running npm audit fix again does nothing new — it already did everything it was allowed to do on the first pass.
If you see a message like
package.json lists direct dependencies with version ranges (^4.2.0, ~1.9.0). Each of those brings its own dependencies, which bring theirs, and so on. The vulnerability scanner walks that whole tree.When a vulnerable package is buried in the tree, npm can only auto-fix it if a patched version exists that still satisfies every range pointing at it. If
some-lib@^2 depends on vulnerable-pkg@^1.0.0, and the patch only lands in vulnerable-pkg@2.0.0, npm won't install it — that would break some-lib's declared contract. So it skips it and counts it as unresolved.This is why the usual panic moves don't help:
Deleting node_modules and reinstalling rebuilds the exact same tree from your lockfile. Same versions, same vulnerabilities.
Deleting package-lock.json re-resolves ranges but lands on the same constrained versions, because the ranges in
package.json haven't changed.Running npm audit fix again does nothing new — it already did everything it was allowed to do on the first pass.
If you see a message like
fixed 0 of 14 vulnerabilities or a list of issues marked as requiring manual review, you've hit the transitive-dependency wall. That's the signal to stop re-running the command and switch strategies.Why --force Is Usually the Wrong Move
The tempting next step is
There's a narrow case where
If you can't commit to testing immediately after, don't run it.
npm audit fix --force. Don't reach for it reflexively.--force tells npm to ignore semver and install whatever version clears the vulnerability — including major-version bumps on your direct dependencies. That means it can silently jump react-scripts from v4 to v5, or webpack from v4 to v5, introducing breaking changes you didn't ask for. Your audit might come back clean while your build is now broken, your config is incompatible, and you have no idea which of a dozen forced upgrades caused it.There's a narrow case where
--force is legitimate: when the only available remediation genuinely requires changing your root project's dependency ranges and you've accepted that. Even then, the rule is non-negotiable:npm audit fix --force
npm run build # must pass
npm test # run the full suite
# then manually click through critical pathsIf you can't commit to testing immediately after, don't run it.
The Three Fixes That Actually Work
1. Upgrade the Parent Dependency
The cleanest fix is to update the direct dependency that drags in the vulnerable package. Newer versions of the parent often already depend on a patched version.Find what's pulling in the vulnerable package:
npm ls vulnerable-pkgThis prints the dependency path — the chain from your direct dependency down to the vulnerable one. Upgrade the package at the top of that chain:
npm install some-lib@latestThen re-audit. If the parent's latest version moved to a safe transitive version, the warning disappears with zero risk to the rest of your tree.
2. Use overrides to Pin a Safe Transitive Version
When the parent hasn't shipped a fix yet — common with slow-moving or abandoned packages — you can force the resolution yourself. npm 8.3+ supports anoverrides block in package.json (the equivalent of Yarn's resolutions):{
"overrides": {
"vulnerable-pkg": "2.0.1"
}
}You can also scope an override so it only applies under a specific parent, which is safer:
{
"overrides": {
"some-lib": {
"vulnerable-pkg": "2.0.1"
}
}
}Then reinstall:
rm -rf node_modules package-lock.json
npm install
npm auditThis forces every copy of
vulnerable-pkg (or just the ones under some-lib) to the patched version without waiting for the whole chain to catch up. Test afterward — you're overriding a version contract, so make sure the patched package is actually compatible.3. Decide Which Findings You Can Safely Defer
Not every audit finding is a real risk to your app. A vulnerability in a package that only runs in a build script, or one that's only exploitable via an input path your app never exposes, may not be worth a risky upgrade.npm audit reports severity and the vulnerable path, but it doesn't know your runtime — a "high severity" prototype-pollution bug in a dev-only dependency is a very different thing from the same bug in your request-handling path.The honest workflow is to read each advisory, check whether the vulnerable code is reachable in production, and prioritize accordingly — fix what's exploitable now, schedule the rest, and don't let a wall of dev-dependency warnings push you into a
--force that breaks prod. For the bigger picture on which security work actually matters at each stage, see what every vibe coder needs to know about security.A Repeatable Order of Operations
When
1.
2.
3. Try upgrading that parent (
4. For anything still stuck, add a scoped
5. Only consider
6. For findings that aren't reachable in production, document the decision and move on.
That's the whole game: stop fighting the auto-fixer, figure out who pulls in the vulnerable package, and fix it at the right level of the tree.
Vulnerable dependencies are only one of the risks AI-assembled apps inherit. Our guide to the best vibe coding security tools covers the other four — leaked secrets, injection, missing tests, and insecure defaults — with tools for each.
npm audit fix stalls, run this sequence:1.
npm audit — read the actual advisories, severities, and paths. Don't just look at the count.2.
npm ls <pkg> for each vulnerable package — find the direct dependency responsible.3. Try upgrading that parent (
npm install <parent>@latest). Re-audit.4. For anything still stuck, add a scoped
overrides entry pinning the patched version. Reinstall and re-audit.5. Only consider
--force if remediation truly requires root-range changes — and gate it behind a full build + test run.6. For findings that aren't reachable in production, document the decision and move on.
That's the whole game: stop fighting the auto-fixer, figure out who pulls in the vulnerable package, and fix it at the right level of the tree.
Vulnerable dependencies are only one of the risks AI-assembled apps inherit. Our guide to the best vibe coding security tools covers the other four — leaked secrets, injection, missing tests, and insecure defaults — with tools for each.
Find Every Vulnerable Dependency in Your Repo in One Pass
The manual
Differ connects to your GitHub repo and runs a full security scan on first import — no live traffic, no setup. It surfaces vulnerable and outdated dependencies, tells you which are exploitable in your app's real paths, and proposes the specific upgrade or override for each one, so you're not running
Connect your repo and get the free report in seconds — it'll show you exactly which of these vulnerabilities are worth fixing first. A clean dependency baseline is also step one toward an agent ready codebase — the repos AI coding agents can actually work in.
npm ls / override loop works, but it's slow when you've got a dozen findings across a real codebase — and npm audit alone won't tell you which ones are actually reachable in your app versus buried in dev tooling.Differ connects to your GitHub repo and runs a full security scan on first import — no live traffic, no setup. It surfaces vulnerable and outdated dependencies, tells you which are exploitable in your app's real paths, and proposes the specific upgrade or override for each one, so you're not running
npm ls a dozen times by hand.Connect your repo and get the free report in seconds — it'll show you exactly which of these vulnerabilities are worth fixing first. A clean dependency baseline is also step one toward an agent ready codebase — the repos AI coding agents can actually work in.