Skip to content

Fix npm Vulnerabilities

Fix npm vulnerabilities through deliberate, minimal changes to package.json.

If specific packages are specified, focus on those. Otherwise, fix all vulnerabilities reported by npm audit.

Principles

  1. Control — Know what's changing and why. Avoid tools like npm audit fix that obscure which changes are made and why.
  2. Minimal intervention — The cleanest fix is the smallest one.
  3. Fix at the source — Vulnerabilities originate from direct dependencies. Focus fix attempts there first. Use overrides to pin indirect dependency versions only when fixes cannot be applied via changes to direct dependencies.
  4. Respect the systempackage-lock.json is output, not input. Edit package.json; let npm derive the lockfile.
  5. Preserve stability — The lockfile makes builds reproducible. Never delete it.

Process

1. Identify all vulnerabilities

Run npm audit. For each vulnerability, note:

  • The vulnerable package and version
  • The fixed version
  • The full dependency path back to the direct dependency

2. Ask: do we need these dependencies?

Before fixing, question whether each vulnerable dependency is necessary.

For each direct dependency in the vulnerability paths, ask the user:

  • Is this dependency essential to the project?
  • Could we remove or replace it?

Some dependencies (like Express) are core and non-negotiable. Others may offer little value and a security issue tips the scales toward removal.

Ask this once, covering all affected dependencies, before proceeding with fixes.

3. Analyze the fix options

For each vulnerability, determine the available fixes before making changes. This is analysis, not trial and error — vulnerability data is deterministic.

Trace the dependency chain:

  • Start from the vulnerable package
  • Trace upward through its parent packages
  • Continue until you reach your direct dependency
  • Note each package and version along the way

Check for a direct dependency fix:

  • Look up available versions of your direct dependency
  • Check which versions pull in a fixed version of the vulnerable package
  • If a fixed version exists, this is your preferred fix

If no direct fix exists, analyze override options:

  • For each package in the chain between your direct dependency and the vulnerable one, check if a version exists that resolves the vulnerability
  • Identify the highest-level package that has a fix available
  • This is your override target

4. Apply and verify each fix

For each vulnerability, apply the fix and verify before moving to the next.

If a direct dependency update resolves it:

  • Update the version in package.json
  • Run npm install
  • Run npm audit to confirm this vulnerability is resolved

If an override is required:

  • Add an override for the highest-level package that resolves the issue:
json
{
  "overrides": {
    "package-to-override": ">=fixed-version"
  }
}
  • Run npm update <package-to-override> to apply
  • Run npm audit to confirm this vulnerability is resolved

Overriding higher in the tree is better because:

  • Fewer side effects on unrelated parts of the tree
  • More likely to get other fixes and improvements along the way
  • Easier to remove later when the direct dependency catches up

5. If no fix is available

If a vulnerability cannot be fixed through version updates or overrides:

  • Document the vulnerability and why it can't be fixed
  • Assess the actual risk in your specific usage
  • Consider alternative packages that provide similar functionality
  • Monitor for upstream fixes

6. Multi-branch repos

Not every branch needs the fix. Check which branches use affected dependency versions before applying. Cherry-pick the fix commit to relevant branches.

Do not

  • Use npm audit fix — loses control, can add unwanted direct dependencies
  • Edit package-lock.json — it's generated; edit package.json instead
  • Delete package-lock.json — throws away version stability