Safeguard
Supply Chain Security•

Why Doesn't npm audit fix Actually Fix My Vulnerabilities?

npm audit fix only makes changes inside your semver ranges. Transitive pins, major bumps and missing patches need overrides or a patched build instead.

Safeguard Security Team
33 min read

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 fix stays 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.
  • --force applies 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 pnpm overrides force 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 ci and 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:

  1. A new release of template-engine that widens its range, followed by a new release of report-builder that allows that template-engine.
  2. A newer major version of report-builder that already uses a fixed chain, which is what --force might try.
  3. An override in your root manifest that tells npm to ignore the parent's range and use 2.1.6 anyway.
  4. 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 sayingHow to confirmWhat to do
No patched version of the vulnerable package existsCheck the advisory's affected and fixed versions; look for a fixed eventCheck reachability, then patch, fork or use a hardened build; add a compensating control
A patched version exists but the parent's range excludes itnpm explain <pkg> shows a parent with a tight rangeOverride the transitive version and run your tests
A fix needs a SemVer major bump of your direct dependencynpm audit fix --dry-run or the JSON report shows a major changeUpgrade that dependency on purpose, read its changelog, run tests
The finding is in a dev only toolnpm explain marks it dev; npm audit --omit=dev drops itLower priority; still update when convenient
The advisory itself is disputed or does not applyRead the advisory and the project's issue trackerRecord 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.json file 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 EOVERRIDE error. 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.4 to 2.1.6 is 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.

SignalRaises priorityLowers priority
Dependency typeProduction, shipped to users or serversDev only, build tool, test runner
Call pathYour code calls the vulnerable function with untrusted inputVulnerable function never called, or only with trusted input
Exploitation dataKEV listed, high EPSSNo known exploitation, low EPSS
ExposureInternet facing service, parses user inputOffline tool, internal batch job
Fix costPatch level bump or scoped overrideMajor 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

ApproachFixes transitive findingsCan cross a major versionWorks when no upstream fix existsMain riskBest for
npm updateOnly within rangesNoNoLowRoutine freshness
npm audit fixOnly within rangesNoNoLowFirst pass on every report
npm audit fix --forceSometimesYes, for top level packagesNoBreaking changes, possible downgradesRarely; after a dry run you have read
Manual upgrade of a direct dependencyYes, if the new version uses a fixed chainYes, on purposeNoMigration workFixes that need a major bump
overrides / resolutions / pnpm overridesYesPossible, but riskyOnly if pointed at a patched forkParent never tested against the forced versionPatched transitive version exists but is blocked
Patch file (pnpm patch, patch: protocol, postinstall patching)YesNot applicableYesPatch drift, silent failure on older toolsSmall, well understood fixes
Private fork plus overrideYesNot applicableYesOngoing ownershipLarger fixes or many consumers
Hardened patched buildYesNot applicableYesDepends on the provider's evidenceTeams that do not want to own forks
Accept with a written verdictNot a fixNot applicableYesThe verdict goes staleUnreachable 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 situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull 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

Last reviewed: 22 October 2026. Package manager behaviour changes between releases; check the linked documentation for the version you run.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.