# Produktionsreife: Audit-Ergebnis & Umsetzungsplan (Stand 2026-05-07, Phase-3-Update)

**Zweck:** Einheitliche Referenz gegen **Drift** zwischen Sicherheits-/Betriebsanforderungen und Code.  
**Bezug:** Zugriffsschicht `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`, Cursor-Regel `.cursor/rules/access-layer.mdc`.

---

## Teil A — Audit-Ergebnis (Kurzfassung)

### A.1 Kritisch / Hoch (behoben oder geplant)

| ID | Befund | Status (2026-05-07) |
|----|--------|----------------------|
| SEC-01 | `GET /api/profile` (Legacy): ohne Header wurde das **erste Profil der DB** geliefert → IDOR / Datenleck | **Behoben:** Profil immer aus Session |
| SEC-02 | OpenAPI `/docs`, `/redoc`, `/openapi.json` in Produktion exponiert | **Behoben:** bei `ENVIRONMENT=production` aus; Override `PUBLIC_OPENAPI=1` |
| SEC-03 | `/api/health/ready` mit Tabellendetails / Migrationszähler öffentlich | **Behoben:** in Prod nur kompakte Antwort; Detail via `HEALTH_READY_PUBLIC_DETAIL=1` |
| SEC-04 | Statische Auslieferung `/media` ohne Auth (Link-Leak) | **Behoben:** `GET /api/exercises/.../media/.../file` mit Governance + `ssetoken`; `/media` nur mit `ALLOW_PUBLIC_MEDIA_STATIC=1` |
| OPS-01 | PostgreSQL-Hostport in `docker-compose.yml` | **Behoben:** Bind `127.0.0.1:5434` (nur Host-Local) |

### A.2 Mittel / Konsistenz

| ID | Befund | Status |
|----|--------|--------|
| CON-01 | Frontend: direktes `fetch('/api/profiles')` ohne zentralen Client | **Behoben** (AdminCatalogsPage → `api.listProfiles`) |
| CON-02 | `ProfileContext.jsx` (unused): falsches Token-Feld / `session` | **Behoben** (`user` + `getCurrentProfile` / `listProfiles`) |
| MEM-01 | Vereinsmitgliederverzeichnis zeigt E-Mail an alle Mitglieder | **Behoben:** E-Mail nur Plattform-Admin / `can_manage_club_org` |
| MAT-01 | `GET /maturity-models/{id}` für jeden Auth-Nutzer | **Behoben:** nur Portal-Admin (UI nutzt ohnehin nur Admin-Panel) |

### A.3 Niedrig / Offen

| ID | Befund | Status |
|----|--------|--------|
| CON-03 | Sparten (`division`) vs. Zielbild ACCESS_LAYER | **Roadmap** — siehe Phase 3 unten (Stufe D); nicht in diesem Schritt implementiert |
| OPS-02 | CSP / restliche Browser-Härtung | **Behoben (Basis):** CSP auf SPA-Dokumente (nginx `location /`); API `X-Content-Type-Options: nosniff` (FastAPI-Middleware) |
| MISC-01 | `auth.py` Debug-`print` beim Import | **Behoben** (entfernt) |
| MISC-02 | `delete_profile`: DELETE auf Mitai-Tabellen schlägt fehl, wenn Tabelle fehlt | **Behoben:** nur löschen, wenn Tabelle existiert |

---

## Teil B — Priorisierter Umsetzungsplan

### Phase 1 — Sofort (Beta-Blocker) ✅ umgesetzt in Repo-Stand

1. Legacy-Endpunkte `/api/profile` GET/PUT: **nur Session-`profile_id`**, kein „erstes Profil“-Fallback.
2. OpenAPI in Produktion deaktivieren; opt-in für Notfall-Debugging.
3. Readiness-Endpoint in Prod entschärfen; operatives Detail per Env.
4. Automatisierte **Security-Release-Tests** (`tests/test_security_release.py`) + Script `scripts/security_release_checks.py` in CI.
5. Profil-Löschung: optionale Tabellen nur bei Existenz.
6. Frontend: Admin-Kataloge Profilliste über `api.js`.

### Phase 2 — Kurzfristig (vor breitem Go-Live) ✅ Hauptpunkte umgesetzt (2026-05-07)

1. **Medien:** `GET /api/exercises/{id}/media/{mid}/file` — Auth (`X-Auth-Token` oder `?ssetoken=`), Zugriff wie GET Übung; statisches `/media` standardmäßig aus.
2. **Mitgliederverzeichnis:** E-Mail nur wenn Plattform-Admin oder `can_manage_club_org`.
3. **DB-Port:** Prod-Compose Postgres an `127.0.0.1` gebunden.
4. **`GET /api/maturity-models/{id}`:** nur Portal-Admin (Liste/resolve unverändert für Trainer).

### Phase 3 — Mittelfristig

1. **CSP & Header:** Content-Security-Policy für die SPA (nginx, `location /`); `X-Content-Type-Options: nosniff` auf allen FastAPI-Responses — ✅ umgesetzt.
2. **Audit-Pflege:** bei Tenant/Governance-Änderungen `ACCESS_LAYER_ENDPOINT_AUDIT.md` und dieses Dokument anpassen — fortlaufend (Hinweis im Endpoint-Audit).
3. **Division / Sparten (CON-03, Stufe D):** noch **nicht** code-seitig durchgesetzt. Nächste technische Schritte (für eigenes Epic):
   - Ist-Zustand: `division_id` auf Objekten & `division_lead` in `can_plan_in_club` ohne strikte Objekt-Filter.
   - Ziel: Lesen/Schreiben nur mit passender Sparten-Mitgliedschaft bzw. Rolle; einheitliche Filter in Listen (`training_groups`, Übungen, Planung).
   - Tests: Zwei Nutzer, zwei Sparten, kein Cross-Lesen; Superadmin-Pfade getrennt.
   - Spec: `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` Stufe D.

---

## Teil C — CI / Qualitätssicherung

- **ACCESS_LAYER_STRICT:** `scripts/check_access_layer_hints.py` (unverändert Pflicht).
- **Security-Release:** `python scripts/security_release_checks.py` (pytest-Modul `test_security_release`).
- **Regression Profil:** `tests/test_profiles_read_access.py` (Legacy-Route).

---

## Teil D — Umgebungsvariablen (Referenz)

| Variable | Bedeutung |
|----------|-----------|
| `ENVIRONMENT` | `production` / `prod` → OpenAPI aus, Health-Ready kompakt |
| `PUBLIC_OPENAPI` | `1` / `true` → OpenAPI trotz Prod einschalten (nur Debugging) |
| `HEALTH_READY_PUBLIC_DETAIL` | `1` / `true` → volle Ready-JSON inkl. Tabellenliste in Prod |
| `ALLOW_PUBLIC_MEDIA_STATIC` | `1` / `true` → öffentliches Mount von `/media/` (Notfall/Legacy; Standard: aus) |
| `CSP` (nginx) | Kein Env: Policy fest in `frontend/nginx.conf` (`location /`). API auf **anderer Host-URL** als SPA: `connect-src` in der Policy erweitern oder Build mit envsubst. |

---

**Pflege:** Nach jeder relevanten Änderung **Status-Spalte** und ggf. **Phase** aktualisieren; bei neuem Audit Datum im Titel anpassen oder Abschnitt „Changelog“ ergänzen.

### Changelog

- **2026-05-07:** Phase 2 Medien, Mitgliederverzeichnis E-Mail, maturity GET admin-only, Postgres localhost bind, Tests `test_exercise_media_download.py`.
- **Phase 3:** CSP (nginx SPA), API `X-Content-Type-Options` (FastAPI), Test `test_api_attachments_x_content_type_options_nosniff`; Division-Durchsetzung nur als Roadmap (CON-03).
