Files
kaffeekasse-saas/docs/m8-haertung.md
T
clemensandClaude Sonnet 5 54217d2acb M8: Security-Headers, Rate-Limits und Audit-Log
Erste drei Bausteine der Haertung:

- Security-Headers (X-Content-Type-Options, X-Frame-Options, Referrer-
  Policy, Permissions-Policy, HSTS bei HTTPS) laufen automatisch ueber
  app_send_security_headers() am Ende von app/bootstrap.php fuer jede
  dynamische Seite; landing.php war als einzige Seite ganz ohne PHP und
  bekam einen minimalen Bootstrap-Aufruf. Bewusst kein CSP, da die
  bestehenden Templates durchgaengig auf Inline-style-Attribute setzen.
- DB-gestuetzte Rate-Limits (neue Tabelle rate_limit_attempts) fuer
  Login (10/15min je E-Mail, 20/15min je IP), Registrierung (5/h je IP)
  und Passwort-Reset-Anfrage (5/h je E-Mail, 10/h je IP); bei
  ausgereiztem Reset-Limit erscheint dieselbe generische Meldung wie im
  Erfolgsfall, um kein Konto-Enumeration-Signal zu geben.
- Zentrales Audit-Log (neue Tabelle audit_log) fuer Mitgliederverwaltung,
  Zugangsvergabe/-entzug, Storno, Mandant-Einstellungen, Hinweise,
  CSV-Import, Jahresbonus-Verteilung und Live-Mailversand; sichtbar fuer
  Owner/Admin auf mandant-einstellungen.php.

Live getestet: Rate-Limit greift nach 10 Fehlversuchen, Audit-Log-Eintrag
mit korrekten Metadaten und Nutzernamen ueber einen isolierten Test-
Mandanten geprueft. Alle Regressionstests weiterhin gruen (26/26 Smoke,
104 Golden-Master-Assertions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 18:04:14 +02:00

109 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
# M8 Härtung, Datenschutz und Betrieb
Stand: 2026-07-15
M8 macht die SaaS-App für den mehrmandantenfähigen Betrieb sicherer und
nachvollziehbarer: Security-Headers, Rate-Limits, ein zentrales Audit-Log,
sowie (folgend) Mandanten-Isolation, Rollenmatrix, Datenexport und
Löschung/Anonymisierung.
## Security-Headers
Umgesetzte Dateien:
```text
app/bootstrap.php
landing.php
```
- `app_send_security_headers()` setzt `X-Content-Type-Options: nosniff`,
`X-Frame-Options: DENY`, `Referrer-Policy: strict-origin-when-cross-origin`,
`Permissions-Policy` (Geolocation/Mikrofon/Kamera aus) sowie `Strict-
Transport-Security` bei HTTPS. Der Aufruf steht am Ende von
`app/bootstrap.php`, das transitiv von jeder dynamischen Seite geladen
wird, und läuft damit automatisch einmal pro Request.
- Bewusst **kein** Content-Security-Policy-Header: Die bestehenden
Templates nutzen durchgängig `style=""`-Inline-Attribute (Banner,
Tabellenzellen, Status-Einfärbung). Ein CSP-Lockdown würde diese
brechen und bräuchte einen eigenen Template-Durchgang als offener
Punkt vermerkt, nicht in diesem Schritt umgesetzt.
- `landing.php` war die einzige Seite ganz ohne PHP (reines HTML). Ein
minimaler `require_once app/bootstrap.php`-Aufruf am Dateianfang sorgt
dafür, dass auch sie die Header bekommt, ohne das Markup zu verändern.
## Rate-Limits
Umgesetzte Dateien:
```text
database/migrations/0010_saas_rate_limits.sql
app/rate-limit.php
login.php
register.php
passwort-vergessen.php
```
- Neue Tabelle `rate_limit_attempts` (Bucket + Zeitstempel) mit
`app_rate_limit_check()`: schreibt einen Versuch, räumt alte Einträge
für denselben Bucket auf und meldet, ob das Limit noch eingehalten ist.
- Login: 10 Versuche / 15 Minuten je E-Mail-Adresse **und** 20 Versuche /
15 Minuten je IP (beide müssen greifen, damit ein einzelnes Konto nicht
gezielt von verteilten IPs aus brute-forced werden kann und eine IP
nicht viele Konten gleichzeitig durchprobieren kann).
- Registrierung: 5 Versuche / Stunde je IP (gegen Spam-Registrierungen).
- Passwort-Reset-Anfrage: 5 / Stunde je E-Mail, 10 / Stunde je IP. Bei
überschrittenem Limit erscheint bewusst dieselbe generische
Erfolgsmeldung wie im echten Erfolgsfall, damit ein ausgereiztes Limit
nicht zusätzlich verrät, ob ein Konto existiert.
- Live getestet: 11 aufeinanderfolgende Fehlversuche gegen `login.php`
die ersten 10 zeigen „ungültig“, der 11. wird korrekt mit „Zu viele
Anmeldeversuche“ abgewiesen.
## Audit-Log
Umgesetzte Dateien:
```text
database/migrations/0011_saas_audit_log.sql
app/audit.php
mitarbeiterverwalten.php
letzteneintraege.php
mandant-einstellungen.php
hinweise.php
csvupload.php
jahresauswertung.php
mailversenden.php
```
- Neue Tabelle `audit_log` (Mandant, ausführender Nutzer, Aktion,
betroffener Datensatz, Metadaten als JSON, IP, Zeitpunkt) gemäß
Kernschema aus dem Umstrukturierungsplan.
- `app_audit_log()` schreibt einen Eintrag, `app_fetch_audit_log()` liest
die letzten N Einträge eines Mandanten.
- Protokollierte Aktionen: Mitglied anlegen/bearbeiten/(de)aktivieren,
Zugang gewähren/entziehen, Storno von Einzahlung/Strich, Mandant-
Einstellungen ändern, Hinweis anlegen/löschen, CSV-Import bestätigen,
Jahresbonus-Verteilung, Live-Mailversand.
- Sichtbar für Owner/Admin auf `mandant-einstellungen.php` unter
„Protokoll" (letzte 50 Einträge, mit Namen des ausführenden Nutzers).
- Live getestet: Hinweis anlegen erzeugt einen Log-Eintrag mit korrekten
Metadaten; über einen eigens angelegten Test-Mandanten geprüft, dass
eine Einstellungsänderung im UI-Protokoll mit korrektem Nutzernamen
erscheint. Testdaten anschließend entfernt.
## Prüfstatus
- Golden Master: grün mit 104 Assertions.
- M4 Ledger-Migration: grün mit 73 Assertions.
- M3 Settings-Flow: grün mit 13 Assertions.
- HTTP-Smoke: grün mit 26 geprüften Seiten.
## Noch offen in M8
- Content-Security-Policy (braucht Template-Bereinigung der Inline-Styles).
- Mandanten-Isolation automatisiert testen.
- Rollenmatrix automatisiert testen.
- Datenexport pro Mandant.
- Lösch-/Anonymisierungsprozess für Teilnehmer und Kunden.
- Backup-/Restore-Prozess und Monitoring/Fehlerlogging dokumentieren.