---
title: "KADiCon KAR-613 — Gate 3 Code-Fix-Plan (konkretisiert, NICHT freigegeben)"
type: audit
tags: [kadicon, security, incident, p0, kar-613, gate-3, code-fix-plan, plan-only]
date: 2026-05-24
status: aktiv
source: "Read-only Code-Analyse am Clone /tmp/kadicon-analysis (keine Werte; jwt.constants.ts NICHT geöffnet)"
confidence: high
related: "[[kadicon-kar613-security-incident-plan]], [[kadicon-kar613-rotation-checklist]], [[kadicon-kar613-live-infra-abgleich]]"
---

# KAR-613 — Gate 3 Code-Fix-Plan

> **Gate 3 noch NICHT freigegeben — reiner PLAN.** Kein Code-Change, kein Push, kein PR, kein Deploy, kein History-Rewrite. Dateipfade read-only am Clone verifiziert. **Production-Auth-Changes (A/B/D) → Review-Gate vor Merge.** `jwt.constants.ts` bewusst nicht geöffnet (enthält den Fallback-Wert).

## Reihenfolge-Abhängigkeit (wichtig)
**D (Infra-Env `JWT_SECRET` setzen) → A (Fallback entfernen) → JWT-Wert je Env setzen + alte Tokens bewusst invalidieren.** Fallback-Removal vor gesetztem Env-Secret = Boot-Fail (durch ECS circuit-breaker auto-rollback abgefangen, aber Reihenfolge einhalten).

## A. JWT-Fallback entfernen + Fail-Fast  *(Production-Auth → Review-Gate)*
- **Datei:** `libs/backend/auth/src/lib/constants/jwt.constants.ts`
- **Änderung:** hardcodeten Fallback-String entfernen; `SECRET` ausschließlich aus `process.env.JWT_SECRET`; bei unset/leer hart werfen (oder via B erzwingen). Konsumenten unverändert: `core.module.ts` (`JwtModule.register`), `auth/user/tenant.service.ts` lesen weiter `JWT_CONSTANTS.SECRET`.
- **Risiko:** App startet nicht mehr ohne gesetztes Env (gewollt) → D MUSS zuerst live sein.

## B. Env-Validation (Boot fail-fast)
- **Datei:** `libs/backend/config/src/lib/env.validation.ts` (existierendes `validate()` via `ConfigModule.forRoot({ validate })`, `class-validator`, `skipMissingProperties:false`).
- **Änderung:** in `EnvironmentVariables` die required Secrets als Felder ergänzen — `JWT_SECRET` (`@IsString() @IsNotEmpty() @MinLength(32)`), plus die real genutzten Pflicht-Secrets (`DATABASE_PASSWORD`, `STRIPE_API_KEY`, `STRIPE_WEBHOOK_SECRET`, `AWS_ACCESS_KEY`, `AWS_SECRET_KEY`, `FIREBASE`). Fehlt eins → Boot wirft.
- **Risiko:** nur tatsächlich gesetzte Keys erzwingen (gegen Gate-2.1-Task-Def-Liste abgleichen), sonst bricht Boot an optionalen Feldern.

## C. Swagger Prod-Guard
- **Datei:** `apps/backend/src/main.ts` (Z. ~31-40).
- **Änderung:** `SwaggerModule.setup(...)` nur wenn `NODE_ENV !== 'production'` (bzw. `APP_MODE`); `--openapi`-Spec-Gen-Pfad bleibt funktionsfähig. Alternativ Basic-Auth statt komplett aus.
- **Bonus (gleiche Datei, separat halten):** CORS `origin:'*', methods:'*'` auf Dashboard-/Customer-Domains einschränken — Hardening, nicht in den Auth-Fix mischen.

## D. Infra-Env-Ergänzung `JWT_SECRET` (Terraform — Infra/Deploy-Gate, Kais/Admin)
- **Dateien:** `infra/src/modules/ecs/variables.tf` (neue `variable "jwt_secret" { sensitive = true }`), `infra/src/modules/ecs/main.tf` (Env-Eintrag `{ name="JWT_SECRET", value=var.jwt_secret }` im `backend`-Container), `infra/src/main.tf` (`jwt_secret = var.jwt_secret` ans `ecs`-Modul), `infra/src/variables.tf` (root-Var), tfvar-Quelle je Workspace (Wert setzen — NICHT im Code).
- **Risiko:** Infra-Change → Ausführung Kais/Admin, Staging→Prod. Danach erst A scharf schalten.

## E. Rate-Limiting (`@nestjs/throttler` — bestätigt NICHT vorhanden)
- **Dateien:** `package.json` (+`@nestjs/throttler`), `apps/backend/src/app/core/core.module.ts` (`ThrottlerModule.forRoot` + globaler `APP_GUARD`=`ThrottlerGuard`), `@Throttle`-Overrides auf Login (`features/auth`), Reservation-Create (`features/reservation/controllers/reservation.controller.ts` + `customer-reservation.controller.ts`), Payment-Intent, QR-Verify (s. F), Password-Reset.
- **Hinweis:** Throttler-Storage auf Valkey legen (Cache via `KeyvValkey` existiert) → konsistente Limits bei Scale-out; sonst In-Memory (bei `desired_count=1` ok).

## F. QR-Bruteforce-Schutz
- **Dateien:** `apps/backend/src/app/features/reservation/controllers/customer-reservation.controller.ts:31` (`@Get('verify/:tableId/:query')`) + `services/reservation.service.ts`; zusätzlich `apps/backend/src/app/features/room-plan/controllers/token.controller.ts:23` (`@Get('verify/:token')`).
- **Änderung:** striktes `@Throttle` + Token-Härtung (langer Random-Query/Token, konstant-Zeit-Lookup, ggf. Backoff/Lockout). Code-TODO „Verify proper working" adressieren.

## G. Customer-Search-Allowlist
- **Dateien:** `apps/backend/src/app/features/reservation/controllers/reservation.controller.ts` + `services/reservation.service.ts` (`searchCustomers`).
- **Änderung:** `field` gegen feste Allowlist `['name','email','phoneNumber']` (DTO `@IsIn`), sonst 400; Admin-Authorization/tenant-Scope prüfen.

## H. .gitignore + Example-Files
- **Dateien:** `.gitignore` (`**/.env`, `**/env_config.json`, Dumps/Backups/Reports); `.env.example` / `env_config.example.json` nur Dummy-Platzhalter.
- **Hinweis:** schützt nur künftige Commits — History-Cleanup ist Gate 4.

## I. CI Secret Scanning (Phase 5, gated)
- **Dateien:** `.github/workflows/pull-request.yml` + `merge.yml` (Gitleaks/TruffleHog-Step als Pflicht-Gate).

## Tests (neu)
- `env.validation.spec.ts`: Boot ohne `JWT_SECRET` wirft (unit).
- jwt.constants: kein Fallback mehr — unset → throw (unit).
- throttler: 429 nach N Requests (e2e) auf login/reservation/payment/QR.
- searchCustomers: nicht-allowlisted `field` → 400 (unit).
- swagger: bei `NODE_ENV=production` nicht erreichbar (e2e).

## Verifikationsbefehle (read-only/lokal)
- `grep -rn "ThrottlerModule" apps libs` → trifft nach Fix.
- App-Boot mit `NODE_ENV=production` ohne `JWT_SECRET` → wirft (fail-fast).
- `curl $PROD/api` → 404/401 in Prod (Swagger zu).
- `git check-ignore apps/backend/.env apps/admin-cli/env_config.json` → ignored.
- `gitleaks detect --source . --log-opts=--all` → 0 (erst nach Gate-4-History-Cleanup wirklich 0).

## Rollback
- Alle Fixes auf eigenem lokalen Branch → verwerfen = kein Prod-Impact (kein Push).
- JWT-Guard/Throttler: Revert-Commit.
- Infra-Env `JWT_SECRET`: tf-Var/Env-Eintrag entfernen + Deploy; ECS circuit-breaker rollt Boot-Fail automatisch zurück.

## Gates/Abhängigkeiten
Gate 3 nicht freigegeben. A/B/D = Production-Auth → Review-Gate. Kein Push/PR/Deploy/History-Rewrite ohne separates Go. Offene Admin-Voraussetzungen: [[kadicon-kar613-admin-questions]].

## Verwandt
- [[kadicon-kar613-security-incident-plan]] · [[kadicon-kar613-rotation-checklist]] · [[kadicon-kar613-live-infra-abgleich]] · [[kadicon-kar613-admin-questions]] · Linear KAR-613
