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:
uuid< 11.1.1 — GHSA-w5hq-g745-h8pq, a missing buffer bounds check inv3/v5/v6when abufargument 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.image-size≤ 2.0.2 — HIGH, DoS via infinite loops in the ICNS/JXL/HEIF parsers.elliptic≤ 6.6.1 — LOW, risky cryptographic primitive; surfaces as thebrowserify-sign/create-ecdh/crypto-browserify/node-polyfill-webpack-pluginchain.
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¶
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-plugin → crypto-browserify → elliptic. 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_modulesdeleted: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 missingaddon-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
uuidoverride (a transitive's runtime use is invisible totsc): all six consumers load, andexceljswas driven through a real workbook write with a cell comment — the path that actually callsuuid.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.
Related commits (append-only)¶
2a913a92— T-202: single uuid override clears every remaining production vulnerability19105aa3— 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=dev → found 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-service → found 0 vulnerabilities