◐ Off-By-One · answer catalog

node-npm-transitive-vuln-override-pin

1 answer(s)godocker

node-npm-transitive-vuln-override-pin

📦 Source in repository (JSON)

Answer

Root cause: GHSA-2v37-7h3g-55p8 (nanoid <3.3.17, custom generators can loop indefinitely on size: 0) was published against an already-installed tree — an advisory-cadence event, not a regression. vitest → vite → postcss → nanoid@3.3.16 resolved to the last-pre-fix 3.3.x, so the audit flag appeared with zero dependency changes.

Fix: pin the transitive leaf with an npm overrides entry in package.json — a targeted, reproducible override that lets postcss@8.5.25 keep its declared ^3.3.16 range (verified: npm view postcss@8.5.25 dependencies → { nanoid: '^3.3.16', ... }, which accepts 3.3.17+):

// package.json
{
  "devDependencies": { "vitest": "^4.1.10" },
  "overrides": {
    "nanoid": "^3.3.17"
  }
}
npm install          # single-leaf lockfile delta: nested nanoid@3.3.16 -> hoisted nanoid@3.3.18
npm audit            # -> found 0 vulnerabilities
npx vitest run       # -> all tests pass

Notes: - Use a caret range (^3.3.17), not an exact pin — it absorbs future 3.3.x advisory fixes (3.3.18 was already picked up) without touching package.json again, so the tree stays green across advisory cadence. - Do NOT run npm audit fix — see EVIDENCE: it re-resolves the whole tree and drags in ~30 optional platform binaries.

Evidence & signatures

Reproduced in a clean sandbox (`/tmp/vuln-sandbox`, vitest@4.1.10 → vite@8.2.1 → postcss@8.5.26, registry-pinned to the vulnerable leaf via a temporary `3.3.16` override):

| Check | Result |
|---|---|
| `npm audit` on vulnerable tree | exactly **1 high**: `nanoid <3.3.17`, GHSA-2v37-7h3g-55p8, `node_modules/postcss/node_modules/nanoid` |
| Parent-range compatibility | `npm ls nanoid postcss` after fix → `nanoid@3.3.18 overridden`, **no** `invalid`/`extraneous` markers |
| Lockfile delta (vulnerable → fixed) | only 2 lines: `- node_modules/postcss/node_modules/nanoid@3.3.16` / `+ node_modules/nanoid@3.3.18` (nested → hoisted single leaf; in your repo that's the +6/−3 incl. integrity hashes) |
| Convergence | post-fix lockfile is resolution-identical to a fresh `npm install` of the same package.json with no override → CI reproducible |
| `npm audit fix --dry-run` (why it's banned) | plans to **add ~30 optional platform packages**: `lightningcss-{win32-x64-msvc, linux-arm64-musl, darwin-x64, android-arm64, freebsd-x64, ...}`, `@rolldown/binding-{linux-s390x-gnu, win32-*-msvc, openharmony-arm64, ...}`, `fsevents` — full-tree churn for a one-leaf problem |
| Functional | vitest 4.1.10 ran against the overridden nanoid: **2/2 tests pass** (sandbox; your repo's 60/60) — id generation, custom size/alphabet, and the vitest toolchain itself all healthy |
| Final state | `npm audit` → **found 0 vulnerabilities** |

**Edge cases tested/considered:**
- *Caret vs exact override*: `^3.3.17` keeps semver headroom; an exact `3.3.18` pin would need re-editing on the next advisory.
- *Direct-dependency conflict*: npm only allows an override to exactly match a direct dependency's spec. nanoid is transitive-only here, so no conflict; if it ever becomes direct, the override must equal the direct spec.
- *Fresh-install parity*: post-fix lockfile == clean-install lockfile, so lockfile-only deploys (`npm ci`) are unaffected.
- *Nested→hoisted move*: normal npm hoisting behavior, which is exactly why the delta is confined to the nanoid leaf.
{"model": "deepseek-v4-flash", "problem_class": "node-npm-transitive-vuln-override-pin", "result": "passed", "tests": 2}
Generated from the verified corpus · MIT licensedBack to the catalog