GHSA-p63j-vcc4-9vmv (**critical**) — `@vitest/browser` Browser Mode provider
commands accept a file path from the browser and act on it without checking the
`allowWrite` permission gate. Patched in 4.1.10; `clients/web` was pinned at
4.1.0.
Dev-scope only — `@vitest/*` is absent from the published `files` allowlist, and
Browser Mode here runs this project's own Storybook play functions, never a
third-party page. That is why #1837 deferred it rather than attempting a
toolchain upgrade immediately before an irreversible publish.
## The knot, and what actually unties it
`^4.1.0` already admits 4.1.10, so the declared ranges were never the problem.
The family is welded by *exact* peer pins — `vitest` pins
`@vitest/browser-playwright`, which pins `@vitest/browser` — so nothing moves
alone.
The issue predicted that an explicit coordinated `npm i -D` would fix it. It
does not. With `clients/web/package-lock.json` in place, npm ERESOLVEs even when
every member of the family plus the Storybook packages are named in one command
and `node_modules` is deleted first:
Found: @storybook/addon-a11y@10.2.19
Could not resolve dependency: dev @storybook/addon-a11y@"^10.5.5"
Conflicting peer dependency: storybook@10.5.5
npm resolves against the existing lock tree and will not move the whole
constellation at once. **Regenerating `clients/web/package-lock.json` is the
only thing that unties it** — a clean resolve places all four at 4.1.10 with no
peer complaints at all.
## Versions
vitest 4.1.0 -> 4.1.10
@vitest/browser 4.1.0 -> 4.1.10 (transitive)
@vitest/browser-playwright 4.1.0 -> 4.1.10
@vitest/coverage-v8 4.1.0 -> 4.1.10
storybook + @storybook/* 10.2.19 -> 10.5.5
eslint-plugin-storybook 10.2.19 -> 10.5.5
@chromatic-com/storybook 5.0.1 -> 5.2.1
`clients/{cli,tui,launcher}` were already resolving 4.1.9 (no browser pins, so
they floated freely) and were never vulnerable. Their declared `^4.1.0` still
*permitted* 4.1.0 on a fresh resolve, so the floor is raised to `^4.1.10` in all
four manifests — same reasoning as the vite floor in #1841. Pinning only the
lockfile would have left a regression path open.
## The vite 8 peer override
Resolved, as a consequence rather than a workaround.
`@joshwooding/vite-plugin-react-docgen-typescript@0.6.4` declared
`peer vite@"^3 || ^4 || ^5 || ^6 || ^7"` against our vite 8. It is not a direct
dependency — it arrives through `@storybook/react-vite`, which at 10.5.5 depends
on `^0.7.0`, and 0.7.0 adds `^8.0.0` to that range. So taking Storybook 10.5.5
for the peer set drops the override too; nothing is forced.
## Two holds the regeneration forced, each with a tracked issue
Regenerating the lock floats every other in-range dependency to current. Most of
that is welcome — it is what takes `clients/web` from 19 advisories to 1 — but
two floats broke the gate and are unrelated to this security fix:
- **zod 4.3.6 -> 4.4.3** makes `clients/web`'s `tsc -b` die with
`FATAL ERROR: Ineffective mark-compacts near heap limit` at the ~4GB default.
Reverting zod alone makes it exit 0, so the attribution is unambiguous.
Held at `~4.3.6`. See #1896.
- **eslint-plugin-react-hooks 7.0.1 -> 7.1.1** enables `set-state-in-effect`,
which fails `lint` on 8 pre-existing violations across 7 components. Fixing
them is a real refactor with interaction-behavior consequences, not a
suppression. Held at `~7.0.1`. See #1897.
Both `~` pins are deliberate and temporary, and both issues say so — they are
constraints to remove, not preferences.
Storybook 10.5.5 also reports that `setProjectAnnotations` is applied
automatically since 10.3, making `.storybook/vitest.setup.ts` redundant. Left in
place: `./preview` carries the Mantine decorator and the a11y annotations drive
the play-function assertions, and if automatic provisioning missed either, the
462 stories would render unthemed with a11y checks inert and very likely still
pass. That silent-failure risk does not belong in a security bump. See #1898.
## Audit delta
`clients/web`, where the advisory lived:
before 19 (4 critical, 6 high, 6 moderate, 3 low)
after 1 (1 low — esbuild-in-tsup, Windows dev server only)
All four critical entries — `vitest`, `@vitest/browser`,
`@vitest/browser-playwright`, `@vitest/coverage-v8` — are gone. The other 14 are
transitive dev-tree advisories (hono, ws, js-yaml, flatted, brace-expansion, ...)
cleared as a side effect of the clean resolve. Elsewhere: cli 6 -> 5,
launcher 3 -> 2, tui 4 -> 4, root unchanged at 9 (its lock is untouched). Those
remainders are pre-existing and unrelated.
## Verification
`npm run ci` green end to end on the upgraded stack — the point of caring here,
since this *is* the test infrastructure:
- coverage gate (>=90 per file, all four dimensions) passes on all four clients;
no file regressed, so no gate was lowered and no `v8 ignore` added
- 4802 web tests, 304 cli, 282 tui, 5 launcher
- 462 Storybook play functions across 109 files, on vitest 4.1.10 Browser Mode
- verify:build-gate, and all five smokes including `smoke:tui` (real TTY here)
Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>