npm audit fix only makes changes it can make without breaking the version ranges your project already declares. When the fix needs a major version bump, sits in a transitive dependency whose parent still pins an old range, or does not exist yet, it reports the problem and leaves it alone. You close those gaps with overrides, targeted upgrades, patches or a patched build.
The gap between "npm audit found 14 vulnerabilities" and "npm audit fix fixed 3 of them" is where many JavaScript teams lose an afternoon, and where some reach for --force and break production. This post covers what the command changes and refuses to change, what "No fix available" means, how to force a safe transitive version, how to check reachability, and what to do when nobody upstream has shipped a fix. You do not have to wait for a maintainer before you stop shipping a vulnerable version.
Key takeaways
npm audit fixstays inside your declared semver ranges. Anything that needs a range change, including a SemVer major upgrade of a top level dependency, is left for--force.- Most stubborn findings are transitive: a parent pins a range that excludes the patched version of something deeper in the tree.
- "No fix available" means either no patched release exists, or no release of the parent chain allows it.
--forceapplies breaking major upgrades, and in at least one reported case it moved a package to a version that was still vulnerable.overrides(npm),resolutions(Yarn) and pnpmoverridesforce a patched transitive version. They only work in the root manifest and need tests.- Check reachability before you spend a day on a finding.
- With no upstream fix, you can still patch, fork or use a hardened build. Keep the fix in place with a committed lockfile,
npm ciand an audit gate.
What does npm audit fix change, and what does it refuse to change?
npm audit sends your dependency tree to the registry's Bulk Advisory endpoint (falling back to the older Quick Audit endpoint if that fails, per the npm docs) and gets back matching advisories. npm audit fix takes that report and tries to apply remediations. Three details explain most of the surprise.
It runs a full install under the hood
The docs note that npm audit fix runs a full npm install behind the scenes, so every installer setting you have also applies. If a normal install would refuse to resolve a version, so will npm audit fix.
It respects your semver ranges
This is the big one. The npm docs say that if a fix requires changing the ranges in your package.json, the command "will require the --force option." Without it, npm only picks versions that satisfy the ranges you, and every package in your tree, already declared.
So if you depend on "some-lib": "^2.3.0" and the only patched release is 3.0.1, plain npm audit fix will not touch it. Below 1.0.0, caret ranges are tighter still: the npm update docs show ^0.4.0 resolving within 0.4.x, so a fix released as 0.5.0 is out of range.
It covers transitive packages through "meta-vulnerabilities"
npm does look past your direct dependencies. The docs describe "meta-vulnerabilities": a package is flagged when it depends on a vulnerable version of another package. If the ranges in the tree allow a safe version, npm updates the vulnerable package to a version without an advisory. If they do not, the finding stays.
Here is a quick way to see what the command would do before it does anything:
# What would change, without changing anything
npm audit fix --dry-run --json > audit-fix-plan.json
# The full report, machine readable
npm audit --json > audit.json
# Only production dependencies
npm audit --omit=dev
--package-lock-only updates the lockfile without touching node_modules. --audit-level sets the severity that causes a non zero exit code; it does not filter the printed report.
What the command refuses to do
Without flags, npm audit fix will not change any range in your package.json, install a SemVer major of a top level dependency, force a transitive package outside its parent's range, invent an unpublished fix, or tell you whether the vulnerable code is reachable. Each of those is a decision someone has to make.
Why can't it fix a transitive dependency when the parent has not released an update?
Picture this tree:
your-app
└── report-builder@4.2.0 (you depend on ^4.2.0)
└── template-engine@1.8.3 (report-builder depends on ~1.8.0)
└── tiny-parser@2.1.4 (template-engine depends on 2.1.4, exact)
An advisory lands for tiny-parser, fixed in 2.1.6. Your app never mentions tiny-parser. template-engine pinned it exactly. Nothing in the tree allows 2.1.6, so a normal install cannot resolve it, and neither can npm audit fix. The only versions npm is allowed to choose are the ones every parent agrees to.
The fix has to come from one of these places:
- A new release of
template-enginethat widens its range, followed by a new release ofreport-builderthat allows thattemplate-engine. - A newer major version of
report-builderthat already uses a fixed chain, which is what--forcemight try. - An override in your root manifest that tells npm to ignore the parent's range and use
2.1.6anyway. - A patch applied to the installed copy of
tiny-parser.
Option 1 is the "wait for upstream" path, and in a deep tree it can take weeks or never happen. Options 3 and 4 are yours to take today.
Find the chain first
npm explain, also available as npm why, prints the chain of requirements that caused a package to be installed:
npm explain tiny-parser
Each entry shows the installed version and location, then the range that required it, back to "the root project." Entries marked dev are development only, and duplicate copies are listed separately. That tells you whether the copy ships to production, which parent pins it and how tightly, and whether several parents share it.
What does "No fix available" really mean?
It means npm could not find versions that both satisfy the tree and avoid the vulnerable range. Several situations hide behind that one line.
There is no patched release
Advisories describe affected versions as ranges. The OSV schema, used by many advisory databases, records this with events: introduced marks where the vulnerability begins and fixed marks the first version that contains the fix. If a range has an introduced event and no fixed event, nothing ends the vulnerable span. The OSV spec gives the example of a range that starts at "0" with no fix, which "expresses that every possible version is affected."
The spec also describes last_affected, used when no fixed version exists. It is treated as "the hard ceiling of the vulnerability at the time of publication," and versions above it are assumed unaffected. The spec warns that this can produce false negatives if a later version is also vulnerable. Read that carefully: an advisory that lists a last affected version is not the same as one that lists a fix.
So "No fix available" may be literally true: the project is unmaintained, the fix is hard, or the report is disputed.
A fix exists, but not anywhere your tree can reach
The vulnerable package may have a fixed release while every version of its parent pins an older range. No version of your direct dependency produces a clean tree, so npm still reports no fix. This is common with abandoned middle layers, such as a small helper last published years ago that pins an exact version of something since patched.
The fix requires a major version change you have not allowed
Here the report usually points you at npm audit fix --force and flags the change as breaking. That is "no fix inside your rules," not "no fix." The JSON report records, per package, whether a fix is available and whether it is a SemVer major change, which helps when scripting triage.
What to do with each case
| What the report is really saying | How to confirm | What to do |
|---|---|---|
| No patched version of the vulnerable package exists | Check the advisory's affected and fixed versions; look for a fixed event | Check reachability, then patch, fork or use a hardened build; add a compensating control |
| A patched version exists but the parent's range excludes it | npm explain <pkg> shows a parent with a tight range | Override the transitive version and run your tests |
| A fix needs a SemVer major bump of your direct dependency | npm audit fix --dry-run or the JSON report shows a major change | Upgrade that dependency on purpose, read its changelog, run tests |
| The finding is in a dev only tool | npm explain marks it dev; npm audit --omit=dev drops it | Lower priority; still update when convenient |
| The advisory itself is disputed or does not apply | Read the advisory and the project's issue tracker | Record the decision and the reason; revisit on a date |
When is --force dangerous, and when is it acceptable?
--force lets npm audit fix install versions outside your stated ranges, including SemVer major updates to top level dependencies. That sounds like what you want. Often it is not.
Why --force is risky
It makes breaking changes on your behalf. A SemVer major version means something incompatible changed. --force applies it without reading the changelog or knowing whether your tests cover the affected area.
It can move you backwards. npm CLI issue 7599 reports that npm audit fix --force installed puppeteer@18.1.0, which the reporter said still depended on a vulnerable ws. The reporter later reproduced it with a test package: the first forced fix landed on a version still vulnerable through its dependencies, and a second run moved again. The issue was closed as "not planned." The general lesson is that --force can produce an older major, or a version that only looks clean because nobody has written its advisory yet.
It can break peer dependency agreements. A forced major bump of a UI library or plugin can leave its peers mismatched, and the failure may only show at build time or in the browser.
It changes many things at once. When something breaks, you bisect a lockfile diff instead of reviewing one intentional upgrade.
When it is acceptable
--force is reasonable when you have read every change in its dry run, the major bumps are ones you planned anyway, your tests cover the affected packages, and the lockfile diff goes through review. Most teams are better off doing the same thing one package at a time:
# See what the forced fix wants to do
npm audit fix --force --dry-run
# Make the one major upgrade you actually want
npm install report-builder@5
# Then let the safe, in range fixes happen
npm audit fix
# And prove it still works
npm test
We cover the flag in more depth in what npm audit fix --force actually does and when not to use it.
How do you use overrides, resolutions and pnpm overrides safely?
When a patched version exists but a parent's range blocks it, override the range yourself. All three major JavaScript package managers support this.
npm overrides
npm's overrides field replaces a package anywhere in the tree, for example to move off a version with a known security issue or to swap in a fork. Two rules matter first:
- Overrides are "only considered in the root
package.jsonfile for a project." Overrides inside your dependencies, including workspace packages, are ignored. - You cannot override a package you also depend on directly unless the specs match exactly. Doing so throws an
EOVERRIDEerror. The$reference avoids this by pointing the override at your direct dependency's spec.
The simplest form forces one package everywhere:
{
"overrides": {
"tiny-parser": "2.1.6"
}
}
A range is often better than an exact pin, because it lets you pick up later patches without editing the override:
{
"overrides": {
"tiny-parser": "^2.1.6"
}
}
You can scope an override to one parent, so other parts of the tree are unaffected:
{
"overrides": {
"template-engine": {
"tiny-parser": "^2.1.6"
}
}
}
Keys can also include versions, such as "template-engine@1.8.3", so the override applies only to the parent version with the problem.
If you also depend on the package directly, use the $ reference so the override follows your own declared range:
{
"dependencies": {
"tiny-parser": "^2.1.6"
},
"overrides": {
"tiny-parser": "$tiny-parser"
}
}
Overrides also accept replacement specifiers, which is how you point a transitive dependency at a patched fork. The npm docs show npm:, github: and file: forms, including this pattern for a transitive dependency:
{
"overrides": {
"parent-package": {
"vulnerable-dep": "github:your-org/vulnerable-dep#v2.0.1-patched"
}
}
}
After editing overrides, reinstall and check the result:
npm install
npm explain tiny-parser
npm audit
npm test
If npm explain still shows the old version, the lockfile may be holding on to it. Run the install again with a clean node_modules, and check that you edited the root package.json rather than a workspace manifest.
Yarn resolutions
Yarn uses a resolutions field in the root package.json. The Yarn Classic docs name security as a main reason: a fix lands in a sub dependency and you do not want to wait for your direct dependency. Their example:
{
"resolutions": {
"d2/left-pad": "1.1.1",
"c/**/left-pad": "^1.1.2"
}
}
The first key pins left-pad under d2; the second applies a range anywhere below c. Yarn Classic warns when a resolution is incompatible with the original range, and its docs admit "nested packages may not work properly" in some cases. Current Yarn keeps resolutions root only, with keys like "webpack/memory-fs": "0.4.1", and describes it as a way to "backport a fix."
yarn install
yarn why tiny-parser
pnpm overrides
pnpm documents overrides in pnpm-workspace.yaml at the project root (older pnpm releases read the same setting from a pnpm section in package.json). The documented syntax:
overrides:
"foo": "^1.0.0"
"quux": "npm:@myorg/quux@^1.0.0"
"bar@^2.1.0": "3.0.0"
"qar@1>zoo": "2"
The > selector scopes an override to one parent. The pnpm docs explain that qar@1>zoo "will only override the zoo dependency of qar@1, not for any other dependencies." A key can carry a version range, so bar@^2.1.0 only matches that range. You can also remove a dependency a package does not need with "-", which the docs say is especially useful with optional dependencies.
pnpm goes further than npm here. Its docs say pnpm audit --fix adds overrides to pnpm-workspace.yaml "in order to force non-vulnerable versions of the dependencies." From pnpm 11, --fix=update updates the lockfile instead and --interactive lets you pick advisories. --ignore-unfixable marks every advisory without a resolution as ignored, which quiets the report without making you safer.
Using overrides safely
Overrides overrule a maintainer's range, so use them with care.
- Prefer the smallest scope. A parent scoped override changes one edge of the graph; a global one changes every consumer.
- Prefer a patch level move.
2.1.4to2.1.6is low risk. A different major under a parent never tested with it is a migration, not a fix. - Run tests that exercise the parent. It is now running against code its authors never tried.
- Write down why. JSON has no comments, so record the advisory ID, date and removal condition in the pull request and a short file next to the manifest.
- Remove it later. Once the parent widens its range, the override is dead weight. Give it a review date.
For the general pattern across ecosystems, including pip, Maven and Gradle, see how to fix a vulnerable transitive dependency.
How do you tell whether the vulnerable code is reachable at all?
Not every finding deserves the same urgency. Dan Abramov's 2021 essay "npm audit: Broken by Design" argued that the findings on a fresh Create React App install were false positives for a project that compiles to static files, such as a regular expression denial of service in the local development server. His point was that noise trains people to ignore audits.
The real question is whether untrusted input can reach the vulnerable code in the environment where it runs.
Step 1: production or development
npm audit --omit=dev
npm explain tiny-parser
If the finding disappears with --omit=dev and npm explain marks the chain dev, the package is a build, test or lint tool. That lowers priority but not to zero: build tools run on laptops and CI runners that hold credentials, and Abramov himself notes that development dependencies can be a real attack vector.
Step 2: read the advisory for the vulnerable function
Good advisories name the function, option or input that triggers the bug. A flaw that only fires with an option you never enable is not exploitable. A denial of service in code that parses your own config file is a different risk from the same bug in code that parses request bodies.
Step 3: search your code and your bundle
# Where does your own code import it?
grep -rn "tiny-parser" src/
# Which of your direct dependencies call the vulnerable function?
grep -rn "parseUnsafe" node_modules/template-engine/
For front end code, check whether the package ends up in the shipped bundle or only in the build pipeline.
Step 4: weigh exploitation signals
EPSS, from FIRST, is "a data-driven machine-learning model" that estimates the probability a published CVE will be exploited in the next 30 days, updated daily. CISA's Known Exploited Vulnerabilities catalog lists vulnerabilities exploited in the wild, and CISA says organisations "should use the KEV catalog as an input" to prioritisation. A reachable finding with a high EPSS score or a KEV entry goes to the top. Many npm advisories have no CVE and so no EPSS score; treat that as missing data, not a low score.
Step 5: record the verdict
Write down the advisory, whether it is reachable, why, who decided and when you will look again. That record lets you suppress a finding without losing it.
Automated reachability analysis does steps 2 and 3 at scale. More on the method in reachability analysis for JavaScript and TypeScript.
| Signal | Raises priority | Lowers priority |
|---|---|---|
| Dependency type | Production, shipped to users or servers | Dev only, build tool, test runner |
| Call path | Your code calls the vulnerable function with untrusted input | Vulnerable function never called, or only with trusted input |
| Exploitation data | KEV listed, high EPSS | No known exploitation, low EPSS |
| Exposure | Internet facing service, parses user input | Offline tool, internal batch job |
| Fix cost | Patch level bump or scoped override | Major migration across many packages |
What do you do when the only fix is an upstream patch that does not exist yet?
This is the case where many teams simply wait. You do not have to.
Option A: patch the installed package yourself
All three package managers support applying a patch file to a dependency at install time.
In pnpm, the workflow is built in:
pnpm patch tiny-parser@2.1.4
# edit the files in the temporary directory pnpm prints
pnpm patch-commit <path-printed-above>
The pnpm docs say patch-commit generates a patch file and registers it under patchedDependencies. Patches can target an exact version, a range or a package name, with exact versions taking priority. In pnpm 11, a patch that fails to apply always throws an error, which is what you want: a silent patch failure would reinstall the vulnerable code. pnpm also notes that you should not use a patch to change a package's own dependencies; use overrides for that.
In npm projects, the common approach is a small open source tool that diffs your edits to node_modules into a patch file and re applies it after every install from a postinstall script. Modern Yarn has a built in patch: protocol for the same purpose.
Patching is a good fit when the fix is small and well understood, for example an upstream commit that has landed but not been released. Copy that commit's change, nothing more, so the patch is easy to review and easy to drop when the release ships.
Option B: point an override at a patched fork
If the change is larger, or several projects need it, fork the package, apply the fix, publish it to your private registry or tag it in your own repository, and override the transitive dependency to point at it:
{
"overrides": {
"template-engine": {
"tiny-parser": "npm:@your-org/tiny-parser@2.1.4-patched.1"
}
}
}
The npm docs show npm:, github: and file: replacement specifiers for exactly this. A published version in a registry you control beats a git branch URL, because it is immutable and shows up cleanly in the lockfile. The cost is ownership: track the upstream issue, offer your fix back, and drop the fork when upstream releases.
Option C: replace or remove the dependency
If an unmaintained helper is the only thing pulling in a vulnerable library, replacing it with a maintained alternative or a few lines of your own code removes the whole chain. pnpm's "-" override can drop a dependency a package declares but does not need.
Option D: add a compensating control while you work
Validate or size limit the input that reaches the vulnerable function, put the feature behind a flag, or run the service with fewer privileges. These are stopgaps, and they belong in the same record as your reachability verdict.
Option E: use a build someone has already patched
Patching and forking cost engineering time on every project that carries the package. The alternative is a hardened build with the fix already applied and tested, from a source that publishes evidence of what changed. See the section on fixing without waiting for upstream below.
How do you keep the fix from being undone at the next install?
A fix that only lives in one developer's node_modules is not a fix. Here is the checklist that makes it stick.
1. Commit the lockfile, and install from it in CI
The lockfile records the exact versions you tested. Per the npm docs, if it does not match package.json, npm ci "will exit with an error, instead of updating the package lock," and it never writes to either file. Use it for CI and production builds. More on this in npm lockfile security best practices.
2. Keep overrides and patches in the root manifest
Overrides and resolutions only work at the root. If someone moves a package into a workspace and copies the override with it, the override silently stops applying. Treat the root manifest's overrides, resolutions and patchedDependencies as security configuration, and require review on changes to them (a code owners rule on package.json and pnpm-workspace.yaml works well).
3. Gate the build on the audit
Fail CI when a new high or critical finding appears in production dependencies. A minimal GitHub Actions workflow:
name: dependency-audit
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install exactly what the lockfile says
run: npm ci
- name: Fail on high or critical findings in production dependencies
run: npm audit --omit=dev --audit-level=high
- name: Confirm the patched version is still in the tree
run: npm explain tiny-parser
--audit-level=high sets the exit threshold, not the printed output, so the log still shows everything. Pin third party actions to a full commit SHA in production workflows.
4. Watch for the override being bypassed
A fix is commonly undone when someone deletes the lockfile to "fix" a merge conflict, or an unrelated install rewrites a resolution. Reviewing lockfile diffs catches both, and a CI step like the npm explain one above asserts the patched version is installed.
5. Revisit on a schedule
Overrides, patches and forks are temporary. Once a month, check whether upstream has released a fix and remove what is no longer needed.
Step by step: working a stubborn npm audit finding
The full loop, from report to merged fix:
# 1. Clean baseline on a branch
git checkout -b fix/audit-$(date +%Y%m%d)
npm ci
npm audit --omit=dev
# 2. Take the safe, in range fixes first, in their own commit
npm audit fix --dry-run
npm audit fix
npm test
git commit -am "Apply in-range audit fixes"
# 3. Triage what is left
npm audit --json > audit.json
npm explain tiny-parser
# 4. Blocked transitive fix: add a scoped override, then verify
npm install
npm explain tiny-parser
npm test
# 5. Fix that needs a major bump: do it on purpose, in its own commit
npm install report-builder@5
npm test
Between steps 3 and 4, check reachability and park dev only or unreachable findings with a written verdict. For anything with no fix, choose patch, fork, replace or a compensating control. Open the pull request listing each advisory, what you did and when each override should be removed, and let CI run the audit gate.
How the options compare
| Approach | Fixes transitive findings | Can cross a major version | Works when no upstream fix exists | Main risk | Best for |
|---|---|---|---|---|---|
npm update | Only within ranges | No | No | Low | Routine freshness |
npm audit fix | Only within ranges | No | No | Low | First pass on every report |
npm audit fix --force | Sometimes | Yes, for top level packages | No | Breaking changes, possible downgrades | Rarely; after a dry run you have read |
| Manual upgrade of a direct dependency | Yes, if the new version uses a fixed chain | Yes, on purpose | No | Migration work | Fixes that need a major bump |
overrides / resolutions / pnpm overrides | Yes | Possible, but risky | Only if pointed at a patched fork | Parent never tested against the forced version | Patched transitive version exists but is blocked |
Patch file (pnpm patch, patch: protocol, postinstall patching) | Yes | Not applicable | Yes | Patch drift, silent failure on older tools | Small, well understood fixes |
| Private fork plus override | Yes | Not applicable | Yes | Ongoing ownership | Larger fixes or many consumers |
| Hardened patched build | Yes | Not applicable | Yes | Depends on the provider's evidence | Teams that do not want to own forks |
| Accept with a written verdict | Not a fix | Not applicable | Yes | The verdict goes stale | Unreachable or dev only findings |
Common mistakes
Running npm audit fix --force on main without a dry run. It applies breaking changes in bulk and can move packages backwards.
Putting overrides in a workspace package. All three package managers only honour them at the root. The override looks fine and does nothing.
Overriding across a major version under an untested parent. You swap a known vulnerability for an unknown break.
Using a global override when a scoped one would do. It changes consumers you never looked at.
Deleting the lockfile to resolve a conflict. That reinstalls whatever the ranges allow today and can quietly drop your fixes.
Treating --audit-level as a filter. It only sets the exit code threshold.
Silencing what you cannot fix. Ignore lists and "ignore unfixable" switches make CI green without making you safer. Every ignored advisory needs a reason and a review date.
Forgetting to remove overrides. An exact pin can hold back future security releases.
Reading "last affected" as "fixed in the next version." In the OSV schema, last_affected is a ceiling at publication time, not a promise.
Fix it without waiting for upstream
Everything above is how to work npm audit findings by hand. The stubborn ones, transitive pins, major bumps and advisories with no patched release, are exactly where teams fall back on waiting for a maintainer, and that wait is optional.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
How this maps to the npm audit cases
The findings npm audit fix leaves behind, a deliberate major upgrade or a transitive version held back by its parent, are where a remediation pull request saves the most time: it makes the change, runs your tests and shows you a diff instead of a ticket. Strategies run from Safest (backward compatible only) through Balanced (minor and patch) to Aggressive (including majors), plus Smart, which picks per item, with breaking change detection in the flow. Where npm audit says no fix is available, Gold Open Source shows whether a safe version exists anywhere, and Gold Registry and the early access fork and patch flow do not depend on a maintainer's release.
FAQ
Why does npm audit fix do nothing?
Every remaining finding needs a change outside your declared semver ranges, or has no published fix. Run npm audit fix --dry-run to see what it could do, and npm explain <package> to see which parent holds the old version.
Why does npm audit still show vulnerabilities after npm audit fix?
The leftovers are usually transitive packages pinned by a parent, fixes that need a SemVer major bump, or advisories with no patched release. They need an override, a deliberate upgrade, or a patch or patched build.
Is it safe to run npm audit fix --force?
Not blindly. It applies breaking major upgrades and has been reported to land on versions that were still vulnerable. Dry run it, read every change, and prefer one upgrade at a time with tests.
How do I fix a vulnerability in a transitive dependency in npm?
If a patched version exists, add a scoped overrides entry in your root package.json, then run npm install, npm explain <package> and your tests. If none exists, patch, fork or use a hardened build.
What does "No fix available" mean in npm audit?
Either the vulnerable package has no patched release, or no version of its parents allows the patched one. The advisory's fixed version tells you which.
What is the difference between npm overrides and Yarn resolutions?
Same goal, different syntax. npm overrides nest by parent and support a $ reference; Yarn resolutions use path keys like parent/child; pnpm uses a parent>child selector. All three only work at the root.
Does pnpm audit --fix work differently from npm audit fix?
Yes. It writes overrides to pnpm-workspace.yaml by default, so it can fix transitive findings npm leaves behind. From pnpm 11, --fix=update updates the lockfile instead.
Should I ignore npm audit warnings for devDependencies?
Lower their priority, but do not ignore them. Build and test tools run on machines with credentials. Record why each dev finding is acceptable.
How do I stop npm audit fixes from being undone?
Commit the lockfile, use npm ci in CI, keep overrides in the root manifest under code review, gate the build on new high findings, and review overrides monthly.
Can I fix a vulnerable npm package if the maintainer never releases a fix?
Yes. Apply a patch file at install time, override to a patched fork, replace the dependency, or use a hardened build from a provider that publishes what it changed.
See how Safeguard would help
The fixes in this post come down to making a dependency change safely with a human in the loop, and checking your tree before and after. To try these flows on your own repositories, download the apps at safeguard.sh/download and read the CLI documentation.
This first demo is a real Safeguard CLI session. You ask it to fix the lodash CVE in a repository; it reads the finding and the advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. The same flow applies to the deliberate, one package at a time upgrade this post recommends instead of a blind npm audit fix --force.
Click through it at your own pace, or open it full screen.
The second demo sets up the Safeguard runner on your own machine, approves it with its device code, and then scans from the terminal. Use it to scan the repository before and after you add an override or patch, so you can confirm the vulnerable version is really gone from the tree rather than trusting one install.
Click through it at your own pace, or open it full screen.
The third demo is a real Android session: you send work to a computer running the CLI and approve each command on the phone with Allow once, Allow for turn or Deny. That fits the moment when a dependency fix is ready to install and test and you are away from your desk; nothing runs on your machine until you approve it.
Click through it at your own pace, or open it full screen.
Quick check
Your package.json depends on "some-lib": "^2.3.0" and the only patched release is 3.0.1. Will plain npm audit fix install it?
No. A caret range on 2.x accepts versions below 3.0.0, so 3.0.1 is outside it. npm audit fix only works inside your declared ranges; you need a deliberate upgrade to the new major, or --force after a dry run you have read.
You add an npm override in a workspace package's package.json and the vulnerable version is still installed. Why?
npm only considers overrides in the root package.json of the project. Overrides in dependencies and workspace packages are ignored during tree resolution. Move the override to the root manifest and reinstall.
Sources
- npm Docs: npm audit (CLI v11)
- npm Docs: package.json, overrides (CLI v11)
- npm Docs: npm explain (CLI v11)
- npm Docs: npm ci (CLI v11)
- npm Docs: npm update (CLI v11)
- npm CLI issue 7599: npm audit fix --force installs a vulnerable version
- pnpm Docs: pnpm audit
- pnpm Docs: dependency resolution settings, overrides
- pnpm Docs: settings reference
- pnpm Docs: pnpm patch
- Yarn Classic Docs: selective dependency resolutions
- Yarn Docs: manifest, resolutions
- OpenSSF: OSV schema
- FIRST: Exploit Prediction Scoring System (EPSS)
- CISA: Known Exploited Vulnerabilities Catalog
- Dan Abramov: npm audit, Broken by Design (July 2021)
Last reviewed: 22 October 2026. Package manager behaviour changes between releases; check the linked documentation for the version you run.