Files
archivmail/features/PROJ-64-session-invalidation-file-permissions.md
sysopsandClaude Sonnet 5 b286352d07 fix(PROJ-64): Session-Invalidation bei Passwort-Change + Datei-Permissions gehärtet
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>
2026-07-03 22:23:27 +02:00

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 per ALTER TABLE ... ADD COLUMN IF NOT EXISTS in initSchema.
  • SetPassword() setzt tokens_valid_after = NOW() bei jedem Passwort-Change/-Reset.
  • Neue Methode InvalidateTokensBefore(ctx, userID) — von Admin-TOTP-Reset aufgerufen.
  • ValidateToken() vergleicht JWT-Claim iat gegen tokens_valid_after; liegt iat davor, 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.WriteFile von 0o644 auf 0o600.
  • Attachment-Dateien: gleiche Änderung in attachments.go.
  • Storage-Verzeichnisse (store/, attachments/, meta/, Shard-Verzeichnisse): os.MkdirAll von 0o755 auf 0o700.
  • 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 gegen tokens_valid_after ergänzt, zusätzlicher DB-Roundtrip pro Request-Validierung (gleiche Größenordnung wie bestehender Blacklist-Check).
  • internal/api/totp_handlers.go (handleTOTPReset): Aufruf von InvalidateTokensBefore() nach erfolgreichem TOTP-Reset ergänzt (Fehler nur geloggt, kein Abbruch — Reset selbst war bereits erfolgreich).
  • internal/api/onboarding_handlers.go (Passwort-Reset) und internal/api/profile_handlers.go (Passwort-Change) benötigten keine Änderung — beide rufen SetPassword() 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.