Security-Audit deckte zwei Medium-Findings auf: JWTs blieben bis zu 8h nach Passwort-Change/-Reset oder Admin-TOTP-Reset gültig (kein Session-Invalidation), und archivierte Mails/Anhänge wurden mit 0644/0755 statt 0600/0700 geschrieben. - users.tokens_valid_after (neue Spalte) wird bei SetPassword() und InvalidateTokensBefore() gesetzt; ValidateToken() lehnt JWTs mit iat davor ab. - Admin-TOTP-Reset revoked jetzt aktive Sessions des Zielnutzers. - Mail-/Attachment-Dateien und ihre Verzeichnisse nur noch für den archivmail-Service-Account lesbar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.0 KiB
PROJ-64: Session-Invalidation bei Passwort-Change + Datei-Permissions-Härtung (Security-Audit-Nachbesserung)
Status: Deployed
Created: 2026-07-03 Last Updated: 2026-07-03
Hintergrund
Vollständiges Codebase-Security-Audit (2026-07-03, 4 parallele Fokus-Reviews: Auth/Authz, Injection, Tenant-Isolation, Crypto/Storage) ergab zwei Medium-/Medium-High-Findings. Injection- und Tenant-Isolation-Review lieferten keine ausnutzbaren Befunde (bestehende Fixes aus PROJ-55/61/62/63 halten).
Finding 1 — Kein Session-Invalidation bei Passwort-Change/Reset (Medium-High):
SetPassword() (Passwort-Change, Passwort-Reset) und Admin-TOTP-Reset aktualisierten nur den bcrypt-Hash bzw. TOTP-Status, invalidierten aber keine bereits ausgestellten JWTs. Die Token-Blacklist arbeitet nur per-JTI (Logout), es gab keinen "invalidate all sessions"-Mechanismus. Ein gestohlenes JWT/Cookie blieb bis zu 8h nach Passwort-Reset gültig — die Standard-Gegenmaßnahme "Passwort ändern, um Angreifer auszusperren" griff nicht.
Finding 2 — Mail-Dateien world-readable (Medium):
internal/storage/storage.go und attachments.go schrieben archivierte Mails/Anhänge mit Mode 0644 (Verzeichnisse 0755) — lesbar für jeden lokalen User/Group, nicht nur den archivmail-Service-Account. Falls Encryption nicht konfiguriert ist (dokumentierter Fallback), lag Mail-Inhalt im Klartext für jeden lokalen Account offen.
Acceptance Criteria
- Neue Spalte
users.tokens_valid_after(TIMESTAMPTZ, nullable), idempotent perALTER TABLE ... ADD COLUMN IF NOT EXISTSininitSchema. SetPassword()setzttokens_valid_after = NOW()bei jedem Passwort-Change/-Reset.- Neue Methode
InvalidateTokensBefore(ctx, userID)— von Admin-TOTP-Reset aufgerufen. ValidateToken()vergleicht JWT-Claimiatgegentokens_valid_after; liegtiatdavor, wird der Token als revoked abgelehnt (auth: token revoked (credentials changed)).- Bestehendes Verhalten unverändert für User ohne gesetztes
tokens_valid_after(NULL-Check, No-op). - Mail-Dateien:
os.WriteFilevon0o644auf0o600. - Attachment-Dateien: gleiche Änderung in
attachments.go. - Storage-Verzeichnisse (
store/,attachments/,meta/, Shard-Verzeichnisse):os.MkdirAllvon0o755auf0o700. - Build- und Schema-Verifikation auf Testserver 192.168.1.132 (kein lokaler Go-Toolchain verfügbar).
Implementierungsnotizen (2026-07-03)
internal/userstore/userstore.go: Spalte +SetPassword()erweitert +InvalidateTokensBefore()+TokensValidAfter()ergänzt.internal/auth/auth.go(ValidateToken):iat-Vergleich gegentokens_valid_afterergänzt, zusätzlicher DB-Roundtrip pro Request-Validierung (gleiche Größenordnung wie bestehender Blacklist-Check).internal/api/totp_handlers.go(handleTOTPReset): Aufruf vonInvalidateTokensBefore()nach erfolgreichem TOTP-Reset ergänzt (Fehler nur geloggt, kein Abbruch — Reset selbst war bereits erfolgreich).internal/api/onboarding_handlers.go(Passwort-Reset) undinternal/api/profile_handlers.go(Passwort-Change) benötigten keine Änderung — beide rufenSetPassword()auf, das die Invalidation jetzt intern miterledigt.internal/storage/storage.go: Zeilen 72 (Init-MkdirAll), 413 (Shard-MkdirAll), 446 (WriteFile) gehärtet.internal/storage/attachments.go: Zeilen 45 (MkdirAll), 49 (WriteFile) gehärtet.- Build (
CGO_ENABLED=0 go build -buildvcs=false) und Schema-Migration (tokens_valid_after-Spalte, klaglos angelegt) auf 192.168.1.132 verifiziert, Dienste liefen danach unverändert weiter. Kein Produktiv-Deploy im Rahmen dieser Verifikation.
Tech Design
Übersprungen (additive Härtung, kein neues Subsystem — Muster analog zu bestehender Blacklist-/TOTP-Reset-Logik).
Priorität
Medium — kein akuter Exploit im laufenden Betrieb, aber schließt eine reale Lücke in der Incident-Response ("Passwort ändern" wirkte bisher nicht sofort) und eine konkrete Klartext-Offenlegung bei fehlender Encryption-Konfiguration.