# CapaKraken — Umsetzungsplan Gitea-Repo: `https://gitea.hartmut-noerenberg.com/Hartmut/plANARCHY` 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/?batch=1&input=...` — läuft im Browser-Kontext der gespeicherten Session. tRPC GET-Format: ``` GET /api/trpc/?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 --- --- ## Anforderungsanalyse Alle 6 Issues stammen aus einem Security-Audit und einem Plattform-Review. 5 davon sind Security-Findings (OWASP A02/A05/A07/A09), 1 ist ein Plattform-Thema (Docker-Reproduzierbarkeit). Die Security-Issues sind weitgehend unabhängig voneinander; lediglich #23 greift in den tRPC-Request-Pfad ein und sollte zuletzt umgesetzt werden, da es den Haupt-Auth-Pfad berührt. --- ## Betroffene Pakete & Dateien | Issue | Paket/Pfad | Datei | Art | |-------|-----------|-------|-----| | #21 | `apps/web` | `src/app/api/perf/route.ts` | edit | | #19 | `apps/web` | `src/components/security/MfaSetup.tsx` | edit | | #19 | `apps/web` | `package.json` | edit (neue Dep: `qrcode` + `@types/qrcode`) | | #20 | `packages/api` | `src/lib/webhook-dispatcher.ts` | edit | | #20 | `packages/api` | `src/router/webhook-support.ts` | edit | | #20 | `packages/api` | `src/lib/ssrf-guard.ts` | create | | #22 | `apps/web` | `next.config.ts` | edit | | #23 | `apps/web` | `src/app/api/trpc/[trpc]/route.ts` | edit | | #23 | `apps/web` | `src/server/auth.ts` | edit (Doku/Cleanup) | | #24 | root | `Dockerfile.dev`, `Dockerfile.prod`, `docker-compose.yml`, `docker-compose.prod.yml`, `tooling/docker/app-dev-start.sh` | edit | --- ## Task-Liste (in empfohlener Reihenfolge) ### Issue #21 — /api/perf fail-closed + Query-Token entfernen - [ ] **Task 1:** `GET`-Handler in `apps/web/src/app/api/perf/route.ts` ändern: - `if (cronSecret)` → `if (!cronSecret) return 401/403` (fail-closed) - `queryToken`-Zweig vollständig entfernen - Nur noch `Authorization: Bearer ` prüfen - → Datei: `apps/web/src/app/api/perf/route.ts` - [ ] **Task 2:** Unit-Tests für `/api/perf`: - Test: autorisiert per Header → 200 - Test: kein Secret → 401 - Test: Query-Param-Token → 401 (nicht mehr akzeptiert) - Test: fehlende `CRON_SECRET`-Env → fail-closed (kein Metrics-Leak) - → Datei: `apps/web/src/app/api/perf/route.test.ts` (neu) --- ### Issue #19 — MFA QR lokal rendern - [ ] **Task 3:** `qrcode`-Paket und `@types/qrcode` zu `apps/web/package.json` hinzufügen, `pnpm install` ausführen. - [ ] **Task 4:** `MfaSetup.tsx` umschreiben: - `` durch lokale QR-Generierung ersetzen - `qrcode.toDataURL(uri)` im Client-Effekt aufrufen und als `` rendern - Sicherstellen: der `otpauth://`-URI verlässt den Browser nicht mehr - → Datei: `apps/web/src/components/security/MfaSetup.tsx` - [ ] **Task 5:** Test sicherstellen, dass kein Rendering-Request an externe QR-URL geht: - Unit-Test oder Playwright-Test der prüft, dass kein `` mit `qrserver.com` oder `chart.googleapis.com` gerendert wird - → Datei: `apps/web/src/components/security/MfaSetup.test.tsx` (neu oder bestehend ergänzen) --- ### Issue #20 — Webhook SSRF-Schutz - [ ] **Task 6:** `ssrf-guard.ts` erstellen mit einer `assertWebhookUrlAllowed(url: string): void`-Funktion: - Parst die URL, löst Hostname auf (DNS-Check via Node `dns.lookup`) - Blockt: Loopback (`127.0.0.0/8`, `::1`), RFC1918 (`10.x`, `172.16–31.x`, `192.168.x`), Link-Local (`169.254.x`), Cloud-Metadata (`169.254.169.254`) - Blockt: alle Schemes außer `https` (und `http` nur wenn expliziter Dev-Override gesetzt) - Wirft `TRPCError({ code: "BAD_REQUEST" })` mit allgemeiner Fehlermeldung (ohne IP preiszugeben) - → Datei: `packages/api/src/lib/ssrf-guard.ts` - [ ] **Task 7:** `ssrf-guard` in `webhook-support.ts` und `webhook-dispatcher.ts` integrieren: - Vor Speicherung + vor Dispatch `assertWebhookUrlAllowed(url)` aufrufen - → Dateien: `packages/api/src/router/webhook-support.ts`, `packages/api/src/lib/webhook-dispatcher.ts` - [ ] **Task 8:** Unit-Tests für `ssrf-guard.ts`: - Erlaubt: `https://example.com/hook` - Blockt: `http://localhost/…`, `http://127.0.0.1/…`, `http://10.0.0.1/…`, `http://192.168.1.1/…`, `http://169.254.169.254/…`, `ftp://…` - → Datei: `packages/api/src/__tests__/ssrf-guard.test.ts` (neu) --- ### Issue #22 — CSP härten - [ ] **Task 9:** CSP in `apps/web/next.config.ts` überarbeiten: - `unsafe-eval` entfernen oder nur für `NODE_ENV === "development"` erlauben - `unsafe-inline` aus `script-src` entfernen - Nonce-basierte Inline-Scripts prüfen: Next.js 15 unterstützt CSP-Nonces via `nonce`-Prop auf `