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>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user