The 61 vulnerabilities that became 2 — the art of the surgical override
Portfolio·

The 61 vulnerabilities that became 2 — the art of the surgical override

“61 vulnerabilities found”

Not the headline you want to see on a Saturday morning. I ran pnpm audit on Portfólio out of pure habit — that security check you do between features — and the terminal fired back:

61 vulnerabilities found
Severity: 25 moderate, 36 high
  1. Twenty-five moderate, thirty-six high. On a project that is literally my business card. The first instinct is always the same: “I need to update EVERYTHING right now.” Except updating everything is exactly what can break what already works — and Portfólio had just gone through a full Vue 3.5 rebuild.

The context: why the audit was screaming

Portfólio is a Vue 3.5 + Fastify app deployed on Vercel. pnpm audit doesn’t only flag what you use directly — it scans the whole tree of transitive dependencies. And that’s where the classic problem lives: packages you don’t even know exist, pinned to old versions because some direct dependency resolved them that way.

The usual suspects showed up in the audit:

Package Where it comes in Problem
ajv validators across several libs old versions with ReDoS / prototype pollution
smol-toml config tooling CVE in parsing
@tootallnate/once promisify helper high-severity CVE
esbuild bundlers in tooling known vuln in versions < 0.24

None of them is a direct dependency of my package.json. They’re all transitive — resolved to old versions because of loose ranges in intermediate libraries.

The struggle: why it wasn’t pnpm audit fix

The temptation is to run pnpm audit fix and let pnpm sort it out. I’d been down that road before, and it has a price: audit fix updates dependencies and regenerates the whole lockfile, sometimes with a different pnpm version than CI expects. I’ve seen deploys break with ERR_PNPM_EEXIST because of exactly that. Auto-fix can also pull major versions of libraries the project doesn’t want — silent behavior changes.

The right path was the opposite: surgical. pnpm has a mechanism built for this: pnpm.overrides. It forces the resolution of a transitive package to a specific version without touching any direct dependency — the rest of the tree stays intact.

// package.json — the diff that fixed it (+5/-1)
{
  "pnpm": {
  "overrides": {
  "@fastify/static": "^10.1.1",
  "ajv": "^8.18.0",
  "smol-toml": "^1.6.1",
  "@tootallnate/once": "^2.0.1",
  "esbuild": "^0.28.1"
  }
  }
}

Five lines. Four of them new — @fastify/static was already there, it just gained a comma. After that, pnpm install re-resolved the tree honoring the overrides, and the lockfile shrank by 739 lines (the whole commit: 48 insertions, 697 deletions). When the lockfile shrinks like that, it means duplicated versions and stale resolutions simply vanished.

The resolution: 61 → 2, and honesty about the rest

pnpm audit after install:

2 vulnerabilities found
Severity: 2 high

The remaining two are the same chain: netlify-cli@netlify/blobs@netlify/dev-utilsimage-size@2.0.2 (GHSA-5p2g-fcmc-qvqq). And here’s the detail that matters: the patched version is <0.0.0 — meaning no patch has been published yet. There is no override possible, no update that fixes it today.

That’s when risk judgment kicks in, not panic:

Factor Analysis
Where it comes in netlify-cli — a local development CLI
Reaches production? No — deploy is Vercel, remote build
Is there a patch? No (patched <0.0.0)
Attack surface A dev running netlify on their own machine

Overrides fix what can be fixed today; for what has no fix, the answer is monitoring — when the advisory publishes a patch, it’s one line in overrides and done. Meanwhile the real exposure is minimal: a local dev tool, never in runtime.

Metrics

Metric Before After
Vulnerabilities 61 (25 moderate, 36 high) 2 (0 moderate, 0 critical)
Override lines 0 5
Lockfile −739 lines
Direct dependencies changed 0

Takeaways

  • pnpm audit fix is not the first answer — it regenerates the lockfile and can pull unwanted majors. Overrides are the scalpel: touch only what needs fixing.
  • Not every vuln is urgent — the netlify-cli → image-size chain has no published patch and never reaches production. Risk needs context, not just a number.
  • A shrinking lockfile is a good sign — duplicated versions leaving the tree usually come hand-in-hand with the vuln drop.
~/lifelog — bash
$cat about.txt
╔══════════════════════════════════════╗
║  Samuel Medeiros                    ║
║  Senior Software Engineer           ║
║  Stack: Python · TypeScript · Rust  ║
║  Projetos: Arachne, Dogwalk,        ║
║            Capivara, TatuEngine      ║
╚══════════════════════════════════════╝
      
$