Skip to content

Commit c6832ef

Browse files
WolfTasksWolfgang Kozianclaude
authored
fix(ci): suppress non-exploitable react-router CVE (GHSA-qwww-vcr4-c8h2), unblock nightly scan (#103)
* fix(ci): suppress non-exploitable react-router CVE to unblock nightly scan 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 <noreply@anthropic.com> * fix(ci): allowlist-aware npm-audit gate for suppressed react-router CVE 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 <noreply@anthropic.com> --------- Co-authored-by: Wolfgang Kozian <kozian.wolfgang@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0add6df commit c6832ef

4 files changed

Lines changed: 165 additions & 2 deletions

File tree

.github/scripts/audit-gate.mjs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env node
2+
// npm-audit-Gate mit begründeter Allowlist.
3+
//
4+
// Ersetzt das nackte `npm audit --audit-level=high`: blockt weiterhin bei JEDEM
5+
// HIGH/CRITICAL-Advisory, LÄSST aber explizit begründete Ausnahmen durch. Gleiche
6+
// Policy wie .trivyignore (dort für den Trivy-FS-Scan). npm audit selbst kann keine
7+
// einzelnen Advisories ausnehmen — daher dieser Filter.
8+
//
9+
// Ausführung: aus frontend/ heraus -> node ../.github/scripts/audit-gate.mjs
10+
import { execSync } from 'node:child_process';
11+
12+
// Begründete Ausnahmen: GHSA-ID -> Grund + Datum. Jede Zeile muss auch in .trivyignore
13+
// gespiegelt sein (Trivy-Gate) und via Dependabot-Alert-Dismiss abgedeckt werden.
14+
const ALLOWLIST = {
15+
'GHSA-QWWW-VCR4-C8H2':
16+
'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',
17+
};
18+
19+
const BLOCK = new Set(['high', 'critical']);
20+
const GHSA_RE = /GHSA-[a-z0-9]{4,}-[a-z0-9]{4,}-[a-z0-9]{4,}/i;
21+
22+
function runAudit() {
23+
try {
24+
return execSync('npm audit --json', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
25+
} catch (e) {
26+
// npm audit exit-code ist != 0, sobald Vulns existieren — das JSON liegt trotzdem auf stdout.
27+
if (e.stdout) return e.stdout.toString();
28+
throw e;
29+
}
30+
}
31+
32+
let audit;
33+
try {
34+
audit = JSON.parse(runAudit());
35+
} catch (e) {
36+
console.error('❌ audit-gate: npm-audit-JSON nicht parsebar:', e.message);
37+
process.exit(1);
38+
}
39+
40+
const vulns = audit.vulnerabilities || {};
41+
const offending = new Map(); // GHSA-ID -> { pkg, severity, url }
42+
43+
for (const [pkg, info] of Object.entries(vulns)) {
44+
if (!BLOCK.has(info.severity)) continue;
45+
for (const via of info.via || []) {
46+
// String-Einträge = transitive Weiterreichung eines anderen Pakets, kein eigenes
47+
// Advisory -> überspringen (verhindert Doppelzählung/false-fail bei re-export-Paketen).
48+
if (typeof via !== 'object' || via === null) continue;
49+
const sev = String(via.severity || info.severity || '').toLowerCase();
50+
if (!BLOCK.has(sev)) continue;
51+
const m = String(via.url || '').match(GHSA_RE) || String(via.title || '').match(GHSA_RE);
52+
const id = m ? m[0].toUpperCase() : (via.source != null ? `SOURCE-${via.source}` : `${via.name || pkg}-UNKNOWN`);
53+
if (!(id in ALLOWLIST)) offending.set(id, { pkg: via.name || pkg, severity: sev, url: via.url });
54+
}
55+
}
56+
57+
if (offending.size > 0) {
58+
console.error('❌ npm audit: nicht-allowlistete HIGH/CRITICAL-Advisories gefunden:');
59+
for (const [id, o] of offending) console.error(` - ${id} (${o.severity}) in ${o.pkg} ${o.url || ''}`);
60+
console.error('\nBehebe die Dependency ODER ergänze eine begründete Ausnahme in');
61+
console.error('.github/scripts/audit-gate.mjs (ALLOWLIST) + .trivyignore + Dependabot-Alert-Dismiss.');
62+
process.exit(1);
63+
}
64+
65+
const allowed = Object.keys(ALLOWLIST);
66+
console.log('✅ npm-audit-Gate ok: keine nicht-allowlisteten HIGH/CRITICAL-Advisories.');
67+
if (allowed.length) console.log(` Aktive begründete Ausnahmen: ${allowed.join(', ')}`);

.github/workflows/ci.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ jobs:
5555
working-directory: frontend
5656
run: npm ci
5757

58-
- name: npm audit (block on high/critical)
58+
- name: npm audit (block on high/critical, begründete Allowlist)
5959
working-directory: frontend
60-
run: npm audit --audit-level=high
60+
# Blockt bei jedem HIGH/CRITICAL-Advisory, lässt aber begründete Ausnahmen
61+
# durch (Allowlist im Script, gespiegelt in .trivyignore). npm audit selbst
62+
# kann keine einzelnen Advisories ausnehmen.
63+
run: node ../.github/scripts/audit-gate.mjs
6164

6265
- name: i18n scanner self-tests
6366
working-directory: frontend

.trivyignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
# Begründete Ausnahmen. Format pro Zeile: CVE-ID # Grund + Datum
22
# z.B.: CVE-2024-12345 # nicht ausnutzbar (Komponente ungenutzt), 2026-06-28
3+
4+
# react-router GHSA-qwww-vcr4-c8h2 (CSRF, CWE-352, CVSS 7.1): betrifft AUSSCHLIESSLICH
5+
# den unstable RSC-Modus (React Server Components). TaskWolf-Frontend ist eine reine
6+
# Client-SPA (createBrowserRouter, kein SSR/RSC, keine unstable_ APIs) -> Code-Pfad
7+
# ungenutzt, nicht ausnutzbar. Fix nur in react-router 8.3.0 (Major, react-router-dom
8+
# in v8 aufgelöst). Auflösung geplant via v8-Migration -> docs/superpowers/plans/2026-07-29-react-router-v8-migration.md
9+
GHSA-qwww-vcr4-c8h2 # RSC-only CSRF, Client-SPA nicht betroffen, 2026-07-29
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# React Router v7 → v8 Migration (Backlog)
2+
3+
**Status:** Backlog / geplant — noch NICHT umgesetzt
4+
**Erstellt:** 2026-07-29
5+
**Auslöser:** Security-Advisory `GHSA-qwww-vcr4-c8h2` (react-router, HIGH, CVSS 7.1)
6+
**Aktueller Interim-Zustand:** CVE als *nicht ausnutzbar* suppressed an **drei** Gates
7+
(2026-07-29): `.trivyignore` (Trivy-Nightly + ci.yml-Spiegel), `.github/scripts/audit-gate.mjs`
8+
ALLOWLIST (npm-audit-Gate in ci.yml) und Dependabot-Alert #82 dismissed `not_used`.
9+
Dieser Plan löst alle drei Ausnahmen sauber auf.
10+
11+
## Kontext / Warum
12+
13+
`GHSA-qwww-vcr4-c8h2` ist eine CSRF-Lücke (CWE-352) **ausschließlich im unstable
14+
RSC-Modus** von React Router. Betroffene Range: `react-router >= 7.12.0 < 8.3.0`,
15+
Fix nur in **8.3.0**. Kein v7-Patch existiert.
16+
17+
TaskWolf-Frontend ist eine **reine Client-SPA** (`createBrowserRouter` +
18+
`RouterProvider`, Data-/Library-Mode, kein SSR/RSC, keine `unstable_`-APIs) →
19+
der verwundbare Code-Pfad wird nicht genutzt, die CVE ist bei uns **nicht
20+
ausnutzbar**. Deshalb ist die Migration eine geplante Aufräum-Aufgabe, keine
21+
Notfall-Remediation.
22+
23+
`react-router-dom` hat **kein v8** (letzte Version 7.18.2, ebenfalls verwundbar):
24+
das Paket wurde in v8 aufgelöst und in `react-router` zusammengeführt. Die Migration
25+
ist damit unvermeidlich ein Major-Bump mit Import-Umstellung in 42 Dateien.
26+
27+
## Umfang (Ist-Stand main, verifiziert 2026-07-29)
28+
29+
- **42 Dateien** importieren aus `react-router-dom` (`frontend/src/**`).
30+
- Genutzte Symbole (alle existieren in v8, keine dom-exklusiven APIs):
31+
`Link, NavLink, Navigate, Outlet, RouterProvider, createBrowserRouter,
32+
useMatch, useNavigate, useParams, useSearchParams`.
33+
- Keine Loader/Actions/`meta`/`useMatches` → das v8-Rename `data → loaderData`
34+
betrifft uns **nicht**.
35+
- Kein Framework-Mode → v8-Future-Flags (`v8_middleware`, `v8_viteEnvironmentApi`,
36+
Vite-Environment-API, Cloudflare-Plugin-Wechsel) betreffen uns **nicht**.
37+
38+
## v8-Breaking-Changes, die uns betreffen
39+
40+
1. **Paket entfernt:** `npm uninstall react-router-dom`, `react-router@^8.3.0` rein.
41+
2. **Import-Umstellung** in allen 42 Dateien:
42+
- `Link, NavLink, Navigate, Outlet, createBrowserRouter, useMatch,
43+
useNavigate, useParams, useSearchParams``from 'react-router'`
44+
- ⚠️ **`RouterProvider``from 'react-router/dom'`** (Sonderfall! NICHT
45+
`react-router`). Betrifft nur `frontend/src/main.tsx:4`.
46+
47+
## Voraussetzungen / Blocker (WICHTIG)
48+
49+
- **React ≥ 19.2.7** — wir sind exakt bei `19.2.7` ✅ (peerDep von react-router@8.3.0
50+
ist `react >=19.2.7`, `react-dom >=19.2.7`).
51+
- **Node ≥ 22.22** — ⚠️ **BLOCKER:** `.github/workflows/ci.yml` `setup-node`
52+
läuft aktuell auf **Node 20**. Muss im selben PR auf **≥ 22.22** (empf. 24) gehoben
53+
werden, sonst bricht `vite build` unter der v8-Engines-Anforderung. Frontend-Dockerfile
54+
nutzt bereits `node:26-alpine` ✅.
55+
- **Vite ≥ 7** — wir sind bei `^8.1.4` ✅ (nur für Framework-Mode relevant, wir nicht).
56+
57+
## Aufgabenliste
58+
59+
1. **Worktree** anlegen (Wolfgangs Standard für Implementierungs-Sessions).
60+
2. `frontend/package.json`: `react-router-dom` raus, `"react-router": "^8.3.0"` rein.
61+
3. Import-Umstellung 42 Dateien (scripted sed für 9 Symbole → `react-router`;
62+
`main.tsx` `RouterProvider``react-router/dom` manuell/separat).
63+
4. `.github/workflows/ci.yml`: `node-version: '20'``'22'` (oder `'24'`).
64+
5. `npm install``package-lock.json` aktualisieren; prüfen, dass `react-router-dom`
65+
komplett aus dem Lockfile verschwindet.
66+
6. Alle **drei** Interim-Ausnahmen für `GHSA-qwww-vcr4-c8h2` **entfernen** (CVE dann echt behoben):
67+
`.trivyignore`-Zeile, `ALLOWLIST`-Eintrag in `.github/scripts/audit-gate.mjs`,
68+
und Dependabot-Alert-Dismiss ist mit dem Upgrade automatisch gegenstandslos.
69+
7. **Verifikation:**
70+
- `npm run typecheck` grün (Frontend hat kein Test-Framework → Typecheck ist das Gate).
71+
- `npm run build` grün.
72+
- Manueller Browser-Smoke DE/EN: Login → Navigation → Board → Issue-Dialog
73+
(`?issue=`) → Settings-Tabs → Deep-Link-Routen (`useParams`/`useSearchParams`).
74+
- PR-CI: Trivy-Gate grün OHNE die trivyignore-Ausnahme.
75+
8. **Dependabot ignore lockern:** falls in `.github/dependabot.yml` ein react-router-
76+
Ignore ergänzt wurde, wieder entfernen (aktuell nur `typescript` semver-major ignored).
77+
9. **Release:** In einer v1.0.x-Version ausliefern; Memory + CHANGELOG aktualisieren.
78+
79+
## Risiko / Hinweise
80+
81+
- Frontend hat **kein** Test-Framework (Typecheck + manuell) → sorgfältiger
82+
manueller Smoke ist Pflicht.
83+
- Major-Bump: trotz identischer API-Namen auf subtile Verhaltensänderungen bei
84+
relativem Routing / `Navigate replace` / `useSearchParams`-Defaults achten.
85+
- Reihenfolge im PR beachten: Node-Bump (Schritt 4) muss zusammen mit dem
86+
Paket-Bump landen, sonst CI-Rotlauf (vgl. TS7-Bump-Lektion, PR #81).

0 commit comments

Comments
 (0)