shinkan-jinkendo/docs/compliance-implementation.md
Lars 75d6a40817
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 32s
docs(compliance): P-01 Dokumentation nach technischer Umsetzung
- compliance-implementation.md: P-01-Abschnitt hinzugefügt (technisch
  teilweise umgesetzt), Testergebnisse, Version 0.8.69
- compliance-package-register.md: P-01  open → ⚠️ partially
  implemented; Fortschrittsblock aktualisiert; App-Version 0.8.69
- compliance-roadmap.md: P-01 in teilweise umgesetzte Pakete; Blocker 1
  aktualisiert; §6 nächste Freigabe auf P-06 / Etappe B; Anhang

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:47:40 +02:00

13 KiB
Raw Blame History

Compliance-Implementierung Umsetzungsbericht

Erstellt: 2026-05-09
Zuletzt aktualisiert: 2026-05-10
Audit-Basis: docs/compliance-audit.md
App-Version nach Umsetzung: 0.8.69


Freigegebene Pakete und Umsetzungsstatus

P-01 Rechtstexte ⚠️ (technischer Teil umgesetzt; juristische Inhalte offen)

Status: Technisch teilweise umgesetzt (2026-05-10, Version 0.8.69)

Betroffene Dateien:

  • frontend/src/pages/LegalPage.jsx (neu) — Platzhalter-Komponente für alle vier Seiten
  • frontend/src/App.jsx — 4 neue öffentliche Routen
  • frontend/src/pages/LoginPage.jsx — Rechtstext-Links im Card-Footer
  • frontend/src/components/DesktopSidebar.jsx — Rechtstext-Links im Sidebar-Footer

Technische Änderung:
Vier öffentliche Routen angelegt, kein Auth erforderlich, kein Redirect für nicht eingeloggte Nutzer:

  • /impressum<LegalPage type="impressum" />
  • /datenschutz<LegalPage type="datenschutz" />
  • /nutzungsbedingungen<LegalPage type="nutzungsbedingungen" />
  • /medienrichtlinie<LegalPage type="medienrichtlinie" />

Jede Seite enthält:

  • Deutlich sichtbaren Platzhalterhinweis: „MUSTER / PLATZHALTER Inhalt wird vor Produktivbetrieb juristisch geprüft und durch den Betreiber ergänzt."
  • Strukturierte Pflichtfelder je Rechtstext (Betreiber, Rechtsgrundlagen, Betroffenenrechte etc.)
  • Querlinks zu den anderen drei Rechtstextseiten

Links zu allen vier Seiten sind in der Login-/Registrierungsseite (Card-Footer) und im Desktop-Sidebar-Footer sichtbar ohne Anmeldung erreichbar.

Nicht umgesetzt (außerhalb Freigabe):
Juristische Texte, inhaltliche Überprüfung, Admin-konfigurierbare Inhalte, Einwilligungs-Checkboxen.

KRIT-01 Blocking-Status:
Der Blocker KRIT-01 bleibt offen bis juristisch geprüfte Texte durch den Betreiber eingepflegt sind. Die technische Voraussetzung (Routen und Seitenstruktur) ist erfüllt.

Tests: 5 Playwright-Tests, alle grün:

  • Impressum ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Datenschutz ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Nutzungsbedingungen ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Medienrichtlinie ohne Auth erreichbar + Platzhalterhinweis sichtbar + Reload OK
  • Login-Seite enthält alle vier Rechtstext-Links

P-03 Papierkorb-Retention-Job aktivieren

Status: Umgesetzt

Betroffene Dateien:

  • docker-compose.yml neuer Service retention-cron

Technische Änderung:
Neuer Docker-Service retention-cron nutzt dasselbe Backend-Image und führt scripts/media_retention_job.py täglich um 03:00 Uhr aus. Der Service startet beim ersten Hochfahren sofort und schläft bis zum nächsten 3 AM (Python-basierter Loop ohne externe Cron-Abhängigkeit). Zugriff auf DB und Media-Volume identisch zur Backend-Konfiguration.

Tests: Keine automatisierten Tests möglich (Runtime-Verhalten); operativ über Container-Logs (docker logs shinkan-retention-cron) überprüfbar.


P-03b Retention-Zeiten mit Löschkonzept abgleichen

Status: Umgesetzt (2026-05-10)

Betroffene Dateien:

  • backend/media_lifecycle.py:24 Default HIDDEN_TO_PURGE_DAYS
  • docker-compose.yml explizite Env-Variable im retention-cron-Service

Befund des Re-Audits:
Das fachliche Löschkonzept (Audit §6.4) sieht 30 + 30 Tage vor:

  • Stufe 1 → Stufe 2 (Soft → Hidden): 30 Tage ✓ (war bereits korrekt)
  • Stufe 2 → Purge (Hidden → gelöscht): weitere 30 Tage → Code-Default war 90 Tage

Technische Änderung:

# media_lifecycle.py Zeile 24 (vorher "90", jetzt "30"):
HIDDEN_TO_PURGE_DAYS = max(1, int(os.getenv("MEDIA_TRASH_HIDDEN_TO_PURGE_DAYS", "30")))

docker-compose.yml enthält jetzt explizit MEDIA_TRASH_HIDDEN_TO_PURGE_DAYS: "${...:-30}" mit Kommentar, der das 30+30-Konzept dokumentiert. Der Wert kann per Env-Variable in einzelnen Deployments überschrieben werden.


Status: Umgesetzt; Tests nachgehärtet (2026-05-10)

Betroffene Dateien:

  • backend/routers/media_assets.py patch_media_asset() und bulk_media_patch()
  • backend/tests/test_media_assets_copyright_promotion.py

Technische Änderung:
Beide Endpoints prüfen, ob copyright_notice vorhanden ist, wenn visibility auf club oder official gewechselt wird. Priorität: Wert aus dem Request-Body > bestehender Wert in der DB. Ist beides leer, wird HTTP 400 zurückgegeben.

Fehlermeldung: "Für Vereins- oder offizielle Medien ist eine Urheberrechtsangabe (copyright_notice) Pflicht." (Umlaut korrekt, 2026-05-10 korrigiert)

Tests: 7 Tests, alle grün:

  • Promotion zu club ohne Copyright → 400 (exakt)
  • Promotion zu official ohne Copyright → 400 (exakt)
  • Promotion zu club mit Copyright im Body → 200 + Payload-Prüfung (gehärtet)
  • Promotion zu club, Copyright bereits auf Asset → 200 + Payload-Prüfung (gehärtet)
  • Kein Visibility-Wechsel → keine Copyright-Prüfung → 200 (exakt)
  • Bulk: ohne Copyright → in failed, updated_count == 0 (exakt)
  • Bulk: mit Copyright → in updated, updated_count == 1 (exakt)

P-05 Passwort-Mindestlänge angleichen ⚠️ (Teil 1 von 2 Re-Audit-Auflage offen)

Status: Teilweise umgesetzt

Betroffene Dateien (initialer Fix 2026-05-09):

  • backend/routers/auth.py:101 PUT /api/auth/pin: < 4< 8
  • frontend/src/pages/LoginPage.jsx:175 minLength="6"minLength="8"
  • frontend/src/pages/AccountSettingsPage.jsx:403 minLength={4}minLength={8}

Verbleibende Lücke identifiziert im Re-Audit 2026-05-09: POST /api/auth/reset-password hatte kein Mindestlängen-Limit → siehe P-05b.


P-05b reset-password Mindestlänge 8 Zeichen

Status: Umgesetzt (2026-05-10)

Betroffene Dateien:

  • backend/models.py:29 PasswordResetConfirm.new_password
  • backend/tests/test_auth_password_reset_minlength.py 7 neue Tests

Technische Änderung:

# models.py (vorher):
class PasswordResetConfirm(BaseModel):
    token: str
    new_password: str

# models.py (nachher):
class PasswordResetConfirm(BaseModel):
    token: str
    new_password: str = Field(min_length=8, max_length=128)

FastAPI lehnt Requests mit new_password < 8 Zeichen nun mit HTTP 422 (Pydantic Validation Error) ab, bevor der Endpoint-Handler ausgeführt wird. Kein DB-Zugriff erfolgt für unvalide Requests.

Tests: 7 Tests, alle grün:

  • Leer-String → 422
  • 1-Zeichen-Passwort → 422
  • 7-Zeichen-Passwort ("1234567") → 422
  • Exakt 8 Zeichen ("12345678") → 200
  • Langes Passwort → 200
  • Fehlendes new_password-Feld → 422
  • Gültiges Passwort, ungültiger Token → 400

P-05 vollständig geschlossen? Ja — alle passwortverarbeitenden Backend-Endpoints erzwingen jetzt Mindestlänge 8:

Endpoint Mindestlänge Mechanismus
POST /api/auth/register 8 if len(password) < 8 im Handler
PUT /api/auth/pin 8 if len(new_pin) < 8 im Handler
POST /api/auth/reset-password 8 Pydantic Field(min_length=8)
Management-Reset (profiles.py) 8 Pydantic Field(min_length=8)
Frontend LoginPage 8 minLength="8"
Frontend AccountSettingsPage 8 minLength={8}

P-07 ALLOW_PUBLIC_MEDIA_STATIC Release-Test

Status: Umgesetzt

Betroffene Dateien:

  • backend/tests/test_security_release.py 2 neue Tests

Tests: Beide grün.


P-12 sessionStorage bei Logout bereinigen

Status: Umgesetzt (2026-05-10, Version 0.8.68)

Betroffene Dateien:

  • frontend/src/context/AuthContext.jsx logout()
  • tests/dev-smoke-test.spec.js neuer Playwright-Test

Befund (war offen):
logout() löschte nur localStorage-Einträge. TrainingCoachPage schrieb sessionStorage-Schlüssel mit Präfix sj_coach_ (sj_coach_step_{unitId}, sj_coach_deltas_{unitId}, sj_coach_debrief_{unitId}), die nach Logout im Tab erhalten blieben. Bei Nutzerwechsel im selben Tab (geteilter Rechner) konnte der neue Nutzer Trainingsfortschritt des Vorgängers sehen.

Technische Änderung:
Gezielte Präfix-Löschung aller sj_coach_*-Schlüssel beim Logout (kein sessionStorage.clear()). Fremde sessionStorage-Schlüssel (Browser-Extensions o. ä.) bleiben erhalten.

// AuthContext.jsx logout() — Ergänzung:
for (const key of Object.keys(sessionStorage)) {
  if (key.startsWith('sj_coach_')) {
    sessionStorage.removeItem(key)
  }
}

Begründung gezielte statt vollständiger Löschung:
Alle Shinkan-spezifischen sessionStorage-Schlüssel sind eindeutig über den Präfix sj_coach_ identifizierbar (definiert in TrainingCoachPage.jsx Zeilen 1525). Ein sessionStorage.clear() würde auch Schlüssel fremder Quellen im selben Tab löschen; die Präfix-Löschung ist spezifischer und sicherer.

Tests: Playwright E2E-Test tests/dev-smoke-test.spec.js (Test „P-12: sessionStorage wird bei Logout bereinigt"):

  • Setzt drei sj_coach_*-Schlüssel und einen fremden Schlüssel
  • Klickt „Abmelden"
  • Prüft: sj_coach_* → null (entfernt), fremder Schlüssel → erhalten, authToken → null (localStorage weiterhin korrekt bereinigt)

Hinweis Tests: Das Projekt verfügt über kein Frontend-Unit-Test-Framework (kein Vitest/Jest in package.json). Der Test ist als Playwright E2E-Test implementiert, der einen laufenden Dev-Server voraussetzt. Automatisierte Ausführung der Playwright-Tests erfordert npx playwright test mit gesetzter PLAYWRIGHT_BASE_URL.


P-23 LoginPage: minLength + Versionsstring

Status: Umgesetzt

Betroffene Dateien:

  • frontend/src/pages/LoginPage.jsx

Technische Änderung:

  • minLength="6"minLength="8"
  • Hardcodierter Versionsstring v0.1.0 • Development entfernt

P-24 CORS allow_methods und allow_headers einschränken

Status: Umgesetzt

Betroffene Dateien:

  • backend/main.py:85-86

Technische Änderung:

allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "X-Auth-Token", "X-Active-Club-Id"],

Test-Zusammenfassung (Stand 0.8.69)

tests/test_auth_password_reset_minlength.py      7 passed  (neu, P-05b)
tests/test_media_assets_copyright_promotion.py   7 passed  (gehärtet, P-04)
tests/test_security_release.py                   9 passed  (inkl. 2 P-07-Tests)
Weitere bestehende Tests:                       81 passed, 6 skipped
Gesamt (Backend):                              104 passed, 6 skipped, 1 failed

Fehlgeschlagener Test: test_list_media_assets_invalid_lifecycle_400
→ Pre-existing: benötigt laufenden PostgreSQL-Container (Hostname "postgres")
→ Bestand bereits vor allen Compliance-Änderungen (verifiziert per git stash)

Playwright E2E (dev-smoke-test.spec.js):        14 passed (inkl. 5 neue P-01-Tests)
→ P-01: 4× Route ohne Auth + Platzhalterhinweis + Reload, 1× Login-Links
→ P-12: sessionStorage-Bereinigung (grün)

Nicht umgesetzte Pakete

Paket-IDs und -Titel gemäß kanonischem Register docs/compliance-package-register.md. Abweichende Beschreibungen in der Ursprungsversion dieses Abschnitts wurden am 2026-05-10 korrigiert (P-06, P-08, P-09, P-10, P-11, P-18 — Details im Konsistenzbericht des Registers).

Paket Kanonischer Titel Status Begründung
P-01 Rechtstexte offen Scope ausgeschlossen (juristischer Inhalt)
P-02 Self-Service-Kontolöschung + Datenexport offen Scope ausgeschlossen
P-06 Upload-Einwilligungsdialog (Recht am eigenen Bild) offen Scope ausgeschlossen
P-08 HSTS / externe Proxy-Sicherheit dokumentieren offen Scope ausgeschlossen (außerhalb Repo — Reverse-Proxy)
P-09 Admin-Audit-Log offen Scope ausgeschlossen
P-10 Mindestalter-Abfrage offen Scope ausgeschlossen
P-11 Legal-Hold Lifecycle-Status offen Scope ausgeschlossen
P-12 sessionStorage bei Logout bereinigen umgesetzt Version 0.8.68 — siehe §P-12 oben
P-13 Content-Melde-Backend offen Scope ausgeschlossen (erst juristisch klären)
P-14 Moderations-UI offen Scope ausgeschlossen
P-15 Uploader-Benachrichtigung bei Sperrung offen Scope ausgeschlossen
P-16 Beschwerdeverfahren offen Scope ausgeschlossen
P-17 MFA für Superadmins (TOTP) offen Scope ausgeschlossen
P-18 HttpOnly-Cookie als Auth-Alternative offen Scope ausgeschlossen
P-19 Anti-Virus-Scan (ClamAV) offen Scope ausgeschlossen
P-20 VVT erstellen offen Scope ausgeschlossen (Betreiber-Aufgabe)
P-21 AV-Verträge abschließen offen Scope ausgeschlossen (Betreiber-Aufgabe)
P-22 HTML-Sanitizer für Rich-Text-Felder offen Scope ausgeschlossen

Re-Audit-Empfehlung

Operativ nach Deployment 0.8.69 prüfen:

  1. P-03/P-03b: docker logs shinkan-retention-cron — Job läuft täglich 03:00 Uhr; Retention-Zeiten: 30 → 30 Tage
  2. P-04: Manuell: PATCH privates Medium auf official ohne copyright_notice → muss 400 liefern
  3. P-05b: Manuell: Reset-Link mit 7-Zeichen-Passwort → muss mit Fehler abgewiesen werden
  4. P-24: Browser DevTools Preflight → Access-Control-Allow-Headers: content-type, x-auth-token, x-active-club-id

Nächster vollständiger Re-Audit nach Umsetzung der kritischen Findings (P-01: Impressum/Datenschutz, P-02: DSGVO-Löschanfragen).