shinkan-jinkendo/docs/compliance-audit.md
Lars aff3020b13
Some checks failed
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 18s
Test Suite / playwright-tests (push) Failing after 58s
docs(compliance): Dokumentation auf Stand P-01b und P-01c aktualisiert (0.8.74)
- compliance-implementation.md: P-01c um copy-as-draft (0.8.72) und jsPDF
  + Abschnitts-Sortierung/-Einfuegen (0.8.74) ergaenzt; Testzusammenfassung
  auf 17 Playwright-Tests aktualisiert; Versionheader 0.8.74
- compliance-roadmap.md: App-Version 0.8.74; P-01-Blocker-Beschreibung
  vollstaendig (alle technischen Faehigkeiten); P-01c-Erweiterungen in
  Abschlussliste; Schnellreferenz aktualisiert
- compliance-package-register.md: P-01 Letzter Stand auf 0.8.74; Verweise
  und Hinweise ergaenzt (copy-as-draft, jsPDF, Sortierung); Fortschritt 0.8.74
- compliance-audit.md: Amendment §14.2 und §14.3 mit aktuellem Umsetzungsstand
  P-01/P-01b/P-01c; historische Befunde unveraendert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:17:51 +02:00

993 lines
45 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Compliance-Audit Shinkan Jinkendo
> **Status:** Entwurf — technischer Audit, kein Rechtsanwalt
> **Datum:** 2026-05-09
> **Auditor:** Claude Code
> **App-Version:** 0.8.65
> **Rechtlicher Hinweis:** Dieses Dokument ist eine technische Analyse. Es ersetzt keine Rechtsberatung. Alle als „juristisch zu prüfen" markierten Punkte müssen durch einen Rechtsanwalt oder Datenschutzbeauftragten bewertet werden. Kein Code wurde verändert.
---
## 1. Executive Summary
Die Shinkan Jinkendo App ist technisch solide aufgebaut: robuste Mandantentrennung (TenantContext), mehrstufiges Löschkonzept für Medien, serverseitig erzwungene Zugriffskontrolle. Die Kernarchitektur der Datenschicht ist gut.
**Kritische Compliance-Lücken:**
- Keine Rechtstexte (Impressum, Datenschutzerklärung, Nutzungsbedingungen, Medienrichtlinie)
- Kein DSA-konformes Meldeverfahren für rechtswidrige Inhalte (UGC-Plattform)
- Kein Recht-am-eigenen-Bild/Minderjährigen-Check beim Medienupload
- Kein Self-Service-Löschrecht für Nutzer (nur Admin kann Konten löschen)
- Auth-Token im localStorage (XSS-Risiko, TDDDG-Dokumentationspflicht)
- HSTS-Header fehlt in der Nginx-Konfiguration
- Papierkorb-Retention-Job nicht automatisch geplant
- Passwort-Mindestlänge inkonsistent (Register: 8, PIN-Änderung: 4 Zeichen)
Vor öffentlichem Betrieb sind die kritischen Findings (KRIT-01 bis KRIT-07) zu adressieren.
---
## 2. Scope
| Bereich | Geprüft |
|---------|---------|
| Backend-Router (alle .py) | ✓ |
| Datenbankmigrationen (001046) | ✓ |
| Frontend App.jsx, Routing, Auth | ✓ |
| API-Authentifizierung und Autorisierung | ✓ |
| Mandanten-/Zugriffschicht (TenantContext, club_tenancy) | ✓ |
| Medien-Archiv (media_assets, lifecycle) | ✓ |
| PWA-Konfiguration (manifest.webmanifest) | ✓ |
| Nginx-Konfiguration (nginx.conf) | ✓ |
| Docker-Compose (docker-compose.yml) | ✓ |
| Vorhandene Tests (backend/tests/*.py) | ✓ |
| LocalStorage / SessionStorage Nutzung | ✓ |
| Rechtstexte (Impressum, DSGVO, AGB) | ✓ |
| CSP / Security-Header | ✓ |
| Passwort-Handling, Session-Management | ✓ |
---
## 3. Annahmen
- App ist öffentlich im Internet erreichbar unter `shinkan.jinkendo.de` (HTTPS)
- SSL/TLS-Terminierung erfolgt am externen Reverse-Proxy vor dem Nginx-Container
- Betreiber ist im EU-Raum ansässig (DSGVO anwendbar)
- Minderjährige können sich registrieren (keine Altersverifikation vorhanden)
- Die Plattform erlaubt Upload und Anzeige von Bildern und Videos mit Personenabbildungen
---
## 4. Nicht geprüfte Bereiche
- Produktions-Infrastruktur (Synology NAS, Raspberry Pi 5) nur Konfigurationsdateien
- Netzwerkinfrastruktur (Fritz!Box) außerhalb des Repos
- SMTP-Anbieter im Detail (Anbieter unbekannt aus Umgebungsvariablen)
- Aktive Penetrationstests
- Backup-Prozess und Restore-Test (kein Skript im Repository)
---
## 5. Technische Bestandsaufnahme
### 5.1 Architektur
| Komponente | Technologie | Sicherheitsrelevanz |
|-----------|-------------|---------------------|
| Frontend | React 18 + Vite, SPA | Routing, Token-Speicherung |
| Backend | FastAPI Python 3.12 | Zugriffskontrolle, Validierung |
| Datenbank | PostgreSQL 16 Alpine | Datenhaltung, Mandantentrennung |
| Proxy | Nginx (Docker) | CSP, Security-Header, Upload-Limit |
| Storage | Lokaler Bind-Mount via Docker | Medienspeicherung |
| Auth | Token-basiert (Sessions-Tabelle) | Session-Management |
| PWA | Web App Manifest + Icons | Offline-Caching (kein Service Worker!) |
| E-Mail | SMTP (konfigurierbar) | Registrierung, Passwort-Reset |
| KI | OpenRouter (optional, nicht MVP) | KI-Features |
### 5.2 Authentifizierung
- Token: `secrets.token_urlsafe(32)` (kryptografisch sicher)
- Hashing: bcrypt mit auto-Upgrade von Legacy SHA256
- Session-Ablauf: 30 Tage (konfigurierbar per `session_days`)
- Rate-Limiting: Login 30/min, Forgot-Password 3/min, Register 3/hour (slowapi)
- No-Enumeration: `/forgot-password` gibt keine Info über E-Mail-Existenz preis
### 5.3 Rollen (global)
| Rolle | Rechte |
|-------|--------|
| `trainer` | Standard-Nutzer; Upload, private Übungen, Planung |
| `admin` | Plattform-Admin; alle Vereine, alle Profile einsehbar |
| `superadmin` | Vollzugriff; Official-Promotion, physische Löschung, Admin-Konfiguration |
### 5.4 Vereinsrollen (pro Verein)
| Rolle | Rechte |
|-------|--------|
| `club_admin` | Vereinsstruktur, Mitglieder, Vereins-Medien/Übungen |
| `trainer` | Training planen, Übungen verwalten |
| `content_editor` | Inhalte bearbeiten |
| `division_lead` | Spartenleitung |
### 5.5 PWA / Service Worker
- **Kein Service Worker** im Repository vorhanden
- Keine Workbox- oder sw.js-Datei gefunden
- **Bedeutung:** Das Hauptrisiko (private Medien im PWA-Cache) entfällt mangels Service Worker
### 5.6 Browser-Storage-Nutzung
| Speicherart | Inhalt | TDDDG-Klassifikation |
|-------------|--------|----------------------|
| `localStorage['authToken']` | Auth-Session-Token | Technisch notwendig |
| `localStorage['shinkan_active_club']` | Aktiver Verein (ID) | Technisch notwendig |
| `localStorage['shinkan_active_profile']` | Profil-ID | Technisch notwendig |
| `sessionStorage[storageStepKey]` | Trainingsschritt (Coach-Page) | Session-temporär, nicht personenbezogen |
| `sessionStorage[storageDeltasKey]` | Trainingsdeltas JSON | Session-temporär |
| `sessionStorage[storageDebriefKey]` | Debrief-Status | Session-temporär |
| Cookies | **keine** | |
| IndexedDB | **keine** | |
---
## 6. Datenflussanalyse
### 6.1 Registrierung / Login
```
Nutzer → POST /api/auth/register → Profil (inaktiv) + Verifikations-E-Mail
Nutzer → E-Mail-Link → GET /api/auth/verify/{token}
→ Profil aktiv, Session-Token in Response
→ Frontend: localStorage.setItem('authToken', token)
Nutzer → POST /api/auth/login → Token in Response
→ Frontend: localStorage.setItem('authToken', token)
```
Gespeicherte Daten: Name, E-Mail, bcrypt-Hash, Rolle, Tier, trial_ends_at, email_verified, verification_token (temporär, wird nach Verifikation gelöscht)
### 6.2 Medienupload
```
Nutzer → POST /api/exercises/{id}/media (multipart) [50 MB Limit]
→ MIME-Type-Prüfung (magic bytes)
→ SHA256-Hash (Deduplizierung)
→ Dateispeicherung: library/{scope}/{kind}/{sha256}{ext}
→ DB-Eintrag: media_assets + exercise_media
Admin → POST /api/media-assets/bulk-upload [1 GB Limit]
→ gleicher Pfad; Sichtbarkeit + Verein als Formular-Parameter
```
### 6.3 Medienpromotion
```
Vereins-Admin → PATCH /api/media-assets/{id}
→ assert_valid_governance_visibility() → Mitgliedschaftsprüfung
→ Bei visibility=club: club_id Pflicht + Mitgliedschaft
→ Bei visibility=official: NUR Superadmin
→ copyright_notice: KEIN Pflichtfeld (nur im exercises-Router für official)
PROBLEM: Copyright-Pflicht ist NICHT im media_assets-Router für alle Promotions implementiert
```
### 6.4 Medienlöschung
```
Stufe 1 (Soft-Trash, lifecycle_state='trash_soft'):
→ Manuell durch Eigentümer / Vereins-Admin / Superadmin
→ Datei bleibt auf Disk; weiterhin sichtbar (je nach Exercise-Implementierung)
Stufe 2 (Hidden, lifecycle_state='trash_hidden'):
→ Nach 30 Tagen (Job) oder manuell
→ Nicht mehr in normalen Abfragen sichtbar
Stufe 3 (Purge):
→ Nach weiteren 30 Tagen (Job) oder Superadmin manuell
→ Datei physisch gelöscht
PROBLEM: media_retention_job.py ist NICHT automatisch geplant
```
### 6.5 Rechteprüfung
```
Jeder Request → require_auth() → Token aus X-Auth-Token-Header → Session aus DB
Vereinsdaten → get_tenant_context() → TenantContext (profile_id, role, effective_club_id)
Listenabfragen → library_content_visibility_sql() → SQL WHERE-Baustein
Schreibzugriffe → assert_valid_governance_visibility() → 403 bei Verstoß
```
---
## 7. Rollen- und Rechteanalyse
### 7.1 Mandantentrennung Stärken
- `TenantContext` konsequent in allen vereinsrelevanten Routern via `Depends(get_tenant_context)`
- `library_content_visibility_sql()` als zentraler Sichtbarkeits-Filter (SQL-Ebene)
- `effective_club_id` aus Header nur für Mitglieder, beliebig nur für Plattform-Admins
- Integrationstests vorhanden: `test_access_layer_integration.py`
### 7.2 Klarstellung: Wer kann Vereinsmedien bearbeiten?
Die Audit-Anforderung „alle Vereinsnutzer können bearbeiten" trifft auf die tatsächliche Implementierung **nicht** zu. In `_item_permissions()` (media_assets.py) ist `edit_metadata` nur für `club_admin`-Rolle oder Plattform-Admin True normale Mitglieder können Vereinsmedien nicht bearbeiten. **Dies ist ein positiver Befund.**
### 7.3 Profil-Löschung (DSGVO-Lücke)
`DELETE /api/profiles/{pid}` nur Plattform-Admin. Nutzer können ihr eigenes Konto **nicht** selbst löschen. Potenzielle DSGVO-Verletzung (Art. 17).
---
## 8. Medienrechteanalyse
### 8.1 Copyright-Feld
- Vorhanden: `copyright_notice` (max. 8000 Zeichen) in `media_assets`
- Pflichtfeld bei `exercise_media` mit `visibility='official'` (exercises-Router)
- **NICHT** Pflichtfeld beim direkten Upload in das Medienarchiv
- **NICHT** Pflichtfeld bei Promotion von `private` zu `club`
- **NICHT** dokumentiert: Wer hat erklärt? Wann? Welche Lizenzversion?
### 8.2 Rechteerklärung beim Upload
- Keine Einwilligungserklärung beim Upload: „Ich bestätige, alle Rechte an dieser Datei zu besitzen"
- Kein Upload-Dialog mit Pflicht-Checkbox
- Kein Hinweis auf verbotene Inhalte (Rechte Dritter, Persönlichkeitsrechte)
### 8.3 Recht am eigenen Bild
- Keine Abfrage, ob erkennbare Personen abgebildet sind
- Keine Abfrage, ob Minderjährige enthalten sind
- Keine Abfrage nach Einwilligung der abgebildeten Personen
- Juristisch zu prüfen: Anforderungen nach §22 KUG
---
## 9. Löschkonzeptanalyse
### 9.1 Stärken
- Klares 3-Stufen-Lifecycle-Modell (active → trash_soft → trash_hidden → purged)
- Superadmin-Direktlöschung als Sofortmaßnahme
- SHA256-Deduplizierung verhindert doppelte physische Dateien
- Datei-Relokation bei Sichtbarkeitsänderung implementiert
### 9.2 Lücken
| Problem | Risiko |
|---------|--------|
| Papierkorb-Job nicht automatisch geplant | Dateien bleiben physisch nach Ablauf der Fristen |
| Keine Löschung aus Backups dokumentiert | DSGVO Art. 17: Backup-Retention oder Löschprozess nötig |
| Kein Legal-Hold-Status | Bei Rechtsverletzung dauert es 30 Tage bis zur vollständigen Unsichtbarkeit |
| Kein Audit-Log für Löschgründe | Keine Nachvollziehbarkeit für DSA/DSGVO |
| Kein Uploader-Benachrichtigungssystem | Bei Sperrung / Löschung kein Feedback an Uploader |
---
## 10. PWA- / Storage-Analyse
### 10.1 Positiv
- Kein Service Worker → kein PWA-Cache-Risiko für Medien
- Keine Cookies → kein Cookie-Banner nötig (für Cookies)
- CSP-Header gesetzt: `script-src 'self'` (XSS-Mitigation)
### 10.2 LocalStorage-Bewertung
Die localStorage-Nutzung ist technisch notwendig (Auth, Mandantenkontext). Nach TDDDG §25 ist technisch notwendige Speicherung ohne Einwilligung zulässig. Dokumentation in der Datenschutzerklärung ist Pflicht.
### 10.3 Token-Sicherheit
- Auth-Token in `localStorage`: vulnerabel bei XSS
- CSP `script-src 'self'` reduziert XSS-Risiko erheblich
- Kein CSRF-Problem (Token im Header, nicht in Cookie)
- `HttpOnly`-Cookie wäre sicherer, erfordert Architekturanpassung
---
## 11. Datenschutzanalyse (DSGVO)
### 11.1 Identifizierte Verarbeitungsvorgänge
| Vorgang | Rechtsgrundlage (technisch) | VVT-Status |
|---------|----------------------------|-----------|
| Registrierung (Name, E-Mail, Passwort-Hash) | Vertrag (Art. 6 Abs. 1 lit. b) | ❌ Kein VVT |
| Login / Session-Management | Berechtigtes Interesse | ❌ Kein VVT |
| E-Mail-Versand | Vertragserfüllung | ❌ Kein VVT, SMTP-Anbieter unbekannt |
| Medienupload (Bilder/Videos) | Einwilligung oder Vertragserfüllung | ❌ Keine Einwilligung abgeholt |
| Vereinszugehörigkeit | Vertragserfüllung | ❌ Kein VVT |
| Training-Logging | Berechtigtes Interesse | ❌ Kein VVT |
| Backup (implizit) | Berechtigtes Interesse | ❌ Keine Retention dokumentiert |
### 11.2 Betroffenenrechte
| Recht | Status |
|-------|--------|
| Auskunft (Art. 15) | ❌ Kein Self-Service-Export |
| Berichtigung (Art. 16) | ⚠ Nur eigener Name/E-Mail über Einstellungen |
| Löschung (Art. 17) | ❌ Kein Self-Service-Löschung |
| Einschränkung (Art. 18) | ❌ Nicht implementiert |
| Datenübertragbarkeit (Art. 20) | ❌ Kein Export-Endpoint |
| Widerspruch (Art. 21) | ❌ Kein Mechanismus |
### 11.3 Auftragsverarbeiter (identifiziert)
| Dienst | Anbieter | AV-Vertrag |
|--------|----------|-----------|
| Hosting | Selbstbetrieb (Raspberry Pi) | Entfällt |
| SMTP / E-Mail | Unbekannt (Env-Variable) | ❌ Nicht dokumentiert |
| MediaWiki-Import | karatetrainer.net | ❌ Nicht dokumentiert |
| OpenRouter (KI) | OpenRouter.ai | ❌ Nicht dokumentiert |
### 11.4 Minderjährige
- Keine Altersverifikation bei Registrierung
- Keine besondere Schutzmaßnahme
- Juristisch zu prüfen: §8 DSGVO
---
## 12. DSA-/UGC-Analyse
### 12.1 Einordnung
Die App erlaubt Upload von User Generated Content (Bilder, Videos). Inhalte können öffentlich sichtbar sein (`official`-Stufe: plattformweit). Dies ist UGC im Sinne des DSA.
**Juristisch zu prüfen:** Ab welcher Nutzerzahl und unter welchen Voraussetzungen der DSA für diese Plattform gilt.
### 12.2 Fehlende Mechanismen
| DSA-Anforderung | Status |
|-----------------|--------|
| Meldeverfahren für rechtswidrige Inhalte | ❌ |
| „Inhalt melden"-Funktion | ❌ |
| Moderations-Backend mit Statuswerten | ❌ |
| Benachrichtigung des Uploaders bei Sperrung | ❌ |
| Begründung für Moderationsentscheidungen | ❌ |
| Beschwerdemechanismus | ❌ |
| Eskalation für schwere Inhalte (CSAM, Straftaten) | ❌ |
| Audit-Log für Meldungen und Entscheidungen | ❌ |
### 12.3 Was vorhanden ist (Notfall-Maßnahmen)
- Superadmin kann Inhalte sofort physisch löschen (`superadmin_hard_delete`)
- Lifecycle-System ermöglicht schrittweise Deaktivierung
- `official`-Promotion nur durch Superadmin (redaktioneller Prozess)
---
## 13. Sicherheitsanalyse
### 13.1 Positiv bewertete Maßnahmen
| Maßnahme | Status |
|----------|--------|
| HTTPS (Produktion via Reverse-Proxy) | ✓ |
| bcrypt Passwort-Hashing mit Legacy-SHA256-Upgrade | ✓ |
| Rate-Limiting (slowapi) | ✓ |
| CSRF-Schutz (Token im Header, nicht Cookie) | ✓ |
| SQL-Injection-Schutz (parameterisierte Queries) | ✓ |
| CSP-Header (nginx) | ✓ |
| X-Content-Type-Options: nosniff (nginx + FastAPI-Middleware) | ✓ |
| X-Frame-Options: SAMEORIGIN | ✓ |
| Referrer-Policy: strict-origin-when-cross-origin | ✓ |
| Permissions-Policy (camera/mic/geo) | ✓ |
| OpenAPI in Produktion deaktiviert | ✓ |
| DB-Port nur localhost exponiert | ✓ |
| MIME-Type-Validierung beim Upload | ✓ |
| SHA256-Integritätsprüfung + Deduplizierung | ✓ |
| Secrets in .env (nicht im Code) | ✓ |
| User-Enumeration verhindert (forgot-password, resend-verification) | ✓ |
| Path-Traversal-Schutz in media_storage.py (`path_under_media_root` + `.relative_to()`) | ✓ |
| Club-Name-Slugify: nur `[a-z0-9-]` im Dateipfad | ✓ |
| CORS: Origins eingeschränkt (ALLOWED_ORIGINS aus Env) | ✓ |
### 13.2 Sicherheitslücken
| ID | Titel | Schwere | Datei/Nachweis |
|----|-------|---------|----------------|
| SEC-01 | Kein HSTS-Header | Hoch | `frontend/nginx.conf` kein `Strict-Transport-Security` |
| SEC-02 | Auth-Token in localStorage | Mittel | `frontend/src/context/AuthContext.jsx:47` |
| SEC-03 | `style-src 'unsafe-inline'` in CSP | Niedrig | `frontend/nginx.conf:23` |
| SEC-04 | Passwort-Mindestlänge inkonsistent: Backend 3 Stellen, Frontend-Feld minLength=6, Backend-Register-Minimum=8 | Mittel | `backend/routers/auth.py:104` (`< 4`), `frontend/src/pages/LoginPage.jsx:175` (`minLength="6"`) |
| SEC-05 | ALLOW_PUBLIC_MEDIA_STATIC umgeht Auth für alle Medien | Hoch | `backend/main.py:222-223` |
| SEC-06 | Kein MFA für Superadmins | Mittel | Kein TOTP/OTP implementiert |
| SEC-07 | Kein Audit-Log für Admin-Aktionen | Mittel | Keine `admin_audit_log`-Tabelle |
| SEC-08 | Password-Reset-Token in sessions-Tabelle (Präfix `reset_`) | Niedrig | `backend/routers/auth.py:143` |
| SEC-09 | Kein Backup-Konzept dokumentiert | Mittel | Kein Backup-Skript im Repo |
| SEC-10 | Kein Anti-Virus-Scan für Uploads | Niedrig | Kein ClamAV o.ä. |
| SEC-11 | Kein genereller HTML-Sanitizer für Rich-Text-Felder | Mittel | `backend/exercise_rich_text.py` nur Inline-Media-Normalisierung, kein bleach/nh3 |
| SEC-12 | `minLength="6"` im Login-Formular, Backend fordert 8 Zeichen | Niedrig | `frontend/src/pages/LoginPage.jsx:175` |
| SEC-13 | Hartcodierte Versionsangabe `v0.1.0 • Development` auf Login-Seite (falsch + Info-Leak) | Niedrig | `frontend/src/pages/LoginPage.jsx:242` |
| SEC-14 | CORS: `allow_methods=["*"]` und `allow_headers=["*"]` breiter als nötig | Niedrig | `backend/main.py:84-87` |
### 13.3 Ergänzende Befunde aus Restprüfung
#### main.py — CORS-Konfiguration
```python
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS, # ✓ aus Env, keine Wildcard-Origins
allow_credentials=True, # ✓ korrekt (kein * + credentials)
allow_methods=["*"], # ⚠ breiter als nötig
allow_headers=["*"], # ⚠ breiter als nötig
)
```
`allow_credentials=True` in Kombination mit `allow_origins=["*"]` wäre ein kritischer Fehler (FastAPI würde ihn aber abweisen). Durch die explizite Origin-Liste ist das Risiko gering. `allow_methods=["*"]` und `allow_headers=["*"]` könnten auf die tatsächlich benötigten Methoden (GET, POST, PUT, PATCH, DELETE) und Header (X-Auth-Token, X-Active-Club-Id, Content-Type) eingeschränkt werden.
#### media_storage.py — Path-Traversal-Schutz (positiv)
`path_under_media_root()` kombiniert zwei unabhängige Prüfungen:
1. String-Prüfung: `".." in key.split("/")`
2. Filesystem-Prüfung: `p.relative_to(media_root.resolve())`
Die Dateiendung wird auf 16 Zeichen begrenzt. Club-Namen werden auf `[a-z0-9-]` normalisiert. SHA256 als Dateiname ist manipulationssicher. **Bewertung: Gut implementiert, kein Path-Traversal-Risiko erkennbar.**
#### exercise_rich_text.py — Fehlender genereller HTML-Sanitizer
Das Modul normalisiert ausschließlich das Inline-Media-Markup (`{{exerciseMedia:id}}` `<span>`). Es enthält **keinen** generellen HTML-Sanitizer (kein bleach, lxml-cleaner, nh3 o.ä.).
Felder in `RICH_HTML_EXERCISE_FIELDS` (`summary`, `goal`, `execution`, `preparation`, `trainer_notes`) können beliebiges HTML enthalten. Risikominderung:
- CSP `script-src 'self'` verhindert `<script>`-Ausführung
- React's `dangerouslySetInnerHTML` muss im Frontend für XSS genutzt werden zu prüfen
- Betroffene Felder sind nur für eingeloggte Nutzer sichtbar (kein öffentlicher Angriffspfad)
**Empfehlung:** bleach oder nh3 für Allowlist-basierte HTML-Sanitierung einsetzen.
#### LoginPage.jsx — Weitere Befunde
1. **Keine Rechtstexte-Links:** Kein Link auf Impressum oder Datenschutzerklärung (bestätigt KRIT-01)
2. **`minLength="6"`:** Browser-seitig 6 Zeichen, Backend erzwingt 8 UX-Bruch, Nutzer sieht kein Frontend-Feedback
3. **Hartcodierter Versionsstring:** `v0.1.0 • Development` ist öffentlich sichtbar, falsch (App ist 0.8.65) und leakt Umgebungsinfo
### 13.5 Warnung: ALLOW_PUBLIC_MEDIA_STATIC (SEC-05)
Das Flag `ALLOW_PUBLIC_MEDIA_STATIC=1` würde bei Aktivierung alle Mediendateien ohne Auth unter `/media/` ausliefern und das gesamte Sichtbarkeitskonzept (privat, Verein, offiziell) für alle gespeicherten Mediendateien unterlaufen. Bestätigt in `backend/main.py:222-223`:
```python
if os.getenv("ALLOW_PUBLIC_MEDIA_STATIC", "").strip().lower() in ("1", "true", "yes"):
app.mount("/media", StaticFiles(directory=_media_dir), name="media")
```
**Dieses Flag darf in Produktionsumgebungen niemals gesetzt sein.** Muss in der Betriebsdokumentation explizit verboten und per Release-Checkliste überprüft werden.
---
## 14. Dokumentationsanalyse
### 14.1 Vorhandene technische Dokumentation
- `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`
- `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`
- `backend/version.py` mit CHANGELOG
- `backend/scripts/check_access_layer_hints.py`
### 14.2 Fehlende rechtliche Dokumentation
| Dokument | Status (Audit 2026-05-09) | Stand 2026-05-10 |
|----------|--------------------------|------------------|
| Impressum | Keine Seite, kein Inhalt, keine Route | Route + Platzhalter vorhanden; Inhalt durch Betreiber/Rechtsanwalt ausstehend |
| Datenschutzerklärung | | Route + Platzhalter vorhanden; Inhalt ausstehend |
| Nutzungsbedingungen / AGB | | Route + Platzhalter vorhanden; Inhalt ausstehend |
| Medienrichtlinie / Community-Regeln | | Route + Platzhalter vorhanden; Inhalt ausstehend |
| Verzeichnis der Verarbeitungstätigkeiten (VVT) | | offen (Betreiber-Aufgabe) |
| AV-Verträge mit Auftragsverarbeitern | | offen (Betreiber-Aufgabe) |
| Backup-Konzept schriftlich | | offen (Betreiber-Aufgabe) |
> **Amendment 2026-05-10:** Routen und Platzhalterseiten implementiert (P-01, 0.8.69); Mobile/PWA via `/settings/legal` (P-01b, 0.8.70); Admin-konfigurierbare Rechtstexte mit DB-Versionierung, Superadmin-UI, PDF-Download, Abschnitts-Editor (P-01c, 0.8.710.8.74). KRIT-01 bleibt offen bis zur Veröffentlichung juristisch geprüfter Inhalte.
### 14.3 Fehlende Frontend-Routen
In `frontend/src/App.jsx` fehlen (Stand Audit 2026-05-09):
- `/impressum`
- `/datenschutz`
- `/nutzungsbedingungen`
- `/medienrichtlinie`
Alle müssen **vor der Authentifizierung** erreichbar und in der PWA verfügbar sein.
> **Amendment 2026-05-10 (P-01, Version 0.8.69):** Alle vier Routen sind implementiert und ohne Auth erreichbar. Links in LoginPage-Footer, DesktopSidebar-Footer und `/settings/legal` (P-01b). Migrationsnummer der Datenbank: bis 046 geprüft (Audit-Scope). Migration 047 (legal_documents) wurde außerhalb des Audit-Zeitfensters angelegt — siehe `docs/compliance-implementation.md` §P-01c.
---
## 15. Testanalyse
### 15.1 Vorhandene Tests
| Test-Datei | Inhalt |
|-----------|--------|
| `test_access_layer.py` | Sichtbarkeits-SQL-Logik |
| `test_access_layer_integration.py` | Cross-Tenant-Isolation mit echter DB |
| `test_exercises_delete_policy.py` | DELETE-Logik |
| `test_exercise_media_download.py` | Download-Zugriffsschutz |
| `test_official_exercise_media_rules.py` | Official-Promotion + Copyright |
| `test_media_assets_archive.py` | Deduplizierung, Lifecycle |
| `test_club_exercise_media_copyright.py` | Copyright bei Vereinsübungen |
| `test_exercise_inline_post.py` | Inline-Medien-Validierung |
| `test_exercise_rich_text.py` | HTML-Sanitizer |
| `test_security_release.py` | OpenAPI/Health in Produktion |
| `test_profiles_read_access.py` | Profil-Zugriffsrechte |
### 15.2 Testlücken
| Bereich | Status |
|---------|--------|
| DSA-Meldeverfahren | Funktion fehlt |
| DSGVO-Betroffenenrechte (Löschung, Export) | Funktion fehlt |
| Minderjährigen-Check beim Upload | Funktion fehlt |
| Einwilligungs-Check beim Upload | Funktion fehlt |
| ALLOW_PUBLIC_MEDIA_STATIC=1 aktiviert | Nicht getestet |
| Admin-Audit-Log | Funktion fehlt |
| Passwort-Mindestlänge bei PIN-Änderung | Nicht getestet |
| Copyright-Pflicht bei Archiv-Promotion | Nicht getestet |
---
## 16. Risiko-Matrix
### KRITISCH
| ID | Titel | Beschreibung | Betroffene Dateien | Juristisch prüfen | Aufwand |
|----|-------|-------------|-------------------|-------------------|---------|
| KRIT-01 | Keine Rechtstexte | Keine Impressum-, Datenschutz-, AGB- oder Medienrichtlinien-Seite. Öffentlich erreichbare App ohne Impressum ist Ordnungswidrigkeit (TMG §5, TDDDG). | `frontend/src/App.jsx` keine Routen | Ja | 25 Tage (Technik) + Rechtsanwalt |
| KRIT-02 | Kein Self-Service-Löschrecht | Nutzer können ihr Konto nicht selbst löschen (DSGVO Art. 17). Nur Plattform-Admin kann Konten löschen. | `backend/routers/profiles.py:414` | Ja | 58 Tage |
| KRIT-03 | Kein DSA-Meldeverfahren | Keine Möglichkeit, rechtswidrige Inhalte zu melden. Kein Moderationssystem für UGC-Plattform. | Kein Router vorhanden | Ja | 1020 Tage |
| KRIT-04 | Kein Recht-am-eigenen-Bild-Check | Keine Abfrage bei Medienupload, ob Personen oder Minderjährige abgebildet sind. | `backend/routers/exercises.py`, `backend/routers/media_assets.py` | Ja | 37 Tage |
| KRIT-05 | Kein DSGVO-Self-Service (Auskunft, Export) | Keine Datenauskunft, kein Datenexport, keine Berichtigungsmöglichkeit (DSGVO Art. 15, 16, 20). | Kein Endpoint | Ja | 510 Tage |
| KRIT-06 | Copyright-Pflicht inkonsistent | Copyright ist Pflichtfeld nur im exercises-Router für `official`. Im media_assets-Router (Archiv) kann ohne Copyright zu `club`/`official` promoted werden. | `backend/routers/media_assets.py:766784` | Ja | 12 Tage |
| KRIT-07 | Papierkorb-Job nicht geplant | `media_retention_job.py` existiert, aber kein Cron-Job konfiguriert. Medien bleiben physisch nach Ablauf der DSGVO-Fristen auf dem Server. | `backend/media_retention_job.py` | Nein | 1 Tag |
### HOCH
| ID | Titel | Beschreibung | Aufwand |
|----|-------|-------------|---------|
| HOCH-01 | ALLOW_PUBLIC_MEDIA_STATIC | Wenn gesetzt, sind alle Mediendateien ohne Auth abrufbar untergräbt gesamtes Sichtbarkeitskonzept | Dokumentation + Test |
| HOCH-02 | Kein HSTS | Kein `Strict-Transport-Security`-Header in nginx.conf | Dokumentation (Reverse-Proxy) |
| HOCH-03 | Auth-Token in localStorage | XSS könnte Token extrahieren; CSP reduziert, eliminiert nicht | Dokumentation (HttpOnly als Langfrist-Plan) |
| HOCH-04 | Kein MFA für Superadmins | Superadmin hat unbegrenzten Systemzugriff ohne zweiten Faktor | 58 Tage |
| HOCH-05 | Kein Admin-Audit-Log | Profil-Löschungen, Lifecycle-Aktionen nicht geloggt | 35 Tage |
| HOCH-06 | Keine Mindestalter-Abfrage | Keine Schranke gegen Registrierung Minderjähriger | 12 Tage |
### MITTEL
| ID | Titel | Beschreibung | Aufwand |
|----|-------|-------------|---------|
| MITT-01 | Passwort-Mindestlänge inkonsistent | Register: 8 Zeichen, `PUT /api/auth/pin`: 4 Zeichen | 30 Min |
| MITT-02 | Keine Sofortsperrung bei Rechtsverletzung | Stufe-1-Papierkorb dauert 30 Tage bis zur vollständigen Unsichtbarkeit | 23 Tage |
| MITT-03 | Kein VVT | Kein Verzeichnis der Verarbeitungstätigkeiten (DSGVO Art. 30) | Betreiber |
| MITT-04 | SMTP-Anbieter unbekannt | Kein AV-Vertrag; E-Mail-Dienstleister nicht dokumentiert | Betreiber |
| MITT-05 | sessionStorage nicht bei Logout bereinigt | TrainingCoachPage-Fortschritt bleibt nach Logout im sessionStorage | 0,5 Tage |
| MITT-06 | Keine Löschung aus Backups | DSGVO-Löschungsanfragen greifen nicht auf Backups | Betreiber (Policy) |
| MITT-07 | MediaWiki-Integration ohne AV-Vertrag | Datentransfer zu karatetrainer.net nicht dokumentiert | Betreiber |
| MITT-08 | OpenRouter ohne AV-Vertrag | Erst aktivieren wenn AV-Vertrag vorhanden | Betreiber |
### NIEDRIG
| ID | Titel | Beschreibung | Aufwand |
|----|-------|-------------|---------|
| NIED-01 | `style-src 'unsafe-inline'` | Inline-Styles in CSP erlaubt | Nonce/Hash |
| NIED-02 | Kein Anti-Virus-Scan | Malware-Dateien hochladbar | ClamAV-Integration |
| NIED-03 | Reset-Token in sessions-Tabelle | Token mit `reset_`-Präfix in Auth-Tabelle | Separate Tabelle |
| NIED-04 | SHA256-Hash in API-Response | Datei-Fingerprinting möglich | Response-Filterung |
| NIED-05 | Kein Passwort-Complexity-Check | Nur Mindestlänge geprüft | zxcvbn o.ä. |
| NIED-06 | `minLength="6"` im Login-Formular | Inkonsistent mit Backend (8 Zeichen); Browser lässt 6-7-char-Passwörter zu, Backend lehnt sie dann ab | `frontend/src/pages/LoginPage.jsx:175` |
| NIED-07 | Hartcodierter Versionsstring auf Login-Seite | `v0.1.0 • Development` sichtbar ohne Auth; falsche Version (0.8.65) + Umgebungsinfo | `frontend/src/pages/LoginPage.jsx:242` |
| NIED-08 | CORS allow_methods/headers=`["*"]` | Breiter als nötig; Origins sind korrekt eingeschränkt, aber Methoden/Header nicht | `backend/main.py:84-87` |
| NIED-09 | Kein genereller HTML-Sanitizer für Rich-Text | `exercise_rich_text.py` bereinigt nur Inline-Media-Markup; beliebiges HTML in `summary`, `goal`, `execution` etc. möglich (CSP schützt gegen Script-Execution) | `backend/exercise_rich_text.py` |
---
## 17. Umsetzungsplan
### Empfohlene Reihenfolge
**Etappe 1 Pflicht vor öffentlichem Betrieb (Kritische Blocker)**
| Paket-ID | Titel | Findings | Aufwand |
|----------|-------|---------|---------|
| P-01 | Rechtstexte (Seiten + Routen, Inhalte durch Rechtsanwalt) | KRIT-01 | 25 Tage Technik |
| P-02 | Self-Service-Kontolöschung + Datenexport | KRIT-02, KRIT-05 | 58 Tage |
| P-03 | Papierkorb-Retention-Job aktivieren | KRIT-07 | 1 Tag |
| P-04 | Copyright-Pflicht für Archiv-Promotion vereinheitlichen | KRIT-06 | 1 Tag |
| P-05 | Passwort-Mindestlänge angleichen | MITT-01 | 30 Min |
| P-06 | Upload-Einwilligungsdialog (Personen, Minderjährige, Rechte) | KRIT-04 | 24 Tage |
**Etappe 2 Sicherheit und Datenschutz (dringend empfohlen)**
| Paket-ID | Titel | Findings | Aufwand |
|----------|-------|---------|---------|
| P-07 | ALLOW_PUBLIC_MEDIA_STATIC dokumentieren + Test | HOCH-01, SEC-05 | 0,5 Tage |
| P-08 | HSTS und externe Proxy-Sicherheit dokumentieren | HOCH-02, SEC-01 | 0,5 Tage |
| P-09 | Admin-Audit-Log | HOCH-05, SEC-07 | 35 Tage |
| P-10 | Mindestalter-Abfrage | HOCH-06 | 12 Tage |
| P-11 | Legal-Hold Lifecycle-Status | MITT-02 | 23 Tage |
| P-12 | sessionStorage bei Logout bereinigen | MITT-05 | 0,5 Tage |
| P-22 | HTML-Sanitizer für Rich-Text-Felder (bleach/nh3) | NIED-09, SEC-11 | 12 Tage |
| P-23 | LoginPage: minLength angleichen + Version entfernen | NIED-06, NIED-07, SEC-12, SEC-13 | 1 Stunde |
| P-24 | CORS einschränken (Methoden + Header) | NIED-08, SEC-14 | 1 Stunde |
**Etappe 3 DSA-Meldeverfahren (mittelfristig, nach juristischer Klärung)**
| Paket-ID | Titel | Findings | Aufwand |
|----------|-------|---------|---------|
| P-13 | Content-Melde-Backend (content_reports-Tabelle + Endpoints) | KRIT-03 | 58 Tage |
| P-14 | Moderations-UI (Frontend) | KRIT-03 | 35 Tage |
| P-15 | Uploader-Benachrichtigung bei Sperrung | KRIT-03 | 12 Tage |
| P-16 | Beschwerdeverfahren | KRIT-03 | 24 Tage |
**Etappe 4 Langfristige Optimierungen**
| Paket-ID | Titel | Aufwand |
|----------|-------|---------|
| P-17 | MFA für Superadmins (TOTP) | 58 Tage |
| P-18 | HttpOnly-Cookie als Auth-Alternative | 35 Tage |
| P-19 | Anti-Virus-Scan (ClamAV) | 35 Tage |
| P-20 | VVT erstellen | Betreiber |
| P-21 | AV-Verträge abschließen | Betreiber |
---
### P-04 im Detail: Copyright-Pflicht vereinheitlichen
**Technische Änderung Backend:**
In `backend/routers/media_assets.py`, Funktionen `patch_media_asset()` und `bulk_media_patch()`:
- Bei Visibility-Wechsel zu `club` oder `official`: `copyright_notice` muss vorhanden und min. 3 Zeichen lang sein
- Fehler: HTTP 422 mit klarer Fehlermeldung
**Test:** Promotion ohne copyright_notice HTTP 422; mit copyright_notice OK
---
### P-05 im Detail: Passwort-Mindestlänge angleichen
**Technische Änderung:**
- `backend/routers/auth.py:104`: `if len(new_pin) < 8:` statt `< 4`
- Fehlermeldung: Passwort muss mindestens 8 Zeichen lang haben"
---
### P-13 im Detail: Content-Melde-Backend
**Neue DB-Migration (`backend/migrations/047_content_reports.sql`):**
```sql
CREATE TABLE content_reports (
id SERIAL PRIMARY KEY,
reporter_profile_id INTEGER REFERENCES profiles(id) ON DELETE SET NULL,
target_type VARCHAR(20) NOT NULL CHECK (target_type IN ('exercise', 'media_asset')),
target_id INTEGER NOT NULL,
reason VARCHAR(50) NOT NULL
CHECK (reason IN ('illegal_content', 'copyright', 'personal_rights',
'minor_protection', 'hate_speech', 'spam', 'other')),
description TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'under_review', 'resolved_removed',
'resolved_kept', 'rejected')),
moderator_profile_id INTEGER REFERENCES profiles(id) ON DELETE SET NULL,
moderator_note TEXT,
resolved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
**Neue Endpoints:**
- `POST /api/reports` Meldung einreichen (require_auth)
- `GET /api/admin/reports` Moderations-Queue (Admin/Superadmin)
- `PATCH /api/admin/reports/{id}` Status setzen + Notiz
---
## 18. Dauerhafter Auditplan
### 18.1 Audit-Frequenzen
| Typ | Frequenz |
|-----|----------|
| Mini-Audit | Bei jedem PR mit Compliance-Relevanz |
| Release-Audit | Vor jedem Produktions-Deployment |
| Quartalsaudit | Alle 3 Monate |
| Jahresaudit | Einmal jährlich (mit Rechtsanwalt/Datenschutzbeauftragtem) |
### 18.2 Sonderaudit-Auslöser
- Neue öffentlich sichtbare Features
- Neue Upload-Funktionen (Medientypen, Quellen)
- Neue Rollen oder Rechteänderungen
- Neue externe Dienstleister
- Einführung von Analytics oder Tracking
- Änderungen am Minderjährigenschutz
- Sicherheitsvorfall oder Datenpanne
- Datenschutzanfrage eines Nutzers
- Meldung rechtswidriger Inhalte
- Größere Architekturänderungen
- Gesetzesänderungen (DSGVO, DSA, TDDDG)
### 18.3 Checkliste: Mini-Audit bei Feature-PR
```
[ ] Betrifft der PR personenbezogene Daten? → DSGVO-Check
[ ] Betrifft der PR Medienupload oder -anzeige? → Rechte-Check + DSA-Check
[ ] Betrifft der PR Rollen oder Sichtbarkeit? → Access-Layer-Check
[ ] Betrifft der PR Storage oder Caching? → TDDDG-Check
[ ] Betrifft der PR externe Dienste? → AV-Vertrag prüfen
[ ] Betrifft der PR Auth oder Session? → Sicherheits-Check
[ ] Neuer Endpoint → require_auth / get_tenant_context vorhanden?
[ ] Neue Datenspalte → Migration + Lösch-Cascade korrekt?
[ ] Neue Frontend-Speicherung → TDDDG-Klassifikation dokumentiert?
[ ] ACCESS_LAYER: python backend/scripts/check_access_layer_hints.py
```
### 18.4 Checkliste: Release-Audit
```
[ ] Versions-Bump (backend/version.py + frontend/src/version.js)
[ ] ALLOW_PUBLIC_MEDIA_STATIC nicht in Prod-Env gesetzt
[ ] OpenAPI/Swagger nicht öffentlich (ENVIRONMENT=production)
[ ] HSTS am externen Reverse-Proxy konfiguriert (manuell prüfen)
[ ] Papierkorb-Retention-Job läuft (Logs prüfen)
[ ] SSL-Zertifikat gültig
[ ] DB-Backup der letzten 24h vorhanden
[ ] Rechtstexte aktuell (kein Placeholder, kein veraltetes Datum)
[ ] pytest -m "not slow" grün
[ ] ACCESS_LAYER_INTEGRATION=1 pytest tests/test_access_layer_integration.py grün
[ ] test_security_release.py grün
[ ] pip-audit (keine kritischen CVEs)
[ ] npm audit --audit-level=high (keine kritischen CVEs)
```
### 18.5 Checkliste: Neues Feature
```
[ ] Verarbeitet das Feature personenbezogene Daten? → VVT aktualisieren
[ ] Neue DB-Tabellen → Lösch-Cascade und Retention definiert?
[ ] Neue API-Endpoints → Access Layer korrekt?
[ ] Feature mit UGC? → DSA-Meldeverfahren abgedeckt?
[ ] Feature mit Medien? → Lifecycle, Sichtbarkeit, Copyright?
[ ] Feature für Minderjährige relevant? → Schutzmaßnahmen?
[ ] Neue localStorage/sessionStorage-Nutzung → TDDDG + Datenschutzerklärung?
[ ] Neue externe Abhängigkeit → AV-Vertrag?
[ ] Keine sensitiven Daten in Fehlerausgaben?
[ ] api.js für alle Frontend API-Calls?
[ ] Tests geschrieben und grün?
```
### 18.6 Checkliste: Änderung am Medienmodell
```
[ ] Neue Medientypen → MIME-Type-Validierung aktualisiert?
[ ] Neue Sichtbarkeitsstufen → library_content_visibility_sql() aktualisiert?
[ ] Lifecycle-Änderungen → Papierkorb-Job angepasst?
[ ] Copyright-Anforderungen konsistent für alle Promotions-Wege?
[ ] ALLOW_PUBLIC_MEDIA_STATIC explizit: In Prod verboten (dokumentiert)?
[ ] Neue Download-Endpoints → require_auth_flexible implementiert?
[ ] Recht-am-eigenen-Bild-Abfragen aktualisiert?
```
### 18.7 Checkliste: Änderung am Rollenmodell
```
[ ] Neue Rollen → assert_valid_governance_visibility() berücksichtigt?
[ ] EXEMPT-Liste in check_access_layer_hints.py aktualisiert?
[ ] test_access_layer*.py angepasst?
[ ] MFA-Anforderung für neue Admin-Rollen geprüft?
[ ] Audit-Log für neue Aktionen?
```
### 18.8 Pflichttests vor Release
```bash
# Zugriffskontrolle
pytest backend/tests/test_access_layer.py -v
# Cross-Tenant-Integration (mit PostgreSQL)
ACCESS_LAYER_INTEGRATION=1 pytest backend/tests/test_access_layer_integration.py -v
# Mediensicherheit
pytest backend/tests/test_exercise_media_download.py -v
pytest backend/tests/test_official_exercise_media_rules.py -v
pytest backend/tests/test_media_assets_archive.py -v
pytest backend/tests/test_club_exercise_media_copyright.py -v
# Sicherheits-Release-Checks
pytest backend/tests/test_security_release.py -v
# Access Layer Script (Strict Mode)
ACCESS_LAYER_STRICT=1 python backend/scripts/check_access_layer_hints.py
# Abhängigkeits-Scan
cd backend && pip-audit
cd frontend && npm audit --audit-level=high
```
### 18.9 Vorlage: Quartalsaudit-Bericht
```markdown
# Compliance-Quartalsaudit Q[X]/[Jahr]
Datum: [DATUM] | Auditor: [NAME]
## 1. Status offener Findings
[Tabelle: ID Titel Status]
## 2. Neue Findings dieser Periode
[Tabelle: ID Titel Schwere Aufwand]
## 3. Rechtstexte
[ ] Impressum geprüft: [DATUM]
[ ] Datenschutzerklärung geprüft: [DATUM]
[ ] AGB/Nutzungsbedingungen geprüft: [DATUM]
[ ] Medienrichtlinie geprüft: [DATUM]
## 4. DSGVO-Status
[ ] VVT aktuell
[ ] AV-Verträge vollständig
[ ] Backup-Retention-Policy dokumentiert
[ ] Betroffenenrechte-Mechanismen getestet
## 5. DSA-Status
[ ] Meldeverfahren Test-Meldung durchgeführt: [DATUM]
[ ] Offene Meldungen: [X] (alle bearbeitet: Ja/Nein)
[ ] Eskalationspfad dokumentiert: Ja/Nein
## 6. Technische Sicherheit
[ ] Abhängigkeits-Scan: [DATUM] Kritische CVEs: [X]
[ ] SSL gültig bis: [DATUM]
[ ] HSTS konfiguriert: Ja/Nein
[ ] Alle Pflichttests grün: Ja/Nein
## 7. Empfehlungen
[Liste]
```
---
## 19. Entscheidungsvorlage
### 19.1 Was ist aktuell kritisch?
1. Keine Rechtstexte App ist öffentlich ohne Impressum (Ordnungswidrigkeit)
2. Kein Löschrecht für Nutzer DSGVO Art. 17 verletzt
3. Kein DSA-Meldeverfahren bei UGC-Plattform möglicherweise Pflicht
4. Copyright-Lücke Archiv-Upload ohne Copyright-Pflicht bei Promotion
5. Kein Recht-am-eigenen-Bild-Check bei Upload
### 19.2 Was blockiert sicheren öffentlichen Betrieb (technisch)?
1. KRIT-01: Rechtstexte fehlen vollständig
2. KRIT-02: Kein Self-Service-Löschrecht
3. KRIT-07: Papierkorb-Job läuft nicht automatisch
4. HOCH-01: ALLOW_PUBLIC_MEDIA_STATIC muss in Prod-Doku explizit verboten sein
5. HOCH-02: HSTS am externen Reverse-Proxy überprüfen
### 19.3 Mindest-Paket vor erstem öffentlichem Betrieb
- P-05 (30 Min): Passwort-Mindestlänge angleichen
- P-04 (1 Tag): Copyright-Pflicht vereinheitlichen
- P-03 (1 Tag): Papierkorb-Job aktivieren
- P-06 (2 Tage): Upload-Einwilligungsdialog
- P-01 (2 Tage Technik + Rechtsanwalt): Rechtstexte-Seiten
- P-02 (5 Tage): Self-Service-Kontolöschung
### 19.4 Was kann zurückgestellt werden?
- P-17 (HttpOnly-Cookie): Größere Architekturänderung, kein unmittelbarer Compliance-Bedarf
- P-19 (Anti-Virus-Scan): Hoher Aufwand, Risiko bei lokalem Storage gering
- P-13 bis P-16 (DSA-Meldeverfahren): Erst juristisch klären ob und in welchem Umfang erforderlich
### 19.5 Juristisch zu prüfende Fragen
| Thema | Frage |
|-------|-------|
| DSGVO Art. 17 | Wie muss Löschung aus Backups gehandhabt werden? |
| DSA | Ab welcher Nutzerzahl gilt der DSA? Welche Pflichten für Kleinplattformen? |
| §22 KUG | Welche Einwilligungen für Personenbilder im Vereinskontext? |
| §8 DSGVO | Mindestalter für die Registrierung? |
| TDDDG §25 | Muss für localStorage eine Einwilligung eingeholt werden? |
| Impressum-Pflicht | Vollständige Angaben des Verantwortlichen? |
| AV-Verträge | Welche Dienstleister benötigen einen AVV? |
| MediaWiki | Lizenzanforderungen für Übungsinhalte aus karatetrainer.net? |
### 19.6 Organisatorische Aufgaben für den Betreiber
1. Rechtsanwalt beauftragen (Rechtstexte)
2. VVT erstellen (DSGVO Art. 30)
3. AV-Verträge abschließen (SMTP-Anbieter, ggf. MediaWiki)
4. Backup-Prozess dokumentieren und Restore-Tests durchführen
5. Moderationsprozess definieren
6. Notfallkontakt für Datenpannen benennen
7. HSTS am externen Reverse-Proxy sicherstellen
8. Papierkorb-Job-Monitoring einrichten
### 19.7 Verbleibende Risiken nach technischer Umsetzung
- HSTS liegt außerhalb des Repos (Betreiber-Verantwortung)
- Backup-Löschung bei DSGVO-Anfragen erfordert manuelle Prozesse
- Minderjährige können Altersangabe fälschen (kein verlässlicher Online-Altersnachweis)
- Rechtswidrige Inhalte können zwischen Upload und Moderationsentscheidung sichtbar sein
- SMTP-Anbieter kann E-Mail-Inhalte verarbeiten
### 19.8 Freigabe-Formulierungen
Verwende diese exakten Formulierungen zur Freigabe einzelner Pakete:
| Paket | Freigabe-Formulierung |
|-------|----------------------|
| P-05 | Freigabe zur Umsetzung P-05: Passwort-Mindestlänge angleichen" |
| P-04 | Freigabe zur Umsetzung P-04: Copyright-Pflicht vereinheitlichen" |
| P-03 | Freigabe zur Umsetzung P-03: Papierkorb-Retention-Job aktivieren" |
| P-06 | Freigabe zur Umsetzung P-06: Upload-Einwilligungsdialog" |
| P-01 | Freigabe zur Umsetzung P-01: Rechtstexte-Seiten technisch anlegen" |
| P-02 | Freigabe zur Umsetzung P-02: Self-Service-Kontolöschung und Datenexport" |
| P-09 | Freigabe zur Umsetzung P-09: Admin-Audit-Log" |
| P-13 | Freigabe zur Umsetzung P-13: Content-Melde-Backend" |
| Etappe 1 komplett | Freigabe zur Umsetzung Etappe 1" |
| Etappe 1+2 | Freigabe zur Umsetzung Etappen 1 und 2" |
---
**Audit abgeschlossen. Keine Codeänderungen vorgenommen. Umsetzung erst nach ausdrücklicher Freigabe.**
---
## 20. Regel zur Paket-ID-Stabilität
> Ergänzt: 2026-05-10 — Kanonisches Referenzdokument: `docs/compliance-package-register.md`
### 20.1 Grundsatz
Paket-IDs (P-01 bis P-24 und alle künftigen) werden nach ihrer ersten Vergabe in diesem Dokument **nie wieder umnummeriert, gelöscht oder wiederverwendet.** Die in §17 (Umsetzungsplan) vergebenen IDs sind dauerhaft stabil.
### 20.2 Regeln im Einzelnen
| Regel | Beschreibung |
|-------|-------------|
| **ID-Stabilität** | Eine einmal vergebene Paket-ID bleibt für immer mit dem ursprünglichen fachlichen Inhalt verknüpft. |
| **Titel-Präzisierung erlaubt** | Der Titel eines Pakets darf präzisiert werden, wenn die fachliche Substanz unverändert bleibt. Die ID ändert sich dabei nicht. |
| **Neue Pakete** | Künftige Arbeitspakete erhalten aufsteigende neue IDs (P-25, P-26 …). Keine Wiedervergabe von IDs abgeschlossener oder gelöschter Pakete. |
| **Nacharbeiten mit Suffix** | Nacharbeiten, Korrekturen oder Teilprobleme eines bestehenden Pakets werden mit alphabetischen Suffixen dokumentiert (P-03b, P-05b, …), nicht als eigenständige Hauptpakete. |
| **Freigabe-Formulierung** | Freigaben müssen immer die Paket-ID **und** den kanonischen Titel aus diesem Dokument oder aus `docs/compliance-package-register.md` nennen, um Verwechslungen auszuschließen. |
| **Kanonisches Register** | `docs/compliance-package-register.md` ist die verbindliche Quelle für alle Umsetzungsberichte, Re-Audit-Dokumente und Freigaben. Bei Widerspruch zwischen Dokumenten gilt dieses Audit als ursprüngliche Quelle; das Register als aktuell gepflegte Wahrheit. |
### 20.3 Umgang mit Drift in nachgelagerten Dokumenten
Falls ein nachgelagertes Dokument (Umsetzungsbericht, Re-Audit, Mini-Fix) eine abweichende Beschreibung für eine bekannte ID verwendet, gilt:
1. Die abweichende Beschreibung ist ein **Dokumentationsfehler**, kein neues Paket.
2. Das betroffene Dokument ist auf die kanonische ID und den kanonischen Titel zu korrigieren.
3. Der Drift und die Korrektur sind im Konsistenzbericht des Paketregisters zu vermerken.
4. Kein fachlicher Inhalt darf bei der Korrektur verloren gehen er wird ggf. dem richtigen Paket-Eintrag zugeordnet.
### 20.4 Freigabe-Vorlagen (aktualisiert)
Verwende diese exakten Formulierungen zur Freigabe einzelner Pakete:
| Paket | Freigabe-Formulierung |
|-------|----------------------|
| P-01 | Freigabe zur Umsetzung P-01: Rechtstexte" |
| P-02 | Freigabe zur Umsetzung P-02: Self-Service-Kontolöschung und Datenexport" |
| P-03 | Freigabe zur Umsetzung P-03: Papierkorb-Retention-Job aktivieren" |
| P-04 | Freigabe zur Umsetzung P-04: Copyright-Pflicht für Archiv-Promotion vereinheitlichen" |
| P-05 | Freigabe zur Umsetzung P-05: Passwort-Mindestlänge angleichen" |
| P-06 | Freigabe zur Umsetzung P-06: Upload-Einwilligungsdialog" |
| P-07 | Freigabe zur Umsetzung P-07: ALLOW_PUBLIC_MEDIA_STATIC dokumentieren und testen" |
| P-08 | Freigabe zur Umsetzung P-08: HSTS und externe Proxy-Sicherheit dokumentieren" |
| P-09 | Freigabe zur Umsetzung P-09: Admin-Audit-Log" |
| P-10 | Freigabe zur Umsetzung P-10: Mindestalter-Abfrage" |
| P-11 | Freigabe zur Umsetzung P-11: Legal-Hold Lifecycle-Status" |
| P-12 | Freigabe zur Umsetzung P-12: sessionStorage bei Logout bereinigen" |
| P-13 | Freigabe zur Umsetzung P-13: Content-Melde-Backend" |
| P-17 | Freigabe zur Umsetzung P-17: MFA für Superadmins" |
| P-18 | Freigabe zur Umsetzung P-18: HttpOnly-Cookie als Auth-Alternative" |
| P-22 | Freigabe zur Umsetzung P-22: HTML-Sanitizer für Rich-Text-Felder" |
| Etappe 1 komplett | Freigabe zur Umsetzung Etappe 1" |
| Etappe 1+2 | Freigabe zur Umsetzung Etappen 1 und 2" |
> Die ursprüngliche Freigabe-Tabelle in §19.8 bleibt erhalten und zeigt den Stand des Initial-Audits. §20.4 ist die aktuellere, vollständigere Version.
---
*Dokument erstellt: 2026-05-09 | Auditor: Claude Code | Kein Rechtsanwalt; alle rechtlichen Einschätzungen sind juristisch zu prüfen.*