From 8c6e938ebbcddac39b8e121c9f0c89e068a9ba9a Mon Sep 17 00:00:00 2001 From: Wolfgang Kozian Date: Wed, 29 Jul 2026 19:38:30 +0200 Subject: [PATCH 1/2] fix(ci): suppress non-exploitable react-router CVE to unblock nightly scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Nightly-Security-Scan- und Dependabot-Update-Läufe schlagen seit 2026-07-25 fehl wegen GHSA-qwww-vcr4-c8h2 (react-router 7.18.1, HIGH, CVSS 7.1). Die CVE ist eine CSRF-Lücke ausschließlich im unstable RSC-Modus (React Server Components). TaskWolf-Frontend ist eine reine Client-SPA (createBrowserRouter, kein SSR/RSC) → verwundbarer Code-Pfad ungenutzt, nicht ausnutzbar. Fix nur in react-router 8.3.0 (Major; react-router-dom in v8 aufgelöst, kein v7-Patch) → Dependabot kann nicht auto-fixen. - .trivyignore: begründete Ausnahme für GHSA-qwww-vcr4-c8h2 - Dependabot-Alert #82 dismissed (not_used) via API - v8-Migrations-Plan als Backlog dokumentiert, löst die Ausnahme später auf Co-Authored-By: Claude Opus 4.8 --- .trivyignore | 7 ++ .../2026-07-29-react-router-v8-migration.md | 83 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-29-react-router-v8-migration.md diff --git a/.trivyignore b/.trivyignore index 2357007b..d69447db 100644 --- a/.trivyignore +++ b/.trivyignore @@ -1,2 +1,9 @@ # Begründete Ausnahmen. Format pro Zeile: CVE-ID # Grund + Datum # z.B.: CVE-2024-12345 # nicht ausnutzbar (Komponente ungenutzt), 2026-06-28 + +# react-router GHSA-qwww-vcr4-c8h2 (CSRF, CWE-352, CVSS 7.1): betrifft AUSSCHLIESSLICH +# den unstable RSC-Modus (React Server Components). TaskWolf-Frontend ist eine reine +# Client-SPA (createBrowserRouter, kein SSR/RSC, keine unstable_ APIs) -> Code-Pfad +# ungenutzt, nicht ausnutzbar. Fix nur in react-router 8.3.0 (Major, react-router-dom +# in v8 aufgelöst). Auflösung geplant via v8-Migration -> docs/superpowers/plans/2026-07-29-react-router-v8-migration.md +GHSA-qwww-vcr4-c8h2 # RSC-only CSRF, Client-SPA nicht betroffen, 2026-07-29 diff --git a/docs/superpowers/plans/2026-07-29-react-router-v8-migration.md b/docs/superpowers/plans/2026-07-29-react-router-v8-migration.md new file mode 100644 index 00000000..3a86d954 --- /dev/null +++ b/docs/superpowers/plans/2026-07-29-react-router-v8-migration.md @@ -0,0 +1,83 @@ +# React Router v7 → v8 Migration (Backlog) + +**Status:** Backlog / geplant — noch NICHT umgesetzt +**Erstellt:** 2026-07-29 +**Auslöser:** Security-Advisory `GHSA-qwww-vcr4-c8h2` (react-router, HIGH, CVSS 7.1) +**Aktueller Interim-Zustand:** CVE als *nicht ausnutzbar* suppressed +(`.trivyignore` + Dependabot-Alert #82 dismissed `not_used`, 2026-07-29). Dieser Plan +löst die Ausnahme sauber auf. + +## Kontext / Warum + +`GHSA-qwww-vcr4-c8h2` ist eine CSRF-Lücke (CWE-352) **ausschließlich im unstable +RSC-Modus** von React Router. Betroffene Range: `react-router >= 7.12.0 < 8.3.0`, +Fix nur in **8.3.0**. Kein v7-Patch existiert. + +TaskWolf-Frontend ist eine **reine Client-SPA** (`createBrowserRouter` + +`RouterProvider`, Data-/Library-Mode, kein SSR/RSC, keine `unstable_`-APIs) → +der verwundbare Code-Pfad wird nicht genutzt, die CVE ist bei uns **nicht +ausnutzbar**. Deshalb ist die Migration eine geplante Aufräum-Aufgabe, keine +Notfall-Remediation. + +`react-router-dom` hat **kein v8** (letzte Version 7.18.2, ebenfalls verwundbar): +das Paket wurde in v8 aufgelöst und in `react-router` zusammengeführt. Die Migration +ist damit unvermeidlich ein Major-Bump mit Import-Umstellung in 42 Dateien. + +## Umfang (Ist-Stand main, verifiziert 2026-07-29) + +- **42 Dateien** importieren aus `react-router-dom` (`frontend/src/**`). +- Genutzte Symbole (alle existieren in v8, keine dom-exklusiven APIs): + `Link, NavLink, Navigate, Outlet, RouterProvider, createBrowserRouter, + useMatch, useNavigate, useParams, useSearchParams`. +- Keine Loader/Actions/`meta`/`useMatches` → das v8-Rename `data → loaderData` + betrifft uns **nicht**. +- Kein Framework-Mode → v8-Future-Flags (`v8_middleware`, `v8_viteEnvironmentApi`, + Vite-Environment-API, Cloudflare-Plugin-Wechsel) betreffen uns **nicht**. + +## v8-Breaking-Changes, die uns betreffen + +1. **Paket entfernt:** `npm uninstall react-router-dom`, `react-router@^8.3.0` rein. +2. **Import-Umstellung** in allen 42 Dateien: + - `Link, NavLink, Navigate, Outlet, createBrowserRouter, useMatch, + useNavigate, useParams, useSearchParams` → `from 'react-router'` + - ⚠️ **`RouterProvider` → `from 'react-router/dom'`** (Sonderfall! NICHT + `react-router`). Betrifft nur `frontend/src/main.tsx:4`. + +## Voraussetzungen / Blocker (WICHTIG) + +- **React ≥ 19.2.7** — wir sind exakt bei `19.2.7` ✅ (peerDep von react-router@8.3.0 + ist `react >=19.2.7`, `react-dom >=19.2.7`). +- **Node ≥ 22.22** — ⚠️ **BLOCKER:** `.github/workflows/ci.yml` `setup-node` + läuft aktuell auf **Node 20**. Muss im selben PR auf **≥ 22.22** (empf. 24) gehoben + werden, sonst bricht `vite build` unter der v8-Engines-Anforderung. Frontend-Dockerfile + nutzt bereits `node:26-alpine` ✅. +- **Vite ≥ 7** — wir sind bei `^8.1.4` ✅ (nur für Framework-Mode relevant, wir nicht). + +## Aufgabenliste + +1. **Worktree** anlegen (Wolfgangs Standard für Implementierungs-Sessions). +2. `frontend/package.json`: `react-router-dom` raus, `"react-router": "^8.3.0"` rein. +3. Import-Umstellung 42 Dateien (scripted sed für 9 Symbole → `react-router`; + `main.tsx` `RouterProvider` → `react-router/dom` manuell/separat). +4. `.github/workflows/ci.yml`: `node-version: '20'` → `'22'` (oder `'24'`). +5. `npm install` → `package-lock.json` aktualisieren; prüfen, dass `react-router-dom` + komplett aus dem Lockfile verschwindet. +6. `.trivyignore`: `GHSA-qwww-vcr4-c8h2`-Zeile **entfernen** (CVE dann echt behoben). +7. **Verifikation:** + - `npm run typecheck` grün (Frontend hat kein Test-Framework → Typecheck ist das Gate). + - `npm run build` grün. + - Manueller Browser-Smoke DE/EN: Login → Navigation → Board → Issue-Dialog + (`?issue=`) → Settings-Tabs → Deep-Link-Routen (`useParams`/`useSearchParams`). + - PR-CI: Trivy-Gate grün OHNE die trivyignore-Ausnahme. +8. **Dependabot ignore lockern:** falls in `.github/dependabot.yml` ein react-router- + Ignore ergänzt wurde, wieder entfernen (aktuell nur `typescript` semver-major ignored). +9. **Release:** In einer v1.0.x-Version ausliefern; Memory + CHANGELOG aktualisieren. + +## Risiko / Hinweise + +- Frontend hat **kein** Test-Framework (Typecheck + manuell) → sorgfältiger + manueller Smoke ist Pflicht. +- Major-Bump: trotz identischer API-Namen auf subtile Verhaltensänderungen bei + relativem Routing / `Navigate replace` / `useSearchParams`-Defaults achten. +- Reihenfolge im PR beachten: Node-Bump (Schritt 4) muss zusammen mit dem + Paket-Bump landen, sonst CI-Rotlauf (vgl. TS7-Bump-Lektion, PR #81). From 12494eba2e4e5cf949478abaef55d312ad8c43d1 Mon Sep 17 00:00:00 2001 From: Wolfgang Kozian Date: Wed, 29 Jul 2026 19:43:09 +0200 Subject: [PATCH 2/2] fix(ci): allowlist-aware npm-audit gate for suppressed react-router CVE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der frontend-build npm-audit-Gate (`npm audit --audit-level=high`) failt ebenfalls an GHSA-qwww-vcr4-c8h2 — .trivyignore greift dort nicht, und npm audit kann einzelne Advisories nicht ausnehmen. Ersetzt durch .github/scripts/audit-gate.mjs: blockt weiterhin bei jedem nicht-allowlisteten HIGH/CRITICAL-Advisory, lässt aber die begründete Ausnahme (RSC-only, Client-SPA nicht betroffen) durch. Gleiche Policy wie .trivyignore. Negativ-getestet: leere Allowlist -> exit 1. v8-Migrations-Plan um dritten Aufräum-Ort ergänzt. Co-Authored-By: Claude Opus 4.8 --- .github/scripts/audit-gate.mjs | 67 +++++++++++++++++++ .github/workflows/ci.yml | 7 +- .../2026-07-29-react-router-v8-migration.md | 11 +-- 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 .github/scripts/audit-gate.mjs diff --git a/.github/scripts/audit-gate.mjs b/.github/scripts/audit-gate.mjs new file mode 100644 index 00000000..d7e7c3d7 --- /dev/null +++ b/.github/scripts/audit-gate.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env node +// npm-audit-Gate mit begründeter Allowlist. +// +// Ersetzt das nackte `npm audit --audit-level=high`: blockt weiterhin bei JEDEM +// HIGH/CRITICAL-Advisory, LÄSST aber explizit begründete Ausnahmen durch. Gleiche +// Policy wie .trivyignore (dort für den Trivy-FS-Scan). npm audit selbst kann keine +// einzelnen Advisories ausnehmen — daher dieser Filter. +// +// Ausführung: aus frontend/ heraus -> node ../.github/scripts/audit-gate.mjs +import { execSync } from 'node:child_process'; + +// Begründete Ausnahmen: GHSA-ID -> Grund + Datum. Jede Zeile muss auch in .trivyignore +// gespiegelt sein (Trivy-Gate) und via Dependabot-Alert-Dismiss abgedeckt werden. +const ALLOWLIST = { + 'GHSA-QWWW-VCR4-C8H2': + 'react-router RSC-only CSRF (CWE-352); TaskWolf ist Client-SPA (createBrowserRouter, kein RSC/SSR) -> nicht ausnutzbar. Fix nur in react-router 8.3.0 (Major); v8-Migration geplant, 2026-07-29', +}; + +const BLOCK = new Set(['high', 'critical']); +const GHSA_RE = /GHSA-[a-z0-9]{4,}-[a-z0-9]{4,}-[a-z0-9]{4,}/i; + +function runAudit() { + try { + return execSync('npm audit --json', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); + } catch (e) { + // npm audit exit-code ist != 0, sobald Vulns existieren — das JSON liegt trotzdem auf stdout. + if (e.stdout) return e.stdout.toString(); + throw e; + } +} + +let audit; +try { + audit = JSON.parse(runAudit()); +} catch (e) { + console.error('❌ audit-gate: npm-audit-JSON nicht parsebar:', e.message); + process.exit(1); +} + +const vulns = audit.vulnerabilities || {}; +const offending = new Map(); // GHSA-ID -> { pkg, severity, url } + +for (const [pkg, info] of Object.entries(vulns)) { + if (!BLOCK.has(info.severity)) continue; + for (const via of info.via || []) { + // String-Einträge = transitive Weiterreichung eines anderen Pakets, kein eigenes + // Advisory -> überspringen (verhindert Doppelzählung/false-fail bei re-export-Paketen). + if (typeof via !== 'object' || via === null) continue; + const sev = String(via.severity || info.severity || '').toLowerCase(); + if (!BLOCK.has(sev)) continue; + const m = String(via.url || '').match(GHSA_RE) || String(via.title || '').match(GHSA_RE); + const id = m ? m[0].toUpperCase() : (via.source != null ? `SOURCE-${via.source}` : `${via.name || pkg}-UNKNOWN`); + if (!(id in ALLOWLIST)) offending.set(id, { pkg: via.name || pkg, severity: sev, url: via.url }); + } +} + +if (offending.size > 0) { + console.error('❌ npm audit: nicht-allowlistete HIGH/CRITICAL-Advisories gefunden:'); + for (const [id, o] of offending) console.error(` - ${id} (${o.severity}) in ${o.pkg} ${o.url || ''}`); + console.error('\nBehebe die Dependency ODER ergänze eine begründete Ausnahme in'); + console.error('.github/scripts/audit-gate.mjs (ALLOWLIST) + .trivyignore + Dependabot-Alert-Dismiss.'); + process.exit(1); +} + +const allowed = Object.keys(ALLOWLIST); +console.log('✅ npm-audit-Gate ok: keine nicht-allowlisteten HIGH/CRITICAL-Advisories.'); +if (allowed.length) console.log(` Aktive begründete Ausnahmen: ${allowed.join(', ')}`); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index faa6fe75..e6f42e18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,9 +55,12 @@ jobs: working-directory: frontend run: npm ci - - name: npm audit (block on high/critical) + - name: npm audit (block on high/critical, begründete Allowlist) working-directory: frontend - run: npm audit --audit-level=high + # Blockt bei jedem HIGH/CRITICAL-Advisory, lässt aber begründete Ausnahmen + # durch (Allowlist im Script, gespiegelt in .trivyignore). npm audit selbst + # kann keine einzelnen Advisories ausnehmen. + run: node ../.github/scripts/audit-gate.mjs - name: i18n scanner self-tests working-directory: frontend diff --git a/docs/superpowers/plans/2026-07-29-react-router-v8-migration.md b/docs/superpowers/plans/2026-07-29-react-router-v8-migration.md index 3a86d954..7a4110ca 100644 --- a/docs/superpowers/plans/2026-07-29-react-router-v8-migration.md +++ b/docs/superpowers/plans/2026-07-29-react-router-v8-migration.md @@ -3,9 +3,10 @@ **Status:** Backlog / geplant — noch NICHT umgesetzt **Erstellt:** 2026-07-29 **Auslöser:** Security-Advisory `GHSA-qwww-vcr4-c8h2` (react-router, HIGH, CVSS 7.1) -**Aktueller Interim-Zustand:** CVE als *nicht ausnutzbar* suppressed -(`.trivyignore` + Dependabot-Alert #82 dismissed `not_used`, 2026-07-29). Dieser Plan -löst die Ausnahme sauber auf. +**Aktueller Interim-Zustand:** CVE als *nicht ausnutzbar* suppressed an **drei** Gates +(2026-07-29): `.trivyignore` (Trivy-Nightly + ci.yml-Spiegel), `.github/scripts/audit-gate.mjs` +ALLOWLIST (npm-audit-Gate in ci.yml) und Dependabot-Alert #82 dismissed `not_used`. +Dieser Plan löst alle drei Ausnahmen sauber auf. ## Kontext / Warum @@ -62,7 +63,9 @@ ist damit unvermeidlich ein Major-Bump mit Import-Umstellung in 42 Dateien. 4. `.github/workflows/ci.yml`: `node-version: '20'` → `'22'` (oder `'24'`). 5. `npm install` → `package-lock.json` aktualisieren; prüfen, dass `react-router-dom` komplett aus dem Lockfile verschwindet. -6. `.trivyignore`: `GHSA-qwww-vcr4-c8h2`-Zeile **entfernen** (CVE dann echt behoben). +6. Alle **drei** Interim-Ausnahmen für `GHSA-qwww-vcr4-c8h2` **entfernen** (CVE dann echt behoben): + `.trivyignore`-Zeile, `ALLOWLIST`-Eintrag in `.github/scripts/audit-gate.mjs`, + und Dependabot-Alert-Dismiss ist mit dem Upgrade automatisch gegenstandslos. 7. **Verifikation:** - `npm run typecheck` grün (Frontend hat kein Test-Framework → Typecheck ist das Gate). - `npm run build` grün.