Skip to content

npm Override Floor Must Be the First Safe Version

Applies to: Anyone adding or bumping a dependency override in ionic_frontend/package.json, admin_dashboard/package.json, agent_runner/package.json, or docs_site/package.json.

The trap

npm's overrides field lets you pin a transitive dependency to a safe version after a vulnerability advisory is published. The mistake is setting the floor inside the still-vulnerable range instead of at the first safe version:

json
// WRONG — ^7.5.5 resolves to 7.5.8, which is still inside <=7.6.2 (vulnerable)
"overrides": {
  "protobufjs": "^7.5.5"
}

The caret ^ keeps the major and minor locked but allows any patch. When npm install resolves ^7.5.5 it locks to the latest 7.5.x available — in this case 7.5.8, which sits inside the vulnerable range <=7.6.2. npm audit won't flag it because the resolved version matches the declared range; the advisory is not re-evaluated against the locked version until the database refreshes.

The same trap applies to >= floors:

json
// WRONG — 8.20.1 is still inside the ws vulnerable range <8.21.0
"overrides": {
  "ws": ">=8.20.1"
}

The rule

Set the floor to the first safe version — the exact version that closes the advisory.

json
// CORRECT — floor is just above the vulnerable ceiling
"overrides": {
  "protobufjs": ">=7.6.3",
  "ws": ">=8.21.0"
}

Use >= (not ^) for security overrides so the range stays open to future patch releases without needing a manual bump.

Canonical example (OBJ-977 / OBJ-971)

ionic_frontend/package.json shipped with "protobufjs": "^7.5.5" — an override added to patch an older advisory. The advisory's vulnerable range was <=7.6.2. The caret floor at 7.5.5 allowed npm to lock to 7.5.8, still inside the vulnerable range. npm audit did not surface the problem until the advisory database refreshed. Fix: "protobufjs": ">=7.6.3".

The same pattern appeared with ws in admin_dashboard ("ws": ">=8.20.1" when the safe version is >=8.21.0). Both were corrected in OBJ-971.

Third occurrence — a correct floor can go stale (OBJ-2143)

The first two occurrences were floors that were wrong the moment they were set. This one is sharper: agent_runner/package.json's "fast-uri": ">=3.1.3 <4" override was the correct first-safe-version floor when it was set by PR #1694, closing the advisory that existed at the time. It went stale later, when a new, later advisory (GHSA-7p8r-x3mc-p8w7) extended the vulnerable ceiling forward to include 3.1.4 — a version the existing floor still happily resolved to. npm audit doesn't re-warn you about an override that used to be correct; it only flags what's currently unresolved. A floor is not "fixed once" — treat every npm audit red as a chance the floor itself has gone stale, not just that a new package needs one.

agent_runner failed npm audit --audit-level=high --omit=dev on 3 findings simultaneously, all from stale or badly-shaped overrides:

PackageStale overrideResolved toAdvisory rangeFix
fast-uri>=3.1.3 <43.1.43.0.0–3.1.4 (GHSA-7p8r-x3mc-p8w7)>=3.1.5 <4
ip-address10.1.1 (exact pin)10.1.1≤10.3.0 (GHSA-mwp4-54f8-5fhr, GHSA-4xrf-jv44-h6hh, GHSA-22jq-vg5j-6vgg)>=10.3.1

The ip-address case is a second, distinct anti-pattern layered on top: an exact-version pin ("ip-address": "10.1.1", no >=) is strictly worse than a stale >= floor. A >= floor at least resolves forward as newer compatible patches are published; an exact pin can never absorb a patch release at all; it stays wrong until a human edits it again. Never use an exact pin for a security override — always >=<first-safe-version>, even if the first-safe-version today happens to equal what you'd have pinned.

express-rate-limit (^8.2.1, locked to 8.3.2) pulled the vulnerable ip-address range through its own dependency chain; bumping it to ^8.6.1 was required alongside the override fix so its transitive resolution also lands outside the advisory. ionic_frontend/package.json:167 carried the identical stale fast-uri floor, but only in a dev-only dependency path (ESLint's ajv), so it never failed --omit=dev — fixed in the same commit before it could bite in a future production tree, not because it was currently gating anything. Fixed under OBJ-2143 (agent_runner/package.json, ionic_frontend/package.json:167, both regenerated lockfiles).

How to find the first safe version

  1. Open the advisory linked in npm audit output.
  2. Look for the "Patched in" or "Fixed in" field — that is the floor.
  3. Verify: after changing the override, run npm install && npm audit --omit=dev. Zero advisories at high or critical means the floor is correct.
  • ionic_frontend/package.jsonoverrides block
  • admin_dashboard/package.jsonoverrides block
  • agent_runner/package.jsonoverrides block
  • OBJ-971 (security audit fix, first occurrence of this pattern)
  • OBJ-977 (follow-up HIGH-severity trace, root cause identified)
  • OBJ-2143 (third occurrence: fast-uri floor went stale after a later advisory; ip-address exact-pin anti-pattern)

Last updated: 2026-08-03 (OBJ-2143: third occurrence — a correct floor can go stale, plus the exact-pin anti-pattern)

Loading…