Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 26s
Sicherheit P-12 (MITT-05): logout() entfernt alle sj_coach_*-Schlüssel
aus sessionStorage gezielt per Präfix-Löschung. Fremde Schlüssel
(Browser-Extensions etc.) bleiben erhalten. Verhindert Datenleak bei
Nutzerwechsel im selben Tab (geteilter Rechner).
- AuthContext.jsx: Präfix-Schleife in logout()
- tests/dev-smoke-test.spec.js: Playwright-Test P-12 (injects/checks 3
sj_coach_*-Schlüssel + 1 Fremd-Schlüssel; prüft selektive Löschung)
Compliance-Dokumentation:
- docs/compliance-implementation.md: P-12 ✅, Version 0.8.68
- docs/compliance-package-register.md: kanonisches Paketregister (neu)
- docs/compliance-roadmap.md: lebende Steuerungs-Roadmap (neu)
- docs/compliance-audit.md: §20 Paket-ID-Stabilitätsregel
version: 0.8.68 (backend + frontend)
module: auth 1.2.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
989 lines
44 KiB
Markdown
989 lines
44 KiB
Markdown
# 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 (001–046) | ✓ |
|
||
| 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 |
|
||
|----------|--------|
|
||
| Impressum | ❌ Keine Seite, kein Inhalt, keine Route |
|
||
| Datenschutzerklärung | ❌ |
|
||
| Nutzungsbedingungen / AGB | ❌ |
|
||
| Medienrichtlinie / Community-Regeln | ❌ |
|
||
| Verzeichnis der Verarbeitungstätigkeiten (VVT) | ❌ |
|
||
| AV-Verträge mit Auftragsverarbeitern | ❌ |
|
||
| Backup-Konzept schriftlich | ❌ |
|
||
|
||
### 14.3 Fehlende Frontend-Routen
|
||
|
||
In `frontend/src/App.jsx` fehlen:
|
||
- `/impressum`
|
||
- `/datenschutz`
|
||
- `/nutzungsbedingungen`
|
||
- `/medienrichtlinie`
|
||
|
||
Alle müssen **vor der Authentifizierung** erreichbar und in der PWA verfügbar sein.
|
||
|
||
---
|
||
|
||
## 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 | 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 |
|
||
|
||
### 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 | 2–3 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 | 2–5 Tage Technik |
|
||
| P-02 | Self-Service-Kontolöschung + Datenexport | KRIT-02, KRIT-05 | 5–8 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 | 2–4 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 | 3–5 Tage |
|
||
| P-10 | Mindestalter-Abfrage | HOCH-06 | 1–2 Tage |
|
||
| P-11 | Legal-Hold Lifecycle-Status | MITT-02 | 2–3 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 | 1–2 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 | 5–8 Tage |
|
||
| P-14 | Moderations-UI (Frontend) | KRIT-03 | 3–5 Tage |
|
||
| P-15 | Uploader-Benachrichtigung bei Sperrung | KRIT-03 | 1–2 Tage |
|
||
| P-16 | Beschwerdeverfahren | KRIT-03 | 2–4 Tage |
|
||
|
||
**Etappe 4 – Langfristige Optimierungen**
|
||
|
||
| Paket-ID | Titel | Aufwand |
|
||
|----------|-------|---------|
|
||
| P-17 | MFA für Superadmins (TOTP) | 5–8 Tage |
|
||
| P-18 | HttpOnly-Cookie als Auth-Alternative | 3–5 Tage |
|
||
| P-19 | Anti-Virus-Scan (ClamAV) | 3–5 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.*
|