security/platform: close audit findings #19–#26

Tests, CSP nonce middleware, SSRF guard, perf-route hardening,
Docker env isolation, migration runbook, RBAC E2E coverage.

Tickets resolved:
- #19: MfaSetup.test.ts — static source tests confirming local QR rendering
- #20: ssrf-guard.test.ts (16 tests) + webhook-procedure-support mock fix
- #21: /api/perf route.test.ts (5 tests) — header-only auth, fail-closed
- #22: middleware.ts (nonce-based CSP) + middleware.test.ts (6 tests);
       layout.tsx async + nonce prop; CSP removed from next.config.ts
- #23: Active-session registry enforcement verified (already in codebase)
- #24: docker-compose.yml REDIS_URL hardcoded (no host-env substitution)
- #25: docker-compose.yml REDIS_URL + docs/developer-runbook.md created
- #26: e2e/dev-system/rbac-data-access.spec.ts (12 tests, 3 roles × 4 procedures)

Quality gates: tsc clean, api 1447/1447, web 189/189 passing.
Turbo concurrency capped at 2 (package.json) to prevent OOM under
parallel test runs.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-04-01 22:14:20 +02:00
parent 4901bc878b
commit bfdf0a82da
15 changed files with 1013 additions and 23 deletions
+150 -2
View File
@@ -1,7 +1,155 @@
# CapaKraken — Umsetzungsplan: Security + Platform Issues
# CapaKraken — Umsetzungsplan
Gitea-Repo: `https://gitea.hartmut-noerenberg.com/Hartmut/plANARCHY`
Stand: 2026-04-01 | Issues: #19, #20, #21, #22, #23, #24
Stand: 2026-04-01 | Issues: #19#26
---
## Plan: In-Review-Tickets verifizieren und in main integrieren
### Anforderungsanalyse
8 Tickets sind `in-review`. Ziel: jeden Ticket-Scope im Code verifizieren, Gitea-Kommentar
mit Bewertung (✅ akzeptiert / ⚠️ Nachbesserung nötig) hinterlassen, dann alle offenen
Changes in einem sauberen Commit auf `main` integrieren.
**Umfang der Verifikation pro Ticket:**
- Code-Artefakte im Repo prüfen (Dateien, Tests)
- Quality-Gates (tsc, test:unit) müssen grün sein
- Akzeptanzkriterien aus dem Ticket-Body abgleichen
- Gitea-Kommentar: Strukturiertes Urteil mit Befunden
**Integration-Strategie:**
- Alle ungestagten Änderungen (`git status`) gehören zu den Tickets #20, #22, #25, #26
- Ein einziger Commit auf `main` mit klarer Commit-Message
- plan.md und docs/ mitcommiten
### Betroffene Pakete & Dateien
| Paket | Dateien | Art der Änderung |
|-------|---------|-----------------|
| `apps/web` | `next.config.ts`, `src/app/layout.tsx`, `src/middleware.ts`, `src/middleware.test.ts`, `src/app/api/perf/route.ts`, `src/app/api/perf/route.test.ts`, `src/components/security/MfaSetup.test.ts`, `e2e/dev-system/rbac-data-access.spec.ts`, `e2e/dev-system/helpers.ts` | Verifikation + commit |
| `packages/api` | `src/__tests__/ssrf-guard.test.ts`, `src/__tests__/webhook-procedure-support.test.ts` | Verifikation + commit |
| root | `package.json`, `docker-compose.yml`, `docs/developer-runbook.md`, `plan.md` | Verifikation + commit |
### Task-Liste (atomare Schritte in Reihenfolge)
**Phase 1 — Verifikation & Kommentierung (sequenziell, ein Ticket nach dem anderen)**
- [ ] **V-1:** Ticket #19 (MFA-QR) — Code-Check: `MfaSetup.tsx` + `MfaSetup.test.ts`; Urteil auf Gitea posten
- [ ] **V-2:** Ticket #20 (Webhook SSRF) — Code-Check: `ssrf-guard.ts` + `ssrf-guard.test.ts` + `webhook-procedure-support.test.ts`; Urteil posten
- [ ] **V-3:** Ticket #21 (/api/perf) — Code-Check: `route.ts` + `route.test.ts`; Urteil posten
- [ ] **V-4:** Ticket #22 (CSP) — Code-Check: `middleware.ts` + `middleware.test.ts` + `next.config.ts` + `layout.tsx`; Urteil posten
- [ ] **V-5:** Ticket #23 (Active-Session-Registry) — Code-Check: Session-Guard-Middleware, Auth-Flow; Urteil posten
- [ ] **V-6:** Ticket #24 (Docker reproducibility) — Code-Check: `docker-compose.yml`, Dockerfile.dev; Urteil posten
- [ ] **V-7:** Ticket #25 (Env/Migration-Strategie) — Code-Check: `docker-compose.yml`, `docs/developer-runbook.md`; Urteil posten
- [ ] **V-8:** Ticket #26 (RBAC E2E-Tests) — Code-Check: `rbac-data-access.spec.ts`; Urteil posten
**Phase 2 — Quality Gates**
- [ ] **Q-1:** `pnpm --filter @capakraken/web exec tsc --noEmit` — keine Fehler
- [ ] **Q-2:** `pnpm --filter @capakraken/api test:unit` — alle Tests grün
- [ ] **Q-3:** `pnpm --filter @capakraken/web test:unit` — alle Tests grün
**Phase 3 — Git-Integration**
- [ ] **G-1:** `git add` aller ungestagten Änderungen (alle Ticket-Artefakte)
- [ ] **G-2:** Commit mit Message: `security/platform: close audit findings #19#26 (tests, CSP nonce, SSRF guard, runbook)`
- [ ] **G-3:** `git push origin main`
### Abhängigkeiten
- V-1 bis V-8 sind unabhängig voneinander, aber sequenziell (ein Kommentar pro Ticket)
- Q-1 bis Q-3 können nach allen V-Tasks parallel laufen
- G-1/G-2/G-3 müssen nach Q-1..3 erfolgen (kein Commit bei roten Tests)
### Akzeptanzkriterien
- [ ] Alle 8 in-review-Tickets haben einen Bewertungskommentar
- [ ] `pnpm test:unit` läuft grün (beide Pakete)
- [ ] `tsc --noEmit` ohne Fehler
- [ ] Alle neuen Dateien in einem sauberen Commit auf `main`
- [ ] `git status` danach: working tree clean
### Risiken & offene Fragen
- **Ticket #23 (Active-Session):** Die Implementierung in früheren Sessions könnte durch spätere Auth-Fixes (jti→sid) überholt worden sein — genau prüfen
- **Ticket #24 vs. #25:** Überlappen in `docker-compose.yml` — beide Tickets betreffen dieselbe Datei; ein Commit deckt beide ab
- **Push auf main:** Direkt auf `main` — kein PR da kein Remote-Review-Prozess konfiguriert. Sicherstellen dass alle Tests grün sind
---
---
## Ticket #25 — Docker/Env/Migration-Strategie
### Anforderungsanalyse
Ziel: Docker-Container-Lifecycle ohne manuelle Eingriffe.
Konkrete Mängel:
1. `REDIS_URL` in `docker-compose.yml` nutzt `${REDIS_URL:-redis://redis:6379}` — Host-Env-Var kann Docker-internen Wert überschreiben (gleiche Klasse wie das behobene `DATABASE_URL`-Problem)
2. Migration-Strategy undokumentiert: DB per `db push` aufgebaut, dann Migration hinzugefügt → `migrate deploy` scheitert mit P3005, erforderte `migrate resolve --applied`
3. Kein Developer-Runbook (Setup, Restart, DB-Ops fehlen)
### Betroffene Dateien
| Datei | Änderung |
|---|---|
| `docker-compose.yml` | `REDIS_URL` hardcoden |
| `docs/developer-runbook.md` | create |
### Task-Liste
- [ ] **#25-T1:** `docker-compose.yml``REDIS_URL: redis://redis:6379` (Literal, kein `${}`)
- [ ] **#25-T2:** `docs/developer-runbook.md` erstellen mit: Erstmaligem Setup, DB-Migration-Strategie inkl. P3005-Recovery, E2E_TEST_MODE-Erklärung, Container-Neustart-Checkliste
---
## Ticket #26 — RBAC Datenzugriffs-Matrix E2E-Tests
### Anforderungsanalyse
Neue Testdatei `apps/web/e2e/dev-system/rbac-data-access.spec.ts` mit **Netzwerk-Ebene** tRPC-Response-Assertions (nicht nur UI-Sichtbarkeit).
Grundlage `docs/route-access-matrix.md`:
| tRPC-Prozedur | Audience | Admin | Manager | Viewer |
|---|---|---|---|---|
| `user.list` | `admin-only` | ✓ 200 | FORBIDDEN | FORBIDDEN |
| `allocation.listView` | `planning-read` | ✓ 200 | ✓ 200 | FORBIDDEN |
| `resource.listSummaries` | `resource-overview` | ✓ 200 | ✓ 200 | FORBIDDEN |
| `user.listAssignable` | `manager-write` | ✓ 200 | ✓ 200 | FORBIDDEN |
Technik: `page.evaluate()` mit `fetch()` gegen `/api/trpc/<proc>?batch=1&input=...` — läuft im Browser-Kontext der gespeicherten Session.
tRPC GET-Format:
```
GET /api/trpc/<proc>?batch=1&input={"0":{"json":null}}
Erfolg: [{"result":{"data":{"json":[...]}}}]
Fehler: [{"error":{"json":{"data":{"code":"FORBIDDEN","httpStatus":403}}}}]
```
### Betroffene Dateien
| Datei | Änderung |
|---|---|
| `apps/web/e2e/dev-system/rbac-data-access.spec.ts` | create |
### Task-Liste
- [ ] **#26-T1:** Datei erstellen mit `trpcQuery(page, procedure, input?)` Helper-Funktion
- [ ] **#26-T2:** Admin-Describe-Block (4 Tests: alle 4 Prozeduren → Erfolg erwartet)
- [ ] **#26-T3:** Manager-Describe-Block (4 Tests: `user.list` → FORBIDDEN, Rest → Erfolg)
- [ ] **#26-T4:** Viewer-Describe-Block (4 Tests: alle → FORBIDDEN)
- [ ] **#26-T5:** Smoke-Run: `pnpm exec playwright test e2e/dev-system/rbac-data-access.spec.ts --config=playwright.dev.config.ts`
### Risiken
- `allocation.listView` könnte ein Pflicht-Input-Objekt erfordern → Falls `BAD_REQUEST` statt FORBIDDEN, Schema im Router prüfen und minimalen Input übergeben
- Viewer-Permissions aus DB-Seed (SystemRoleConfig) — prüfen ob VIEWER tatsächlich kein `VIEW_PLANNING` hat
---
---