Skip to content

chore(deps): full dependency audit — 14 advisories to 0, adopt Vitest 5 stack - #25

Merged
chriskehayias merged 5 commits into
devfrom
chore/dependency-audit-2026-09
Sep 13, 2026
Merged

chore(deps): full dependency audit — 14 advisories to 0, adopt Vitest 5 stack#25
chriskehayias merged 5 commits into
devfrom
chore/dependency-audit-2026-09

Conversation

@chriskehayias

Copy link
Copy Markdown
Contributor

Full dependency audit: in-range updates, per-major evaluation, an advisory sweep beyond npm audit, and a repeatable process so this does not have to be re-derived next time.

Security: 14 → 0

Every advisory was cleared by in-range updates; no major bump was required for security.

The headline is next 16.2.10 → 16.3.5, which carried a critical advisory plus ten others. Three matter structurally for this app:

  • GHSA-p293-qw3h-jr36critical, unauthenticated RCE on Windows-hosted servers.
  • GHSA-6gpp-xcg3-4w24 — proxy/middleware bypass in App Router + Turbopack. Route protection lives in src/proxy.ts, so a bypass here is an authentication bypass.
  • GHSA-955p-x3mx-jcvp — unauthenticated disclosure of internal Server Function endpoints, which is where MP data access lives.

Also closed: postcss (XSS + sourceMappingURL arbitrary .map read), @xmldom/xmldom via docxtemplater, undici, sharp, svgo, js-yaml, nanoid, @vitest/mocker.

Sweep beyond npm audit

npm audit reports only GitHub-reviewed advisories, so all 472 packages in the resolved tree were additionally queried against OSV.dev.

One hit, confirmed false positive: grapesjs@0.22.16GHSA-589f-c66p-hxr4. The advisory text says fixed in 0.19.5; the machine-readable record declares {"introduced": "0"} with no fixed event, so it matches every version ever published. Documented so it is not re-triaged.

Supply chain: no deprecated packages in the tree. zod@4.6.4 was published the same day it was installed — verified before accepting (sole maintainer colinhacks, published by GitHub Actions with npm provenance attestations, zero runtime deps, no install scripts).

Majors adopted

Vitest 5 + @vitest/coverage-v8 5, jsdom 30, jest-dom 7, chalk 6 — each applied and verified individually.

Two notes worth reading:

  • jest-dom 7 needed a source change. It moved Vitest expect type augmentation to the @testing-library/jest-dom/vitest entry point. The bare import still registers matchers at runtime, so 805 tests passed while next build gained 60 type errors. src/test-setup.ts now imports the /vitest entry.
  • Vitest 5 relocated reporter output to .vitest/. Coverage was not moved, but it was verified explicitly that test:coverage still writes coverage/coverage-final.json — the exact path test.yml uploads to Codecov. Had it moved, CI would have gone green while uploading nothing.

engines.node: >=20^22.22.2 || ^24.15.0 || >=26.0.0, mirroring the strictest dev dependency (jsdom 30). Node 20 reached EOL in April 2026; CI already runs Node 24.

Majors held back

Each recorded with the upstream condition that clears it and a one-line re-check command:

Package Blocked by
typescript 7 No stable Compiler API until 7.1; typescript-eslint peers typescript: >=4.8.4 <6.1.0
eslint 10 Attempted and reverted — eslint-plugin-react calls a removed rule-context method. 7.37.5 is the latest release and peers eslint ^9.7, so no npm override fixes it
grapesjs 0.23 @grapesjs/react@2.0.0 (latest) peers grapesjs ^0.22.5

Removed — 5 unreferenced packages

openai (zero imports — also makes the pending 6 → 7 major moot), @types/js-cookie (runtime package not installed, and misfiled under dependencies), @types/react-syntax-highlighter (runtime package not installed), autoprefixer (not in postcss.config.mjs; Tailwind v4 prefixes via Lightning CSS), @tailwindcss/typography (never registered with @plugin, no prose usage).

Process

  • /update-deps (.claude/commands/update-deps.md) makes this repeatable.
  • .claude/packages/ holds one record per audit. The "held back" section is the point — it keeps the next audit from re-deriving the same blockers.
  • CLAUDE.md gains a Dependencies section with the audit history and current holds.

.gitignore was silently swallowing .claude/packages/ via a NuGet rule inherited from the Visual Studio template (**/[Pp]ackages/*); negated.

Pre-existing, NOT fixed here

npm run build already fails on dev with 12 type errors in three test files (MP fixtures using snake_case wire names against camelCase types). Confirmed pre-existing — reproduced when only package.json/package-lock.json differed. CI does not catch this: test.yml runs only npm run test:coverage, never npm run build.

Being handled separately. Adding npm run build (or tsc --noEmit) to CI afterwards would stop it regressing.

Verification

Before After
npm audit 14 (1 critical, 9 high, 4 moderate) 0
Tests 805 passing / 50 files 805 passing / 50 files
eslint . clean clean
next build type errors 12 (pre-existing) 12 (unchanged)
Direct dependencies 60 55

🤖 Generated with Claude Code

chriskehayias and others added 5 commits September 13, 2026 06:54
Runs `npm update --save`, which resolves every open npm audit finding
(1 critical, 9 high, 4 moderate) without any major version bump:

- next 16.2.10 -> 16.3.5 (CRITICAL: unauthenticated RCE on Windows
  hosts GHSA-p293-qw3h-jr36, plus proxy bypass, SSRF, cache confusion,
  and Image Optimization RCE/DoS)
- postcss 8.5.16 -> 8.5.28 (XSS + sourceMappingURL arbitrary file read)
- @xmldom/xmldom 0.9.10 -> 0.9.12 via docxtemplater (name injection, ReDoS)
- undici, sharp, svgo, js-yaml, nanoid, browserslist (transitive)
- vitest/@vitest/mocker 4.1.10 -> 4.1.11 (mocker path traversal)
- react/react-dom 19.2.7 -> 19.3.0, lucide-react 1.24 -> 1.45,
  Radix primitives, and the remaining in-range minors

npm audit: 14 vulnerabilities -> 0. Tests: 805 passing, unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each of these has zero references outside package.json (verified by
repo-wide grep excluding node_modules/.next):

- openai          no import anywhere; also removes the pending 6 -> 7 major
- @types/js-cookie  js-cookie itself is not a dependency (and was declared
                  under "dependencies", not "devDependencies")
- @types/react-syntax-highlighter  the runtime package is not installed
- autoprefixer    not referenced by postcss.config.mjs, which loads only
                  @tailwindcss/postcss; Tailwind v4 prefixes via Lightning CSS
- @tailwindcss/typography  never registered with @plugin in globals.css and
                  no `prose` class is used anywhere

Verified: `next build` compiles successfully, 805 tests pass, eslint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…alk 6

All four majors were viable; each was applied and verified separately.

- vitest + @vitest/coverage-v8 4.1.11 -> 5.0.0. Vite is already 8.3.0, so
  the vite ^6.4||^7||^8 peer is satisfied. Vitest 5 clears mocks before
  each test by default, which this suite already did explicitly, so all
  805 tests pass unchanged. Verified `test:coverage` still writes
  coverage/coverage-final.json — the exact path .github/workflows/test.yml
  uploads to Codecov — since Vitest 5 relocated several other reporter
  outputs to .vitest/.
- jsdom 29 -> 30, chalk 5 -> 6 (scripts/setup.ts only; `setup:check`
  verified), @testing-library/jest-dom 6 -> 7.
- jest-dom 7 moved its Vitest `expect` type augmentation to the
  '@testing-library/jest-dom/vitest' entry point. The bare import still
  registers matchers at runtime, so tests passed while `next build`
  reported 60 spurious "Property 'toBeInTheDocument' does not exist"
  errors. src/test-setup.ts now imports the /vitest entry.

engines.node: >=20 -> ^22.22.2 || ^24.15.0 || >=26.0.0. Every package
above requires Node >=22, and jsdom 30's range is the strictest; the old
>=20 would have let an install succeed and then fail at runtime. Node 20
reached end-of-life in April 2026. CI already runs Node 24.

Type-check output is unchanged at the same 12 pre-existing errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a repeatable process so the analysis behind this audit is not
re-derived every time:

- .claude/commands/update-deps.md — the /update-deps command. Applies
  in-range updates, evaluates majors one at a time, sweeps OSV.dev (which
  carries advisories npm audit's GitHub-reviewed-only feed does not), runs
  supply-chain checks, and writes a record.
- .claude/packages/ — one record per audit, plus README conventions. The
  "held back" section is the point: each hold carries the upstream
  condition that clears it and the command that re-checks it.
- CLAUDE.md — Dependencies section with the audit history table, the
  current holds, and two facts worth not rediscovering: CI never runs
  `next build`, and CI's Codecov upload depends on the exact path
  coverage/coverage-final.json.

Also documents the jest-dom v7 `/vitest` entry-point requirement under
Testing, since that break is invisible to the test suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The audit records were silently untracked: .gitignore carries a NuGet rule
from the Visual Studio template, `**/[Pp]ackages/*` (line 208), which also
matches .claude/packages/. Negated with `!.claude/packages/**` — the
directory-only form does not work here, since the NuGet rule excludes the
contents rather than the directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chriskehayias
chriskehayias merged commit d82a94b into dev Sep 13, 2026
2 checks passed
@chriskehayias
chriskehayias deleted the chore/dependency-audit-2026-09 branch September 13, 2026 11:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant