Offizielles @tailwindcss/upgrade-Tool durchgelaufen: tailwind.config.ts
entfernt, Theme nach globals.css als @theme-Block (CSS-first-Config),
PostCSS auf @tailwindcss/postcss umgestellt. tailwind-merge im selben
Schritt auf v3 (Pflicht-Kopplung an Tailwind v4).
Codemod-Bug korrigiert: variant="outline" (Prop-Wert) wurde faelschlich
als CSS-Klasse zu "outline-solid" umbenannt in 4 Call-Sites - zurueckgesetzt.
Neue Lint-Regel (no-restricted-syntax) gegen *-opacity-* Utilities, die
in v4 nicht mehr existieren und von tailwind-merge v3 lautlos verschluckt
wuerden statt einen Fehler zu werfen.
tsc/lint/build gruen. Visueller Regressionstest offen (kein Browser-Tool
in dieser Session) - folgt auf 132 vor 131-Deploy.
ProtectSystem=strict + ReadOnlyPaths=.../etc/ssl/archivmail blockierte den
Cert-Handler (internal/api/cert_handlers.go) beim Erstellen/Erneuern von
Zertifikaten mit "read-only file system" — Fehler kam vom systemd-Namespace,
nicht von einem echten ro-Mount oder Owner-Problem. SSL_DIR gehört in
ReadWritePaths statt ReadOnlyPaths, da die Anwendung dort aktiv schreibt.
Fix im Unit-Generator in update.sh, nicht nur manuell am Server, da die
Unit bei jedem Deploy neu geschrieben wird.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
npm run lint rief next lint auf, das es in Next.js 16 nicht mehr gibt —
seit dem Next-16-Upgrade lief effektiv gar kein Lint mehr. Umgestellt auf
eslint . mit Flat-Config (eslint.config.mjs statt .eslintrc.json). Der
dadurch wieder sichtbare Lint-Lauf zeigte 30 Findings (25 Fehler,
5 Warnungen), alle gefixt:
- 19x react-hooks/set-state-in-effect: Loading-States wo möglich als
echte Ableitung statt eigenem Effect-State (use-mobile.tsx komplett auf
useSyncExternalStore umgebaut), sonst async-Wrapper mit Cancel-Guard um
bestehende Loader — Timing/Ladeanzeige unverändert.
- react-hooks/refs (useSearch.ts): Ref-Schreibzugriff aus dem Render in
einen Effect verschoben.
- 4x no-html-link-for-pages: <a href> durch next/link ersetzt in
admin/login, forgot-password, signup.
- Rest (exhaustive-deps, no-img-element, unused disable) einzeln gefixt.
- 4 bewusst belassene disable-Kommentare mit Begründung (shadcn/ui-Datei,
QR-Code-data-URL, Full-Reload nach Auth laut Projektregel).
eslint-Major-Upgrade auf 10 selbst bleibt blockiert: eslint-plugin-react/
jsx-a11y/import unterstützen ESLint 10 in ihrer aktuellen Latest-Version
noch nicht (Crash beim Laden), siehe Feature-Spec PROJ-79.
Verifiziert auf 132 (Build-Sandbox, kein Live-Deploy): npm ci/tsc/lint/
build grün, 8 Kern-Routen per Standalone-Server auf HTTP 200 geprüft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
lucide-react 0.562.0 auf 1.28.0 (kein Codeumbau nötig, alle genutzten
Icon-Namen kanonisch unverändert). npm audit fix behebt alle 6 gefundenen
Vulnerabilities ohne Breaking Change (next minor 16.2.9->16.3.0 im
bestehenden ^16.1.1-Range). caniuse-lite/Browserslist-DB aktualisiert
(war 8 Monate alt).
update.sh: npm audit --audit-level=high läuft jetzt als nicht-blockierendes
Warn-Gate nach npm ci. Bewusst kein Auto-Fix während des laufenden Deploys
— ein npm audit fix live auf dem Produktivsystem könnte unbemerkt Versionen
ändern, ohne vorherige Verifikation. Bei Fund wird gewarnt und auf lokales
audit fix + Test + Commit verwiesen.
Restliche PROJ-79-Schritte (eslint, @types/node, tailwindcss v4 +
tailwind-merge v3, typescript) bleiben offen, siehe Feature-Spec.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
Jeder Tastendruck im Suchfeld setzte bisher sofort State in SearchPage,
was über eine instabile doSearch-Referenz einen vollen Re-Render der
Ergebnistabelle auslöste (bei page_size 100: 100 Zeilen inkl.
sanitizeSnippet pro Tastendruck). Suchbegriff liegt jetzt lokal in der
Filterbar und wird debounced (300ms) nach oben propagiert; doSearch liest
Filterwerte aus einem Ref statt sie als Dependencies zu führen und bleibt
dadurch referenzstabil; SearchResultsTable ist jetzt memoisiert.
Enter-Verhalten bleibt sofort (Debounce wird vor dem Submit geflusht),
übrige Filter unverändert direkt-kontrolliert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
18 Admin-Tab-Komponenten wurden bisher alle statisch in src/app/admin/page.tsx
importiert, unabhängig davon welcher Tab tatsächlich geöffnet wird. Umstellung
auf next/dynamic mit gemeinsamem TabSkeleton-Loading-State reduziert das
initiale JS-Bundle der Admin-Route um 204,9 KB (-19,8%). Initial aktiver Tab
bleibt weiterhin serverseitig vorgerendert (ssr: true), kein Skeleton-Flash
beim ersten Laden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
blockExternalSrcs() filterte bisher nur src= bei img/video/audio/source und
ließ Tracking-Pixel via <link href>, background=, CSS url() (Style-Block und
inline) sowie srcset durch — DSGVO-relevantes Read-Tracking trotz aktivierter
Blockierung. Zusätzlich deckt die neue Erkennung protokollrelative URLs
(//host/px.gif) ab, die die alte https?:-Prüfung durchließ.
data:/cid:-URIs bleiben unangetastet, <a href> weiterhin klickbar. CSP-Header
als robustere Ergänzung (Blocklist-Regex bleibt grundsätzlich umgehbar) folgt
separat mit dem firewall-security-Skill.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
Der index-pending-Cron (PROJ-58) scannte bei jedem Lauf die komplette
emails-Tabelle, weil indexed_at unindiziert war — skaliert linear mit
Archivgröße. Partial Index auf (received_at DESC) WHERE indexed_at IS NULL
verifiziert auf 192.168.1.132: Seq Scan (13.3ms, 52895 Zeilen) → Index Scan
(0.086ms).
Restlicher PROJ-75-Audit (pgxpool-Tuning, reconciliation N+1) bewusst
zurückgestellt, siehe Feature-Spec.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
archivmail-export nutzte noch die alte storage.New(path string)-Signatur
statt storage.Config (fehlender Keyfile/Compress hätte Rohbytes statt
Klartext-EML exportiert). Toten Self-Assignment-Code in storage.go entfernt.
Testdateien (storage, audit, api, userstore, auth) an aktuelle Signaturen
angeglichen; auth-Tests liefen bisher gegen einen SQLite-Pfad statt Postgres-
DSN und wurden auf das TEST_DATABASE_URL-Schema-Isolationsmuster der übrigen
Pakete umgestellt. api_test.go las den Login-Token noch aus dem JSON-Body
statt aus dem httpOnly-Cookie (Auth-Contract-Drift).
TestParseMissingDate an tatsächliches Verhalten angepasst: der Parser lässt
das Datum bewusst als Zero-Value, der time.Now()-Fallback sitzt in der
Storage-Schicht — damit bleibt nachvollziehbar ob ein Datum aus der Mail
stammt oder vom Archiv gesetzt wurde (GoBD).
Verifiziert auf 192.168.1.132: go build/vet/test ./... komplett grün,
kein Skip (Postgres + Manticore erreichbar).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
Upload-Job bleibt bei einem Panic im Verarbeitungspfad nicht mehr auf
"running" hängen, sondern zeigt "error" mit generischer Meldung (Panic-
Rohwert nur im Server-Log, ErrMsg geht ans Frontend und könnte sonst
Mail-Inhalt-Fragmente transportieren). Zusätzlich fail-closed nil-Guards
an allen 13 GetByUsername-Aufrufstellen in internal/api/, die das
Ergebnis bisher ungeprüft dereferenzierten.
Verifiziert auf 192.168.1.132: Build und go vet fehlerfrei für die
geänderten Dateien.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
Feature-Spec, INDEX.md und features.ts für PROJ-72 (Superadmin-Peer-Patch-Fix)
ergänzt inkl. QA-Ergebnisse. Zusätzlich: abgelehnte Privilege-Escalation-/
Tenant-Isolation-Versuche (403) werden jetzt als Success:false im Audit-Log
protokolliert (vorher nur erfolgreiche Updates) - Härtungspunkt aus QA-Runde.
Wichtig für PROJ-32-Duplikatserkennung. Bereits erfüllt durch 1:1-Weitergabe
der rohen RFC-2822-Bytes an AppendToMailbox() ohne Reparse.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Beide Features auf 132 und 131 deployed (2026-07-07). PROJ-69/PROJ-70
ohne vollständigen Browser-/E2E-Test, nur Smoke-Tests — offene QA-Punkte
in den Spec-Dateien dokumentiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Datei war durch .git/info/exclude (features/) von "git add -u" ausgenommen,
INDEX-Eintrag existierte bereits, die eigentliche Spec-Datei fehlte im
Commit. Enthält jetzt auch die Implementation Notes zum Backend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neuer Opt-in-Endpunkt, mit dem User archivierte Mails per IMAP APPEND
zurück in ihr eigenes externes Postfach (INBOX) kopieren können. Das
Archiv selbst bleibt read-only (nur storage.Load(), kein Schreibzugriff
auf internal/imapserver).
- PATCH /api/auth/imap-restore: Opt-in-Flag umschalten, Aktivierung
erfordert Passwort-Reverifikation.
- POST /api/mails/{id}/restore: lädt Mail lesend, prüft Mail- und
Account-Ownership in restoreAccessAllowed() (PROJ-61-Muster), APPEND
via neuer internal/imap/append.go, kein Admin-Override.
- Audit-Log-Eintrag (EventRestore) pro Versuch, erfolgreich und
fehlgeschlagen.
- imap_restore_enabled-Spalte (default false) via idempotenter
initSchema-Migration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Manticore-Upgrade (PROJ-67) und sudo-Provisionierung (PROJ-68) auf 131
und 132 verifiziert abgeschlossen; QA/Deployment-Notizen ergänzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/BACKUP_RESTORE_RUNBOOK.md deckt beide Sicherungsebenen ab: App-eigenes
CLI-Backup (archivmail backup/restore, PROJ-66) und Infrastruktur-Ebene
(PBS + Sync auf Zweitserver, pct restore). Enthaelt Bitwarden-Keyfile-
Escrow-Anleitung, Pflicht-Verifikationsschritte (reconcile/reindex/
Stichprobe), Rotation-vs-DSGVO-Hinweis und den offenen Punkt "noch kein
Restore-Test durchgefuehrt" als groesste verbleibende Luecke.
AC 8 aus PROJ-66 damit erfuellt; AC 6 (Restore-Test) und AC 7 (Alerting)
bleiben offen.
QA-Re-Diagnose auf 132 fand einen zweiten, unabhängigen Blocker: die
archivmail.service-Unit setzt CapabilityBoundingSet=CAP_NET_BIND_SERVICE
CAP_NET_ADMIN. Das kappt das Capability-Set des GESAMTEN Prozessbaums
inklusive der von exec.Command("sudo", ...) geforkten Kindprozesse - sudo
braucht aber zusaetzlich CAP_SETUID/CAP_SETGID um auf uid/gid 0 zu
wechseln, sonst bricht es ab ("unable to change to root gid: Operation not
permitted"), selbst wenn sudoers korrekt konfiguriert ist.
Ein erster Verifikationsversuch per `runuser -u archivmail -- sudo ...`
hatte faelschlich Erfolg gemeldet, weil runuser den restriktiven
Bounding-Set der Unit nicht traegt - nur ein Test im echten
systemd-Unit-Kontext (systemd-run oder echter API-Call) ist aussagekraeftig.
Fix: CapabilityBoundingSet in update.sh und install.sh um CAP_SETUID
CAP_SETGID erweitert. AmbientCapabilities bleibt unveraendert (der Go-Daemon
selbst soll diese Rechte nie dauerhaft halten, nur der sudo-Kindprozess
braucht sie im Bounding-Set verfuegbar).
Root Cause für den HTTP-500-Bug beim Manticore-Neustart über den Dienste-Tab:
sudo war auf 132 gar nicht installiert, admin_services_handlers.go ruft aber
für JEDE Dienst-Aktion sudo systemctl auf. Betraf nicht nur Manticore,
sondern alle Dienste in der Whitelist - PROJ-67 hat es nur erstmals sichtbar
gemacht.
install.sh/update.sh installieren jetzt sudo (falls fehlend) und legen
/etc/sudoers.d/archivmail mit NOPASSWD-Regeln für genau die Service-
Whitelist an (archivmail, archivmail-web, manticore, postgresql@17-main,
postfix, nginx) - validiert per visudo -c vor dem Einspielen, damit ein
Bug im Generator nie eine kaputte sudoers-Datei live schaltet. update.sh
zieht das als Backfill für Bestandsinstallationen nach, analog zum
PROJ-67-Upgrade-Pfad-Muster.
archivmail-nft-Helper (externe Zugriffskontrolle) fehlt auf 132 ebenfalls,
aber bewusst nicht Teil dieses Tickets (vermutlich firewall-security-Skill-
Verantwortung).
install.sh/update.sh übersprangen den Manticore-Block komplett, sobald
irgendeine Version schon lief — der Versions-Pin aus dem letzten Fix wirkte
dadurch nie auf existierende Installationen (nur Neuinstallationen). Jetzt
per dpkg --compare-versions: ist die installierte Version älter als
_MANTICORE_VERSION, wird der Dienst gestoppt, das neue Paket eingespielt und
neu gestartet. Downgrade-Schutz durch "lt"-Vergleich.
Spec PROJ-67 dokumentiert die Recherche zum Versionssprung 25.0.0 -> 27.1.5
(u.a. offene Frage zur neuen v27-Auth-Funktionalität) und den geplanten
Produktiv-Test auf 131.
BUG-1 (QA): Backticks in der restore-Hilfezeile schlossen das Raw-String-
Literal von printHelp() vorzeitig, Build brach komplett. Backticks durch
einfache Anführungszeichen ersetzt.
BUG-2 (QA): -force bei restore scheiterte an bereits vorhandenen
Hardlink-Zieldateien (os.Link: file exists) — für den Normalfall (Restore
gegen einen PROJ-65-Tenant-Hardlink-Bestand) war -force damit funktionslos.
copyTreePreservingHardlinks() entfernt jetzt eine vorhandene Zieldatei vor
os.Link.
install.sh gibt das frisch generierte Keyfile nach der Erzeugung einmalig
aus (beide Installationspfade), mit Aufforderung es sofort in einen
Passwort-Safe (Bitwarden) zu verschieben. Backup ist ohne dieses Keyfile
wertlos — der Hinweis darf nicht vom "mach ich später" verschluckt werden,
da der Wert danach nirgends mehr sichtbar abrufbar ist.
Sichert Store (Hardlinks erhalten, PROJ-65-tauglich ohne Abhängigkeit von
rsync -H), Keyfile, config.yml und PostgreSQL (pg_dump -Fc) konsistent in
ein Zielverzeichnis. Rotation läuft nur nach erfolgreichem Lauf, ein
fehlgeschlagener Backup rotiert nie ein gutes altes Backup weg.
restore ist bewusst konservativ: bricht bei nicht-leerem Store/Keyfile ohne
-force ab, stoppt/startet den Dienst nicht selbst, gibt am Ende die
Pflicht-Verifikationsschritte (reconcile, reindex, Stichprobe) aus.
Ergänzt die vorhandene PBS+Sync-Infrastruktur um einen App-eigenen,
selektiven Restore-Weg. Kein automatischer Cron-Eintrag aktiv (Zielpfad
noch offen), nur als Vorlage in deploy/cron.d/archivmail auskommentiert.
Kein lokaler go build möglich, QA folgt auf Testserver.
PROJ-66 (Planned): kein App- oder Cron-Backup für /var/archivmail/store,
Keyfile, PostgreSQL auf 131 und 132 vorhanden. Nutzer-Rückmeldung ergänzt:
Infra-Ebene sichert bereits per Proxmox Backup Server + Sync auf Zweitserver,
das entschärft den Off-Site-Blocker deutlich - verbleibende Kernlücke ist
ein nie getesteter Restore, nicht das fehlende Backup-Ziel. Bitwarden-
Keyfile-Escrow als zusätzlicher, von PBS unabhängiger Wiederherstellungsweg
ergänzt.
PROJ-65: Deploy-Agent hatte Status/QA-Ergebnisse bereits lokal geschrieben,
aber noch nicht committed.
Jeder Tenant bekommt ein eigenes Verzeichnis store/tenant_<id>/, das per
Hardlink auf die kanonische content-adressierte Datei zeigt — das bestehende
Cross-Tenant-Dedup-Modell (email_refs M:N, PROJ-32/37) bleibt dadurch
erhalten, kein Speicherplatz-Mehrverbrauch. Neues CLI-Subcommand
`archivmail migrate-tenant-dirs` zieht Bestandsdaten einmalig nach
(idempotent). Zusätzlich neuer Status-Check checkStoragePermissions
(warnt bei zu offenen store_path-Rechten, analog checkEncryption/PROJ-49).
DB-gestützte Zugriffskontrolle bleibt der maßgebliche Zugriffspfad im Code;
die Tenant-Ordner sind eine zusätzliche Defense-in-Depth-Ebene für manuelle
Dateisystem-Audits. Kein lokaler go build möglich, QA folgt auf Testserver.
Backend/Frontend erfolgreich auf 192.168.1.131 deployt (update.sh, code war
bereits committed). Deployment-Abschnitt mit Smoke-Test-Ergebnis ergänzt,
Breaking-Change-Hinweis (Tenant-User Login nur noch per E-Mail) ins DEVLOG.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bewertet drei Optionen (Hardlink-Farm pro Tenant, Metadaten-Trennung
dokumentieren, Dedup nur noch pro Tenant) gegen das bestehende
Cross-Tenant-Dedup-Modell (email_refs M:N). Empfehlung: Option B
zuerst, Option A nur bei konkreter Kundenanforderung.
Checklist war seit 2026-06-13 nicht aktualisiert: PROJ-48/49/50/51/52 waren
laengst deployt, standen aber noch als fehlend/teilweise drin. PROJ-56c
(Cron-Purge mit Markierungspflicht) war im Code implementiert, hatte aber
nie eine eigene Feature-Spec — nachtraeglich dokumentiert.
Beide Features wurden auf 192.168.1.131 deployt (Commit 4c92587), inkl. der
zugehörigen QA-Bugfixes (Dry-Run-Adressmatching PROJ-43, days-Clamp PROJ-52).
Deployment-Abschnitte in den Feature-Specs und INDEX.md aktualisiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dryRunCondition() erwartete faelschlich die Winkelklammer-Form "Name <addr>",
waehrend mail_from/mail_to die Adresse bare speichern - Dry-Run zeigte dadurch
immer 0 Treffer fuer Adress-Regeln, obwohl der Live-Matcher (routeBareAddr)
korrekt matcht. QA-Ergebnisse (Bug-1) in die Feature-Spec uebernommen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tenant-User (tenant_id IS NOT NULL) melden sich künftig per E-Mail an statt
per Username — behebt Verwechslungen wie im Support-Fall vom 2026-06-13
(Login schlug trotz Passwort-Reset fehl, weil E-Mail statt Username
verwendet wurde). Nicht-Tenant-User (Superadmin/System) können weiterhin
Username ODER E-Mail nutzen.
Neue Store.VerifyLogin() prüft erst per E-Mail (alle User), fällt dann auf
Username zurück (nur tenant_id IS NULL). VerifyPassword() bleibt für den
IMAP-Server-Login-Pfad (PROJ-26) unverändert. Bewusster Breaking Change für
Tenant-User, Datenqualität vorab geprüft (0 Kollisionen).
Security-Nachtrag: bcrypt-Dummy-Compare im "user not found"-Pfad ergänzt,
um Timing-basierte Identifier-Enumeration zu verhindern.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Täglicher Cron-Job (archivmail reconcile) berechnet pro Tenant/Quelle
(SMTP-Journal, IMAP-Konto, POP3-Konto, Datei-Import) archivierte Mail-Zahlen,
für IMAP zusätzlich einen Soll/Ist-Vergleich via UID-Tracking. Abweichungen
über Schwellenwert erzeugen Audit-Log-Warnung. Neue Admin-Dashboard-Kachel
"Vollständigkeits-Check" (letzte 7 Tage, Warn-Badge, CSV-Export).
Schließt die "teilweise erfüllt"-Lücke bei Vollständigkeit im
GoBD/DSGVO-Compliance-Check (VOI-Grundsatz 2).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
tenantAccessAllowed()-Check in allen {id}-Handlern von tenant_handlers.go,
tenant_domain_handlers.go und tenant_logo_handlers.go ergänzt — No-op für
globale Admins, zweite Verteidigungslinie für hypothetische tenant-gebundene
Admin-Sessions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
handleDeletePop3 und handleStartPop3Import prüften nur Owner/Rollen-Level,
nicht den Tenant-Scope (anders als das korrekte IMAP-Pendant). Ein
domain_admin konnte dadurch POP3-Konten eines fremden Tenants löschen
oder deren Import anstoßen. Fix: tenantAccessAllowed(sess, acc.TenantID)
ergänzt, analog zum IMAP-Handler. Gefunden bei gezielter Nachsuche nach
Geschwister-Bugs zu PROJ-61.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /api/tenants/{id}/logo prüfte nur die Authentifizierung, aber keinen
Tenant-Scope — jeder eingeloggte Nutzer konnte das Logo jedes beliebigen
Tenants lesen. Kombiniert mit dem bisher erlaubten SVG-Upload (kann
eingebettetes JavaScript enthalten) ergab das einen Cross-Tenant Stored-XSS:
ein domain_admin konnte ein bösartiges SVG als eigenes Logo hochladen und
Opfer aus beliebigen anderen Tenants per direktem Link darauf locken.
Fix: tenantAccessAllowed()-Scope-Check beim Logo-Lesepfad (analog PROJ-55),
SVG aus erlaubten Upload-Typen entfernt, X-Content-Type-Options: nosniff
als Defense-in-Depth ergänzt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root Cause für ausbleibende Lastsenkung: update.sh hat /etc/cron.d/archivmail
nie auf den Server kopiert, daher fehlten die PROJ-58-Cronzeilen trotz
aktivem batch_mode komplett. Jetzt kopiert update.sh die Cron-Datei und alle
Wrapper-Skripte bei jedem Deploy automatisch ein.
Zusätzlich: Wrapper-Skripte (analog mailpiler indexer.delta.sh) verhindern
per Lockfile, dass sich Cron-Läufe bei großem Backlog überlappen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
config.docker.yml.example setzt index.batch_mode/ocr.batch_mode jetzt
auf true statt es nur kommentiert zu zeigen, damit neue Installationen
direkt mit Cron-Batch-Verarbeitung statt Dauerbetrieb starten. Bestehende
Installationen (131/132) sind unberührt, da ihre config.yml unabhängig
vom Repo-Beispiel ist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neues config.yml-Feld batch_mode (index/ocr, Default false = unverändertes
Verhalten). Bei batch_mode:true verarbeiten neue Cron-Jobs (index-pending,
ocr-reprocess) die Backlogs in größeren Abständen statt sofort bei jedem
Mail-Import, um Schreiblast auf der Festplatte zu glätten. Zeiten in
/etc/cron.d/archivmail frei anpassbar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OCR-Worker pausieren optional in konfigurierbarem Zeitfenster
(paused_hours), Jobs bleiben pending statt verworfen zu werden.
IMAP-Scheduler verteilt Sync-Starts via deterministischem
Pro-Account-Jitter, um Lastspitzen bei vielen Postfächern mit
gleichem Intervall zu vermeiden. Beides per Config opt-out,
Default-Verhalten unverändert. Build + Smoke-Test auf 132 verifiziert.