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:
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.
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
// 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
# 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 --forcefor 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.
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.
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.
# 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 — the overview for auditing and update automation.