Upgrade react-router to v8 to clear GHSA-qwww-vcr4-c8h2 #132

Closed
opened 2026-07-30 21:17:33 +00:00 by claude-bot · 1 comment

Context

Filed from the #72 audit triage — the one advisory npm audit still reports after the
lockfile refresh, and the only one currently carrying an entry in
frontend/audit-allowlist.json.

Advisory GHSA-qwww-vcr4-c8h2
Title React Router: RSC Mode CSRF Bypass Allows Action Execution Before 400 Response
Severity High
Affected react-router 7.12.0 – 8.2.0
Fixed in 8.3.0
Currently locked react-router / react-router-dom 7.18.2

It does not apply to Circa today

The vulnerable path is React Router's RSC (React Server Components) mode, which needs a
React Router server runtime handling server actions. Circa's frontend is a static Vite build
served to the browser; the API is FastAPI.

  • frontend/src/App.tsx uses BrowserRouter with declarative <Routes> / <Route>
  • no SSR, no framework mode, no createBrowserRouter data router, no actions
  • the application's CSRF control is server-side anyway — CSRFOriginMiddleware rejects
    cross-origin state-changing requests (#63), so a client-router CSRF weakness would not by
    itself reach the API

So this is not an incident, and it is not a reason to hold v0.1.1. It is recorded here so
the exception in the allowlist has somewhere to point and cannot quietly become permanent.

Why it is not just fixed

7.18.2 is the newest v7; the fix is only in 8.3.0. That is a major bump, which
renovate.json deliberately holds for review (#130) — and forcing a major framework upgrade
through under a CVE that does not apply to us is exactly the reflex that breaks working
software.

Done when

  • react-router-dom is on v8, or a documented decision is made to stay on v7
  • The app builds, typechecks, and the review workspace / photo browser / dashboard routes
    still work (there is no frontend test suite yet — #12)
  • The GHSA-qwww-vcr4-c8h2 entry is removed from frontend/audit-allowlist.json

Note on the deadline

The allowlist entry carries reviewBy: 2026-10-31. frontend/scripts/audit.mjs fails the
build
once that date passes, so this cannot be forgotten — CI will start refusing the merge
and force a re-decision rather than letting a stale exception ride.

References

  • frontend/audit-allowlist.json
  • frontend/scripts/audit.mjs
  • frontend/src/App.tsx

Related: #72 (audit tooling), #130 (Renovate policy — majors held for review), #12 (frontend tests).

## Context Filed from the #72 audit triage — the one advisory `npm audit` still reports after the lockfile refresh, and the only one currently carrying an entry in `frontend/audit-allowlist.json`. | | | |---|---| | Advisory | [GHSA-qwww-vcr4-c8h2](https://github.com/advisories/GHSA-qwww-vcr4-c8h2) | | Title | React Router: RSC Mode CSRF Bypass Allows Action Execution Before 400 Response | | Severity | High | | Affected | `react-router` 7.12.0 – 8.2.0 | | Fixed in | 8.3.0 | | Currently locked | `react-router` / `react-router-dom` 7.18.2 | ## It does not apply to Circa today The vulnerable path is React Router's **RSC (React Server Components) mode**, which needs a React Router server runtime handling server actions. Circa's frontend is a static Vite build served to the browser; the API is FastAPI. - `frontend/src/App.tsx` uses `BrowserRouter` with declarative `<Routes>` / `<Route>` - no SSR, no framework mode, no `createBrowserRouter` data router, no actions - the application's CSRF control is server-side anyway — `CSRFOriginMiddleware` rejects cross-origin state-changing requests (#63), so a client-router CSRF weakness would not by itself reach the API So this is **not an incident**, and it is not a reason to hold v0.1.1. It is recorded here so the exception in the allowlist has somewhere to point and cannot quietly become permanent. ## Why it is not just fixed 7.18.2 is the newest v7; the fix is only in 8.3.0. That is a **major** bump, which `renovate.json` deliberately holds for review (#130) — and forcing a major framework upgrade through under a CVE that does not apply to us is exactly the reflex that breaks working software. ## Done when - [ ] `react-router-dom` is on v8, or a documented decision is made to stay on v7 - [ ] The app builds, typechecks, and the review workspace / photo browser / dashboard routes still work (there is no frontend test suite yet — #12) - [ ] The `GHSA-qwww-vcr4-c8h2` entry is removed from `frontend/audit-allowlist.json` ## Note on the deadline The allowlist entry carries `reviewBy: 2026-10-31`. `frontend/scripts/audit.mjs` **fails the build** once that date passes, so this cannot be forgotten — CI will start refusing the merge and force a re-decision rather than letting a stale exception ride. ## References - `frontend/audit-allowlist.json` - `frontend/scripts/audit.mjs` - `frontend/src/App.tsx` Related: #72 (audit tooling), #130 (Renovate policy — majors held for review), #12 (frontend tests).
claude-bot added this to the v0.2.0 milestone 2026-07-30 21:17:37 +00:00
Author

Done in d5a8db0. react-router 8.3.0.

The migration is one rename

v8 consolidates react-router-dom into react-routerreact-router-dom stops at 7.18.2 and does not publish a v8. So the change is eight import sites (five components, two test files, the render helper) and the dependency swap. Nothing else: BrowserRouter, Routes, Route, Navigate, useParams, useNavigate, useSearchParams, NavLink and MemoryRouter all keep their names and signatures.

Verified

npm audit zero advisories — info, low, moderate, high, critical all 0
npm run audit:ci passes
tsc --noEmit clean
Vitest 58 passed
npm run build clean (291.23 kB, gzip 90.27 kB — a hair smaller than v7)
e2e all 7 journeys pass against a real browser

The e2e run matters most here: the routing is what changed, and the reviewer journey navigates the SPA by clicking through the browser, the workspace, and back.

The allowlist is now empty, and the gate insisted

scripts/audit.mjs refused the build the moment the advisory cleared:

npm audit gate failed:
  - Allowlist entry GHSA-qwww-vcr4-c8h2 matches nothing in the current audit.
    Remove it -- an exception must not outlive its advisory.

Which is exactly what #72 built it for. "The exception is now stale" became something that had to be dealt with in the same change, rather than a comment left in a file for a year. audit-allowlist.json now holds an empty allow list and a note recording why.

The original triage stands, incidentally — the RSC path was never reachable from a static Vite build with declarative routes — so this closes a real advisory without there having been an exposure to fix.

Done in d5a8db0. `react-router` **8.3.0**. ## The migration is one rename v8 consolidates `react-router-dom` into `react-router` — `react-router-dom` stops at 7.18.2 and does not publish a v8. So the change is eight import sites (five components, two test files, the render helper) and the dependency swap. Nothing else: `BrowserRouter`, `Routes`, `Route`, `Navigate`, `useParams`, `useNavigate`, `useSearchParams`, `NavLink` and `MemoryRouter` all keep their names and signatures. ## Verified | | | |---|---| | `npm audit` | **zero** advisories — info, low, moderate, high, critical all 0 | | `npm run audit:ci` | passes | | `tsc --noEmit` | clean | | Vitest | 58 passed | | `npm run build` | clean (291.23 kB, gzip 90.27 kB — a hair *smaller* than v7) | | e2e | all 7 journeys pass against a real browser | The e2e run matters most here: the routing is what changed, and the reviewer journey navigates the SPA by clicking through the browser, the workspace, and back. ## The allowlist is now empty, and the gate insisted `scripts/audit.mjs` refused the build the moment the advisory cleared: ``` npm audit gate failed: - Allowlist entry GHSA-qwww-vcr4-c8h2 matches nothing in the current audit. Remove it -- an exception must not outlive its advisory. ``` Which is exactly what #72 built it for. "The exception is now stale" became something that had to be dealt with in the same change, rather than a comment left in a file for a year. `audit-allowlist.json` now holds an empty `allow` list and a note recording why. The original triage stands, incidentally — the RSC path was never reachable from a static Vite build with declarative routes — so this closes a real advisory without there having been an exposure to fix.
Sign in to join this conversation.
No description provided.