Back to core workflows Fix dependency resolution Tune package metadata Jump to monorepo patterns

Pinning Vulnerable Transitive Versions with overrides

An advisory names a package deep in your dependency tree that you never installed directly, and there is no direct upgrade to apply. This page shows how to pin the patched transitive version with overrides (or resolutions) safely.

Exact symptoms and error messages

npm audit flags a package you do not depend on directly:

Exact symptoms and error messages npm audit flags a package you do not depend on directly: Exact symptoms and error messages npm audit flags a package you do not depend on directly:
Exact symptoms and error messages — the core idea of this section at a glance.
high   Regular Expression Denial of Service in nth-check
  node_modules/svgo/node_modules/nth-check
  fix available via `npm audit fix --force`
  Will install svgo@1.3.2, which is a breaking change

Root cause analysis

The vulnerable package is pulled in transitively, so there is no direct dependency to bump. The patched version exists, but the direct parent has not released an update that references it. An override rewrites the resolved version of that specific transitive package across the graph, which is exactly what the resolution machinery in Dependency Resolution Explained supports.

Root cause analysis The vulnerable package is pulled in transitively, so there is no direct dependency to bump. Root cause analysis The vulnerable package is pulled in transitively, so there is no direct dependency to bump.
Root cause analysis — the core idea of this section at a glance.

An override is a blunt instrument, which is both its strength and its risk. It rewrites the resolved version of a package everywhere it appears in the graph, regardless of what the intermediate parents asked for. That is exactly what you want to clear a transitive advisory quickly, but it also means you are overriding the compatibility assumptions of every parent that depended on the old range — so the pinned version must genuinely satisfy all of them.

The scoping syntax lets you narrow that blast radius. A bare "nth-check": "^2.1.1" forces the version everywhere; a scoped "svgo>nth-check": "^2.1.1" forces it only under the svgo subtree. Prefer the narrow form when only one path is affected, because it leaves other consumers of the package on their originally-resolved versions and reduces the chance of a compatibility surprise elsewhere in the tree.

Resolution and configuration patch

Resolution and configuration patch Scope the override narrowly, and remove it once the parent dependency ships a release that references the patched versio Resolution and configuration patch Scope the override narrowly, and remove it once the parent dependency ships a release that references the patched version, so you do not carry a permanent pin.
Resolution and configuration patch — the core idea of this section at a glance.
// npm / pnpm
{
  "overrides": {
    "nth-check": "^2.1.1"
  }
}
// Yarn v1
{
  "resolutions": {
    "nth-check": "^2.1.1"
  }
}

Scope the override narrowly, and remove it once the parent dependency ships a release that references the patched version, so you do not carry a permanent pin.

The package managers differ in syntax, so match your tool. npm and pnpm use overrides (pnpm nests it under pnpm), while Yarn Berry uses resolutions with glob support:

// Yarn Berry — glob-scoped resolution
{
  "resolutions": {
    "svgo/**/nth-check": "^2.1.1"
  }
}

Whichever tool you use, re-run a full install so the lockfile is regenerated against the pin, then re-audit to confirm every affected path now resolves to the patched version rather than just the one you noticed.

CLI validation and debug commands

CLI validation and debug commands CLI validation and debug commands in production JavaScript package workflows. CLI validation and debug commands CLI validation and debug commands in production JavaScript package workflows.
CLI validation and debug commands — the core idea of this section at a glance.
# Confirm every path now resolves to the patched version
npm ls nth-check
# Re-audit to confirm the advisory clears
npm audit --audit-level=high
# In pnpm, verify the override took effect
pnpm why nth-check

Prevention and CI guardrails

  • Prefer a narrow override over audit fix --force for transitive advisories.
  • Document why each override exists so it is removed when no longer needed.
  • Re-audit after applying so a mis-scoped override does not leave a path unpatched.
  • Let the update bot propose removing stale overrides as parents catch up.
Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows. Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows.
Prevention and CI guardrails — the core idea of this section at a glance.

Treating overrides as temporary debt

Every override is a small piece of technical debt: it pins a version that upstream has not yet caught up to, and it will silently hold that package back even after the ecosystem moves on. Left unmanaged, overrides accumulate and quietly prevent legitimate updates, so treat each one as temporary and track why it exists.

Override lifecycle Pin now, track why, remove when upstream catches up. pin patched version clear advisory document the reason tracked debt remove when upstream ready converge to ranges
An override is a temporary bridge that should be removed once upstream converges.

A one-line comment or a tracking issue per override — 'pins nth-check until svgo releases a version referencing ^2.1.1' — makes removal a deliberate follow-up rather than a forgotten pin. Let your update bot flag when the parent dependency finally references the patched version, and remove the override then. The goal is a graph that converges back to upstream ranges, with overrides as short-lived bridges, not permanent fixtures.

Verifying an override patched every path

The trap with a transitive pin is assuming it worked because the advisory you noticed cleared. A package can appear at several points in the graph, and a mis-scoped override might fix one path while leaving another on the vulnerable version. The only proof is to list every resolved copy after applying the pin.

Verify the pin List every path, confirm the patched version, re-audit. npm ls --all every path all on patched no stragglers re-audit advisory clears
Prove every path resolves to the patched version — a narrow override can miss one.
# Every path must show the patched version
npm ls nth-check --all
pnpm why nth-check
# Re-audit to confirm nothing is left behind
npm audit --audit-level=high

If ls shows even one path still on the old version, the override is too narrow — widen its scope or switch from a scoped form to a global one. Making this check part of the PR that adds the override means a partial fix is caught in review, not in the next audit run weeks later when someone assumes the issue was already handled.

Frequently Asked Questions

Do overrides affect my published package's consumers?

No — overrides/resolutions apply to your own install tree, not to consumers of your package. To force a fix on consumers you must bump your own declared dependency range.

Are permanent overrides a problem?

They can mask an unmaintained parent and drift from upstream. Treat each override as temporary, document it, and remove it once the direct dependency references the patched version.

Does an override change what my package requires of its consumers?

No. overrides/resolutions apply only to your own install tree. Consumers of your published package resolve their own graph and are unaffected — to influence them you must change your declared dependency ranges.

How narrow should an override be?

As narrow as clears the issue. Use a scoped form (parent>dep) when only one path is affected so other consumers keep their resolved versions, and reserve the global form for when every path must move to the pinned version.

When should I remove an override?

Once the direct parent ships a release that references the patched version. Track each override so removal is a deliberate step; otherwise it lingers and blocks legitimate updates to that package.

Related

Dependency Auditing and Automated Updates