Skip to content

T-202 · Dependabot triage part 2 — clear the moderate/low residue

Why this task exists

T-201 cleared every critical and high in the production dependency tree and deliberately deferred the rest — the moderates and lows — as "each needs a major-version project". The owner then asked for exactly that follow-through (2026-08-10, verbatim): "I'll merge the PR for you, but proceed to the fixing of the rest".

This task is the "rest". It is a separate task rather than an extension of T-201 because T-201's own scope statement excludes this work by name, and because it closes at merge.

What was actually wrong (the useful finding)

The 20 remaining alerts looked like twelve different problems. They were three:

  1. uuid < 11.1.1 — GHSA-w5hq-g745-h8pq, a missing buffer bounds check in v3/v5/v6 when a buf argument is passed. This one advisory produced 11 of the 13 moderates. It entered the tree through six independent consumers (gaxios, google-gax, googleapis-common, teeny-request, exceljs, @storybook/addon-actions), and each of those made its own parents show up as vulnerable too — @google-cloud/firestore, @google-cloud/storage, firebase-admin, googleapis.
  2. image-size ≤ 2.0.2 — HIGH, DoS via infinite loops in the ICNS/JXL/HEIF parsers.
  3. elliptic ≤ 6.6.1 — LOW, risky cryptographic primitive; surfaces as the browserify-sign / create-ecdh / crypto-browserify / node-polyfill-webpack-plugin chain.

Why npm audit fix --force would have done real damage here. For the uuid-derived alerts, npm's own suggested remedies were downgrades: @google-cloud/storage@5.18.3 (from 7.x) and exceljs@3.4.0 (from 4.4.0). Applying those would have rolled back the storage and spreadsheet libraries by whole majors to "fix" a bug in a shared transitive.

What was done

One override retires 11 moderates

overrides.uuid = ^11.1.1

The ceiling is deliberately ^11, not ^12: uuid v12 removes CommonJS support, and every consumer in this tree reaches it through require('uuid').

Checked before applying, because a transitive dependency's runtime use is invisible to the type-checker: - every consumer imports the bare specifier — none uses the uuid/v4 deep path that was removed back in v7; - the only functions any of them call are v1 (exceljs) and v4 (the Google libraries), both still exported by 11.1.1; - the tree deduped from three copies (a shared 9.0.1, exceljs's private 8.3.2, and next-auth's already-patched 11.1.1) down to a single 11.1.1.

Then verified by execution, not inspection: every affected consumer was loaded, and exceljs was driven through a real workbook write with a cell comment — the code path that actually calls uuid.v1 — producing a valid 8078-byte xlsx. google-auth-library, googleapis, firebase-admin, @google-cloud/storage and gaxios all load clean.

Storybook 8.4.7 → 10.5.7 (owner's explicit choice)

@storybook/addon-essentials, @storybook/addon-interactions and @storybook/test no longer exist as published packages — Storybook 9 folded them into core. So the migration is: drop those three devDependencies, move the remaining four to ^10.5.7, and shorten .storybook/main.ts's addons array to just @storybook/addon-onboarding. Nothing in the repo imported the removed packages (only the config named them), so no story code changed.

The lockfile had to have its 32 @storybook/* entries purged before npm would resolve v10 — with the Storybook 8 subtree pinned, npm dead-ended on a peer conflict between @storybook/addon-onboarding@10.5.7 (which needs storybook@^10) and the pinned storybook@8.6.18.

One thing the version bump alone did not carry: @storybook/addon-docs. Storybook 9 did not fold docs into core the way it folded controls/actions/viewport/backgrounds/toolbars/ measure/outline — docs became a separate opt-in package. Without it listed, stories/Introduction.mdx gets no loader and the build dies with Module parse failed: Unexpected character '#'. This was caught only by actually building Storybook; the type-check passes cleanly without it, because the missing piece is a webpack loader, not a type. @storybook/addon-docs@^10.5.7 is therefore a new devDependency and is listed in .storybook/main.ts.

Storybook 10 requires Node ≥ 20.19 or ≥ 22.12 and refuses to start otherwise. See the Node section below — this has a consequence for the owner's machine.

Outcome

Scope Before T-202 After T-202
production tree (--omit=dev) 11 moderate 0 — clean at every severity
root lockfile, all severities 2 high · 13 moderate · 5 low 2 high · 0 moderate · 5 low
services/bank-login-service 0 0 — clean at every severity
services/workspace-billing-service 7 moderate 0 — clean at every severity

services/workspace-billing-service turned out to have the same single root cause — all 7 of its moderates were uuid again, via gaxios / google-gax / teeny-request, with @google-cloud/firestore and @google-cloud/storage inheriting. The same override fixes it, with one wrinkle: that service declares uuid as a direct dependency, and npm rejects an override that conflicts with a direct range (Override for uuid@^11.0.4 conflicts with direct dependency). So its direct range moves ^11.0.4^11.1.1 to agree with the override. Its own usage is import { v4 as uuidv4 } from 'uuid', which is unaffected, and npm run build (tsc) passes.

Every manifest in the repo is now free of critical, high AND moderate vulnerabilities except the root's 7 dev-only Storybook alerts, which no released version can fix.

The 7 that remain, and why no upgrade removes them

All 7 are dev-only and reach the tree solely through @storybook/nextjs: the image-size high, the elliptic low chain, and the two parent entries that inherit them.

They are not fixable by upgrading. image-size has no patched release at any version (≤2.0.2 is its entire release history), and Storybook still depends on image-size ^2.0.2 at 10.5.7 — the newest version published. The same holds for node-polyfill-webpack-plugincrypto-browserifyelliptic. Upgrading Storybook 8 → 10 therefore cleared zero alerts, which was stated to the owner before the work was done and is recorded here so a future reader doesn't retry it expecting a different result. The only thing that would clear these is removing Storybook as a dependency.

Node version — a real consequence of the Storybook upgrade

Storybook 10 hard-gates on Node ≥ 20.19 or ≥ 22.12. The owner's machine was on v22.11.0, which fails that check, so npm run storybook / build-storybook would have been dead on arrival.

This also silently broke installs in a way worth recording, because it explains a mystery from T-201. oxc-parser (a Storybook 10 dependency) declares the same engine requirement, and npm silently skips an optional dependency whose engine requirement the running Node fails — so npm ci under 22.11 completed "successfully" while omitting @oxc-parser/binding-darwin-x64, and Storybook then died on a missing native binding. The same mechanism is what made vitest unrunnable in T-201 (rolldown's binding). It presents as npm/cli#4828 but the trigger here is the engine gate, not the npm bug.

Node 22.12.0 was installed via nvm to verify this work. ⚠️ This changed the machine's default Node, because the nvm default alias is node (= "newest installed"), so it now resolves to 22.12.0 instead of 22.11.0. This was a side effect, not an intent — flagged to the owner. To revert: nvm uninstall 22.12.0 (but note that Storybook will then no longer run).

Verification

  • Gate (AGENTS.md), fresh install with node_modules deleted: npm ci → exit 0; NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit --incremental false → exit 0, zero output.
  • npx storybook build → exit 0, storybook-static/ produced. This is what caught the missing addon-docs; the type-check alone would have shipped a broken Storybook. (storybook-static/ is now in .gitignore — it wasn't, and the build drops it in the repo root.)
  • Runtime check on the uuid override (a transitive's runtime use is invisible to tsc): all six consumers load, and exceljs was driven through a real workbook write with a cell comment — the path that actually calls uuid.v1 — producing a valid 8078-byte xlsx.

The test suite runs again — and 7 tests fail at main, unrelated to this work

With Node ≥ 22.12 the native-binding problem disappears and vitest runs for the first time in either task: 830 passed, 7 failed across 2 files. Those 7 were then run against unmodified main (d1b3e6f1) in a throwaway worktree with its own npm ci, and they fail there identically — same two files, same seven tests. So they are pre-existing, not a regression from any dependency change here. They split into two unrelated causes:

  • __tests__/lib/accounting/ppeMovement.test.ts (3) — genuine assertion mismatches in the PP&E depreciation schedule from T-142 (e.g. expected 6,750, got 7,500). The module under test (lib/accounting/fixedAssets/types.ts) has no imports at all, so no dependency can affect it. Worth someone's attention: this is real accounting arithmetic disagreeing with its own test.
  • __tests__/pages/api/workspace/billing/ingest.test.ts (4) — environmental, not a code fault: Unable to detect a Project Id in the current environment, i.e. no GCP credentials locally.

These were invisible until now because the suite could not start on this machine. Neither is in scope for this task; flagged so they are not mistaken for fallout from the upgrades.

Decision log

2026-08-10 — the moderate/low residue, and what to do about Storybook. Attestation: AGENTS.md read; board checked by scope — T-201 is the only neighbouring task and it explicitly excludes this work, so this is not a duplicate. Source: Dependabot Assistant · local session 1fafa0d3-166b-4efe-8df1-aff416391d5e Proposed: Dependabot Assistant. Approved: the owner. Owner's words (verbatim, 2026-08-10): "I'll merge the PR for you, but proceed to the fixing of the rest".

On Storybook the owner was given three options — remove it, keep it and accept the alerts, or keep it and upgrade 8 → 10 — with the trade-offs stated plainly, including that the upgrade would clear no alerts because image-size has no fixed version and Storybook 10 still depends on it. The owner chose "Keep it, but upgrade 8 → 10". Recorded because a later reader will otherwise see a major Storybook migration sitting in a security task having fixed nothing, and reasonably assume it was a mistake. It was a deliberate, informed choice to modernise the tooling; the security residue is unrelated and unfixable.

Rejected alternatives: removing Storybook (would clear all 7, but it is the owner's tool to keep or drop, not the agent's); npm audit fix --force (would have downgraded @google-cloud/storage and exceljs by whole majors).

Firestore: no structure changes — this task touches no Firestore documents at all; it reads and writes nothing.

  • 2a913a92 — T-202: single uuid override clears every remaining production vulnerability
  • 19105aa3 — T-202: Storybook 8 → 10, and the uuid override reaches the billing service too
  • (follow-up) — rebased onto current main (stacked on T-201); SHAs above are post-rebase

Post-rebase note — installing needs Node ≥ 22.12 too, not just running

2026-08-10. Rebasing this branch onto current main re-verified everything, and clarified the Node requirement recorded above. Storybook 10 doesn't only run on Node ≥ 20.19 / 22.12 — its dependencies only install correctly there. Installing under Node 22.11 makes npm skip @oxc-parser's platform binding (its engine requirement fails, and npm drops engine-gated optional dependencies silently), so npm ci reports success and storybook build then dies on a missing native binding. Same install, same lockfile, different Node → different tree.

So the rule for this repo is now simply: use Node ≥ 22.12 for npm ci as well as for the tools.

Re-verified on the rebased branch, installed under Node 22.12: - npm ci exit 0 under npm 10.9.0 and under npm 11.14.1 (see T-201's post-rebase note for why both had to be checked) - tsc --noEmit --incremental false exit 0, zero output - npx storybook build exit 0 - npm audit --omit=devfound 0 vulnerabilities; whole tree 0 critical / 2 high / 0 moderate / 5 low (the 2 high + 5 low being the unfixable dev-only Storybook chain) - services/workspace-billing-servicefound 0 vulnerabilities