> **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
### 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
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
| 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`)
| 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 |
`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.
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.**
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:
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"):
**Dieses Flag darf in Produktionsumgebungen niemals gesetzt sein.** Muss in der Betriebsdokumentation explizit verboten und per Release-Checkliste überprüft werden.
> **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.
| 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 | 2–5 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 | 5–8 Tage |
| KRIT-03 | Kein DSA-Meldeverfahren | Keine Möglichkeit, rechtswidrige Inhalte zu melden. Kein Moderationssystem für UGC-Plattform. | Kein Router vorhanden | Ja | 10–20 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 | 3–7 Tage |
| KRIT-05 | Kein DSGVO-Self-Service (Auskunft, Export) | Keine Datenauskunft, kein Datenexport, keine Berichtigungsmöglichkeit (DSGVO Art. 15, 16, 20). | Kein Endpoint | Ja | 5–10 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:766–784` | Ja | 1–2 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 | 5–8 Tage |
| HOCH-05 | Kein Admin-Audit-Log | Profil-Löschungen, Lifecycle-Aktionen nicht geloggt | 3–5 Tage |
| HOCH-06 | Keine Mindestalter-Abfrage | Keine Schranke gegen Registrierung Minderjähriger | 1–2 Tage |
| 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 | 2–5 Tage Technik |
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+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.