Files
CapaKraken/plan.md
T
Hartmut 0e119cfe73 security: close audit findings #19–#23 and harden Docker setup (#24)
#19 MFA QR code: render locally via qrcode package, remove external qrserver.com request
#20 Webhook SSRF: add ssrf-guard.ts with DNS-verified IP blocklist; enforce on create/update/test/dispatch
#21 /api/perf: fail-closed when CRON_SECRET missing; remove query-string token auth
#22 CSP: remove unsafe-eval and unsafe-inline from script-src in production builds
#23 Active session registry: forward jti into session object; validate against ActiveSession on every tRPC request

#24 Docker: add missing packages/application to Dockerfile.dev; fix pnpm-lock.yaml glob;
    run db:migrate:deploy on container start so a fresh checkout boots without manual steps

Also: fix pre-existing TS error in e2e/allocations.spec.ts (args.length literal type overlap)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-01 18:19:21 +02:00

9.4 KiB
Raw Blame History

CapaKraken — Umsetzungsplan: Security + Platform Issues

Gitea-Repo: https://gitea.hartmut-noerenberg.com/Hartmut/plANARCHY Stand: 2026-04-01 | Issues: #19, #20, #21, #22, #23, #24


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 <secret> 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:

    • <img src="https://api.qrserver.com/..."> durch lokale QR-Generierung ersetzen
    • qrcode.toDataURL(uri) im Client-Effekt aufrufen und als <img src={dataUrl}> 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 <img src> 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.1631.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 <Script> — recherchieren ob tatsächliche Inline-Scripts existieren, die Nonces brauchen
    • Unnötige connect-src-Origins bereinigen
    • → Datei: apps/web/next.config.ts
  • Task 10: Smoke-Test: App unter gehärteter CSP starten, Browser-DevTools auf CSP-Violations prüfen (mindestens Login → Dashboard → Timeline → Allocations).
    Gefundene Violations entweder beheben (Move to external file / Nonce) oder als Known Exception dokumentieren.


Issue #23 — Active-Session-Registry bei jedem Request prüfen

  • Task 11: Im tRPC-Route-Handler (apps/web/src/app/api/trpc/[trpc]/route.ts) nach dem auth()-Call die jti aus dem Session-Token lesen und gegen prisma.activeSession validieren:

    const jti = session?.user?.jti as string | undefined;
    if (jti) {
      const active = await prisma.activeSession.findUnique({ where: { jti } });
      if (!active) {
        return NextResponse.json({ error: "Session revoked" }, { status: 401 });
      }
    }
    
    • Gilt für alle authentisierten tRPC-Requests
    • Gilt auch für nicht-tRPC Auth-Pfade wenn vorhanden (Route-Handler prüfen)
    • → Datei: apps/web/src/app/api/trpc/[trpc]/route.ts
  • Task 12: Unit-/Integrations-Tests:

    • gültige aktive Session → Request durch
    • ausgeloggte (gelöschte) Session → 401
    • durch Concurrent-Session-Limit verdrängte Session → 401
    • → Datei: apps/web/src/app/api/trpc/[trpc]/route.test.ts (neu oder ergänzen)

Issue #24 — Docker-Setup host-unabhängig

  • Task 13: Dockerfile.dev prüfen und absichern:

    • pnpm install muss im Container ablaufen (kein Volume-Mount der Host-node_modules)
    • prisma generate muss Teil des Starts sein (nicht als Host-Voraussetzung)
    • Fehlende Systempakete (z. B. OpenSSL für Prisma) explizit installieren
    • → Datei: Dockerfile.dev
  • Task 14: Dockerfile.prod prüfen:

    • Multi-Stage-Build: Build-Stage hat pnpm + alle Dev-Deps; Runtime-Stage nur Prod-Artefakte
    • Generierte Prisma-Artefakte (node_modules/.prisma) korrekt aus Build-Stage kopiert
    • → Datei: Dockerfile.prod
  • Task 15: docker-compose.yml absichern:

    • node_modules-Volume-Override korrekt gesetzt damit Host-Modules nicht reinmappen
    • app-dev-start.sh ausführbar und alle Schritte (generate, migrate, start) enthalten
    • → Dateien: docker-compose.yml, tooling/docker/app-dev-start.sh
  • Task 16: Frischer-Checkout-Smoke-Test dokumentieren:

    git clone … && cd capakraken
    docker compose up --build
    # → App erreichbar, Login funktioniert, keine Host-Abhängigkeiten
    

    → Schritt in docs/ oder README.md festhalten


Abhängigkeiten

Task 1 → Task 2          (Tests setzen fertige Impl voraus)
Task 3 → Task 4 → Task 5 (Dep-Install → Impl → Test)
Task 6 → Task 7 → Task 8 (Guard-Lib → Integration → Tests)
Task 9 → Task 10         (CSP-Änderung → Smoke-Test)
Task 11 → Task 12        (Session-Impl → Tests)
Task 1316 unabhängig von allen anderen (Docker-only)

Parallel möglich (nach Task-Gruppe):
- #21 (Tasks 12) || #19 (Tasks 35) || #24 (Tasks 1316)
- #20 (Tasks 68) || #22 (Tasks 910) — erst nachdem #21/#19 fertig sind (separater Branch)
- #23 (Tasks 1112) — zuletzt, da Auth-Pfad berührt

Akzeptanzkriterien

  • pnpm test:unit läuft grün
  • pnpm --filter @capakraken/web exec tsc --noEmit — keine neuen Errors
  • pnpm lint — sauber
  • #21: /api/perf ohne CRON_SECRET gibt 401/403, Query-Token wird nicht mehr akzeptiert
  • #19: MfaSetup macht keinen Request an qrserver.com oder chart.googleapis.com
  • #20: Webhook-Save und Dispatch für http://127.0.0.1/… gibt BAD_REQUEST
  • #22: Browser-DevTools zeigt keine CSP-Violations für Haupt-User-Flows in Production-Mode
  • #23: Request mit gelöschter ActiveSession-jti gibt 401
  • #24: docker compose up --build auf sauberem Checkout bootet die App ohne Host-Abhängigkeiten

Risiken & offene Fragen

Risiko Einschätzung Maßnahme
CSP-Nonces in Next.js 15: <Script> und Tailwind CSS benötigen ggf. Nonces Mittel Vor Task 9 in Next.js-15-Doku recherchieren; ggf. nur unsafe-eval entfernen als erster Schritt
Session-Registry-Check (#23) erhöht DB-Load: jeder tRPC-Request = 1 DB-Read Mittel Redis-Cache mit kurzer TTL (30s) als Opt-in; erst messen ob nötig
SSRF-Guard DNS-Lookup: async, könnte Race-Condition durch DNS-Rebinding haben Niedrig Nach DNS-Lookup Socket-Verbindung ebenfalls gegen IP prüfen (defense-in-depth)
Docker #24: node_modules-Volume-Semantik bei pnpm-Workspaces komplex Mittel Symlink-Struktur von pnpm in Container testen; ggf. --shamefully-hoist Flag
jti im Session-Token: Auth.js-Version muss jti ins JWT schreiben Offen In auth.ts prüfen ob token.jti tatsächlich im JWT-Callback persistiert wird