← All posts

When is it safe to delete an npm override?

August 19, 2026 · 8 min read

You have an override in your package.json that someone added during an incident. Deleting it is either a one-line cleanup or a silent reintroduction of a vulnerability, and the file gives you no way to tell which.

The decision reduces to four questions. Work through them in order; the first no ends the investigation.

Worked example throughout: an override pinning tough-cookie to 4.1.3, added for GHSA-72xf-g2v4-qvf3 (prototype pollution). The project depends on request@^2.88.2, which is where tough-cookie comes from.

1. What was this override actually for?

Not every override is a security fix. Some pin a package to dodge a bug, force a duplicate dependency down to one copy, or work around a broken release. Those have different removal criteria entirely, and you need to know which kind you are holding.

If it is a security pin, find the advisory. The pinned version is your best clue — it is almost always the first patched version:

# What advisories exist for the version below your pin?
npm audit --json | jq '.vulnerabilities["tough-cookie"]'

# Or query OSV directly for the version you suspect was vulnerable
curl -s https://api.osv.dev/v1/query \
  -H 'Content-Type: application/json' \
  -d '{"package":{"name":"tough-cookie","ecosystem":"npm"},"version":"4.1.2"}'

You are looking for the advisory's patched range. For our example that is >=4.1.3, which confirms the pin was a security fix and tells you the bar any replacement has to clear.

If you cannot find an advisory

Check the commit that introduced the override and its pull request. If there is no advisory and no explanation, treat it as unknown rather than safe — an override with no recoverable reason is the single most common way a fix gets reverted by accident.

2. Who pulls this package in, and where are they now?

An override is only removable if every path to the vulnerable package now resolves to something safe without it. Start by finding the parents:

npm ls tough-cookie

[email protected]
└─┬ [email protected]
  └── [email protected]  overridden

One parent here, which is the easy case. Multiple parents is common and matters: the override can only go when all of them have caught up. One straggler keeps the pin alive.

Now check what the newest parent actually depends on:

npm view request@latest dependencies.tough-cookie
# ^4.1.4

[email protected] asks for ^4.1.4. That satisfies the advisory's >=4.1.3, so the parent has genuinely caught up — with the override gone, npm would resolve tough-cookie to 4.1.4 or later on its own.

3. Can you actually take that parent version?

This is the question that most often turns a “yes” into a “not yet.” The fix exists, but it may sit behind an upgrade you are not ready to make.

Compare the parent version you need against the range you currently allow:

  • Already in range. You depend on request@^2.88.2 and need 2.89.0. Same major, satisfied by your existing range — a lockfile update, nothing more. Delete the override.
  • In-major bump needed. You are on ^2.87.0 and need 2.89.0. Widen the range and re-lock. Low risk, but it is a real change that should go through review and CI.
  • Major upgrade needed. The safe transitive only arrives with the parent's next major. This is no longer override cleanup — it is a migration, with breaking changes to read and code to touch. The override stays until you do that work deliberately.
Treat “removable, but only behind a major upgrade” as its own answer, not as a yes. Collapsing it into “safe to remove” is how a routine cleanup PR turns into a broken build.

4. Does the lockfile agree once the override is gone?

The first three questions are about declared ranges. The lockfile is what actually gets installed, and it is the only thing that settles the argument. Verify rather than reason:

# Remove the override entry, then re-resolve from scratch
rm -rf node_modules package-lock.json
npm install

# What did we actually get?
npm ls tough-cookie
# └─┬ [email protected]
#   └── [email protected]        ← at or above the patched version

npm audit
npm test

A clean re-resolve matters. Updating in place can leave the old resolution pinned in the lockfile, which makes a still-needed override look removable — the most dangerous possible false positive.

If npm ls shows a version at or above the advisory's patched range and npm audit is clean, the override has done its job and can go.

The short version

QuestionAnswer that lets you delete
What was it for?A known advisory, with a patched range you can check against
Who pulls it in?Every parent now resolves to a safe version unaided
Can you take that parent?It is within your range, or an in-major bump you are willing to make
Does the lockfile agree?A clean re-resolve installs the safe version and audit is quiet

Doing this without the ten minutes per entry

The four questions are mechanical, which means they can be answered for you. Backpatch walks the same path — advisory lookup, parent resolution, in-major versus major classification — for every entry in a package.json at once:

curl -X POST https://api.backpatch.dev/analyze_overrides \
  -H "X-API-Key: $BACKPATCH_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @package.json
[
  {
    "packageName": "tough-cookie",
    "overriddenVersion": "4.1.3",
    "status": "SafeToRemove",
    "suggestedParentUpgrade": "upgrade request to 2.89.0",
    "advisoryId": "GHSA-72xf-g2v4-qvf3",
    "reason": "[email protected] already ships [email protected]"
  }
]

Note that NeedsMajorUpgrade is a distinct status from SafeToRemove, for the reason above — and pass your lockfile alongside the package.json when you have one, since that is what turns question four from an inference into a fact. Question four still belongs to you either way: re-resolve and run your tests before you merge the deletion.

Backpatch answers this question automatically.

It checks whether the parent dependency already pulls in a safe version, for every override in your package.json, and tells you which ones you can delete. Works as a REST API or an MCP server.