Screenshots dürfen nicht auf Gitea landen, waren in af16138 versehentlich
mit committet. Aus Tracking entfernt (git rm --cached, Dateien bleiben
lokal erhalten) und in .gitignore aufgenommen.
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.
Baseline/Post-Vergleich, PROJ-18-Integritätscheck, Manticore-Konsistenz,
PROJ-52-Reconciliation und Byte-Vergleich als Ablauf nach jeder Schema-/
Index-/Tenant-Migration. Checklist-Punkt 10 auf Erfüllt gesetzt.
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.
Punkt 1 (Vollständigkeit) war noch als "Teilweise" markiert, obwohl PROJ-52
(Reconciliation-Report) das inzwischen abdeckt. Punkt 7 (Löschsperre)
beschrieb noch den alten Purge() ohne Markierungspflicht; ergänzt um
PROJ-56c Cron-Purge, der nur retain_until < NOW() UND marked_for_deletion
löscht.
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>
strings.Contains(nftStr, "dport 443") fand den Substring nicht, wenn
nftables mehrere Ports als Set ausgibt ("tcp dport { 80, 443 } accept" statt
"tcp dport 443 accept") — das ist bei archivmail-Installationen der
Normalfall (80+443 stehen zusammen in einer Regel). Dashboard zeigte
dadurch fälschlich "Kein HTTPS — Verbindungen unverschlüsselt", obwohl
Port 443 korrekt offen war (bestätigt auf 131 und 132).
Neue Helper-Funktion nftHasPort() per Regex erkennt beide Formen
(Einzelport und Set). Betrifft die Checks für HTTPS/443, Port 3000 und
Port 8080.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bisher schrieb nur install.sh die Unit-Dateien (einmalig beim Erst-Setup).
Server, die vor einer Unit-Änderung installiert wurden, blieben dauerhaft
auf altem Stand — konkret fehlte auf 192.168.1.132 CAP_NET_ADMIN (wurde in
install.sh ergänzt, aber nie auf bereits laufende Installationen
nachgezogen), wodurch die Security-Tab-Aktion "Firewall aktivieren"
(nft -f /etc/nftables.conf) mit "Operation not permitted" fehlschlug.
update.sh schreibt jetzt bei jedem Deploy beide Unit-Dateien neu (identisch
zum install.sh-Template) und lädt sie per daemon-reload nach, bevor die
Dienste gestartet werden.
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>
Backend liefert bei GET /api/admin/dsgvo ein Objekt {"requests":[...]},
Frontend erwartete rohes Array → .map() auf Nicht-Array warf Exception,
angezeigt als "DSGVO-Anträge konnten nicht geladen werden".
Zusätzlich stimmten DSGVOResultSummary/DSGVOMailResult-Feldnamen nicht mit
dem Go-JSON überein (total vs. total_hits; id/status/received_at existieren
im Backend nicht, stattdessen mail_id/deletable/deleted/date). Status wird
jetzt im Frontend aus deletable/deleted abgeleitet (mailStatus()).
Bug bestand seit Feature-Implementierung (2026-06-13), fiel erst jetzt beim
ersten Live-Test auf.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Läuft nachts um 04:10 Uhr, nach dem Purge-Job und außerhalb der
OCR-Pause. Ohne diesen Eintrag existierte der neue Subcommand
"archivmail reconcile" nur manuell aufrufbar, ohne produktiven Betrieb.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Security-Audit-Nachtrag (siehe PROJ-64):
- internal/api/v1_handlers.go: handleV1SearchMails fehlte der fail-closed
Tenant-Post-Filter-Fallback für den Fall idxMgr==nil (gleiches Muster wie
bereits in search_handlers.go). Aktuell nicht ausnutzbar, da idxMgr in
main.go immer gesetzt wird, aber strukturelle Absicherung gegen künftige
Regressionen (analog PROJ-55 BUG-1).
- internal/smtpoutconfig/store.go: Verschlüsselungsschlüssel wird jetzt aus
dem HKDF-abgeleiteten aesKey gebildet statt aus dem rohen cfg.API.Secret,
konsistent mit internal/ldapconfig und internal/imap/store.go (SEC-08).
Verifiziert: smtp_out_config auf Produktiv (131) war leer, kein
Breaking Change für bestehend gespeicherte Zugangsdaten.
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>
Bei NextPart()-Fehler (EOF, malformed header) wird der bereits geparste Inhalt
zurückgegeben statt der gesamte Parse abgebrochen. parseMultipart() gibt keinen
Fehler mehr zurück — partieller Text/HTML ist besser als gar kein Index-Eintrag.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OS-Check: bricht ab wenn nicht Debian 13 erkannt
- Go: apt-golang-go entfernt, Go 1.24.4 von dl.google.com (arm64/amd64)
- Manticore: bookworm-Paket hardcodiert (kein natives trixie-Paket), in
install.sh und update.sh konsistent
- Native config: batch_mode: true für index + ocr (wie PROJ-58 vorgesehen)
- systemd: NEXT_PUBLIC_API_URL entfernt (Next.js baked bei Build, nicht Runtime)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neuer Tab im Admin-Bereich: Antrags-Liste, Formular (Adresse + Zeitraum),
Detail-Dialog mit Mail-Tabelle, PDF-Export und Löschen mit Bestätigung.
Rollen-Gating: admin/auditor sehen den Tab, nur admin kann anlegen/löschen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug-1: GetDSGVOMailMeta mit tenant_id-Filter (Defense-in-Depth an DB-Schicht).
Bug-2: Verwaiste open-Einträge bei Auswertungsfehler werden auf failed markiert.
Bug-3: Fehlgeschlagene Suchen werden im Audit-Log protokolliert.
Bug-6: Ungültige date_from/date_to-Eingaben liefern HTTP 400.
Co-Authored-By: Claude Sonnet 4.6 <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>
Dockerfile und Makefile bauten noch mit CGO_ENABLED=1 -tags xapian und
installierten libxapian-dev/libxapian30, obwohl der Xapian-Code bereits
entfernt wurde. PRD.md und api-v1.md erwähnten Xapian noch im Tech-Stack
bzw. als Such-Syntax-Referenz.
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>
Xapian war seit der Manticore-Migration (PROJ-30) nur noch ein
build-tag-gatetes, in Produktion nie genutztes Legacy-Backend.
internal/index/xapian.go, xapian_stub.go, xapian_wrapper.cpp/.h entfernt;
index.New() unterstützt jetzt nur noch "manticore". Default-Fallbacks in
Nebenwerkzeugen (archivmail-import/-export, cmd_export/cmd_reindex/...)
von "xapian" auf "manticore" korrigiert, Xapian-spezifische Tests entfernt,
Kommentare bereinigt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
xapian_path/backend: xapian waren toter Datenmüll (StorageConfig hat kein
xapian_path-Feld, wird beim YAML-Unmarshal stillschweigend ignoriert).
Auf den tatsächlich genutzten Manticore-Default umgestellt
(backend: manticore, manticore_dsn). Behebt das in PROJ-56 QA als
INFO 3 dokumentierte, außerhalb des Scopes liegende Finding.
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>
Der Mail-Parser ignorierte das charset-Parameter aus Content-Type und
interpretierte Bytes immer als UTF-8, wodurch iso-8859-1/windows-1252
kodierte Mails (z.B. mit Umlauten) als Mojibake gespeichert wurden.
Zusätzlich fehlte das Charset für die Manticore-MySQL-Verbindung und
der charset-Parameter im JSON-Response-Header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
archivmail import --dir/--file ordnete importierte Mails bisher immer
ohne Mandant zu (Save(..., nil) fest verdrahtet). Neuer --tenant <id> Flag
übergibt die Tenant-ID an Store.Save() und an idxMgr.ForTenant(), sodass
Mail sowohl in der DB als auch im richtigen Tenant-Suchindex landet —
und automatisch die Retention-Regeln des Tenants greifen (applyRetention
nutzt bereits die übergebene tenantID). Relevant für den geplanten
Watch-Folder-Import pro Mandant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der automatische Purge-Cron darf nicht allein anhand des abgelaufenen
retain_until löschen — eine Mail muss zusätzlich von einem Admin im UI
zur Löschung markiert worden sein. Dafür: neue Spalten
marked_for_deletion(_by/_at) auf emails, Store.ListExpiredMarkedMailIDs()
(retain_until abgelaufen UND markiert) für den Cron-Pfad, und
Store.SetMarkedForDeletion() zum Setzen/Löschen der Markierung.
Neue Endpoints (domain_admin+, tenant-scoped):
- GET /api/admin/retention/expired Metadaten abgelaufener Mails
(kein Body-Zugriff, SEC-29)
- PUT /api/admin/mails/{id}/mark-deletion Markierung setzen/entfernen,
mit Audit-Log-Eintrag
RetentionTab.tsx zeigt abgelaufene Mails mit Checkbox zum Markieren.
Der bestehende manuelle "Jetzt löschen"-Button (/api/admin/purge) bleibt
unverändert und löscht weiterhin alle abgelaufenen Mails auf einen Klick —
nur der unbeaufsichtigte Cron-Job ist jetzt auf markierte Mails beschränkt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
archivmail purge ist ein neuer CLI-Befehl, der Mails mit abgelaufener
retain_until löscht, aus dem Suchindex entfernt und pro Mail einen
Audit-Eintrag (mail_purged) schreibt — analog zu Pilers purge.sh, nachts
03:40 Uhr über deploy/cron.d/archivmail. Nur Mails mit explizit gesetztem
und abgelaufenem retain_until werden angefasst; ohne retain_until bleibt
alles unberührt, die Löschsperre (PROJ-34) greift weiterhin.
Beim Testen aufgedeckt: Store.Delete() entfernte die Datei vor dem
DB-Delete und verschluckte den Fehler, wenn email_refs/email_attachments
per Fremdschlüssel die Löschung blockierten — Ergebnis war ein DB-Eintrag
ohne zugehörige Datei. Jetzt läuft die DB-Löschung (inkl. abhängiger
Zeilen) zuerst in einer Transaktion, die Datei wird erst nach
erfolgreichem Commit entfernt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Statt einer Datei pro Job (wie bisher archivmail-ocr-pause) bündelt
/etc/cron.d/archivmail jetzt alle archivmail-Wartungsjobs in einer Datei,
analog zu /etc/cron.d/piler. Neue Jobs (Purge, nächtlicher Reindex,
Watch-Folder-Import) werden hier künftig als weitere Zeile ergänzt statt
in eigenen Dateien.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
paused_hours konnte bisher nur über einen vollen Prozess-Restart geändert
werden, was SMTP/IMAP/API unnötig unterbricht. Worker.pausedHours ist jetzt
ein atomic.Pointer mit SetPausedHours(); SIGHUP liest config.yml neu und
aktualisiert nur die OCR-Pausenzeit im laufenden Prozess. Neue
deploy/cron.d/archivmail-ocr-pause(.sh) lässt Admins die Pausenzeiten direkt
in der Cron-Datei pflegen und löst per systemctl reload statt restart aus.
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.
PageSkeleton, das shadcn Toast-System (toaster/sonner/toast/use-toast)
und das sonner-Paket waren im Code nirgends importiert. Build erfolgreich
verifiziert nach Entfernung.
systemctl is-active zeigt nur, dass der Prozess läuft — nicht, dass er
tatsächlich auf Anfragen antwortet (z.B. bei hängendem DB-Connect).
Beide Dienste werden jetzt per HTTP gegen /api/health (Backend) bzw.
Port 3000 (Frontend) mit Retry (15x1s) geprüft, bevor das Update als
erfolgreich gemeldet wird.
Der automatische Reindex lief bisher bei jedem Deploy mit (volle
Mailmenge), obwohl er nur einmalig für die Xapian→Manticore-Migration
gedacht war. Neue Mails werden ohnehin synchron beim Import indiziert
(IndexSync). Reindex bei Bedarf (z.B. Schema-Änderungen) jetzt manuell.
Kritischer Sicherheitsbug: Auditoren mit zugewiesenem Tenant sahen Mails
und Audit-Log-Einträge anderer Tenants (DSGVO-relevant). auditor wird
jetzt analog zu domain_auditor pro Tenant gescoped, sofern tenant_id
gesetzt ist (Abwärtskompatibilität: ohne tenant_id bleibt der bisherige
globale Zugriff erhalten). Betrifft Mail-Suche, Mail-Detailzugriff,
Export, eDiscovery, Threads, OCR sowie das Audit-Log (DB + tamper-evidentes
Flat-File), inkl. Befüllung von tenant_id an allen Audit-Log-Schreibstellen.
Behebt 5 von 7 gemeldeten Schwachstellen (picomatch ReDoS/Method-Injection,
mehrere Next.js-Advisories) via semver-konformer Patch-Updates in
package-lock.json. Verbleibende 2 moderate (postcss <8.5.10) sind in
next@16.2.9 selbst gebündelt, kein stabiler Fix verfügbar (nur 16.3.0
canary/preview) — npm audit fix --force würde next auf 9.3.3 downgraden.
Filtert die Mails des Nutzers (From/To/Cc/Bcc) jetzt serverseitig via
req.AnyAddress vor LIMIT/OFFSET im Index, statt wie bisher per
Post-Filter nach der Paginierung. total und totalPages stimmen damit
mit den tatsächlich sichtbaren Treffern überein.
Der Admin-Endpoint "Firewall aktivieren" (POST /api/admin/security/fix,
enable_firewall) ruft "nft -f /etc/nftables.conf" auf. flush ruleset
benötigt CAP_NET_ADMIN, das fehlte bisher in der systemd-Unit, wodurch
der Aufruf mit "Operation not permitted" fehlschlug.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Architektur-Entwurf: neue dsgvo_requests-Tabelle, Admin-Workflow "DSGVO-Anfragen"
(Antrag -> Suche -> retain_until-Prüfung -> Ablehnung/Löschbar -> Export/Löschung),
Erweiterung des Manticore-Index um cc_addr/bcc_addr (inkl. Reindex-Hinweis). Status
auf In Progress gesetzt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PROJ-51 (Aufbewahrungsfristen nach Dokumentenart, inkl. minimaler PROJ-43-Basis)
ist auf Test (132) und Produktion (131) deployed und verifiziert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- search_handlers.go: retain_until_source wird nur noch an Rollen != user
ausgegeben, um interne Archivierungsregel-IDs nicht an normale
Endbenutzer zu exponieren
- cmd_status.go: archivmail status zeigt [WARN] statt [OK] wenn Detail
mit "WARNUNG" beginnt (z.B. PROJ-51 Retention-Check); Exit-Code/r.OK
bleibt unverändert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fuehrt archiving_rules ein (PROJ-43-Basis: Tabelle + CRUD-API + Admin-UI) und
erweitert die Retention-Logik (PROJ-34) um Regel-basierte Fristen, eine
globale Mindestfrist (min_retention_days) sowie Nachvollziehbarkeit der
Frist-Quelle (retain_until_source) in API und Mail-Detailansicht.
Architektur-Entwurf: archiving_rules-Tabelle (minimale PROJ-43-Basis) + CRUD-API +
Regel-Verwaltung im Admin-Bereich, erweitert um retention_days pro Regel und globale
Mindest-Aufbewahrungsfrist (min_retention_days). Status auf In Progress gesetzt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Erfolgreich auf Test (192.168.1.132) und Produktion (192.168.1.131)
ausgerollt und verifiziert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
storage.loadKey() startet bei fehlendem/unlesbarem/ungültigem Keyfile
weiterhin unverschlüsselt (kein Hard-Fail), aber:
- einmalige WARN-Logzeile beim Start mit konkretem Grund
- neuer Healthcheck-Prüfpunkt "Encryption" in archivmail status
- Dashboard-API liefert encryption.enabled
- README: GoBD-Hinweis zu storage.keyfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Erfolgreich auf Test (192.168.1.132) und Produktion (192.168.1.131)
ausgerollt und verifiziert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gleiches Muster wie bei IMAP (730099d): domain_admin konnte POP3-Konten
fremder Tenants auflisten, löschen und Importe/Progress fremder Tenants
ansehen, da pop3_accounts keine tenant_id hatte und Store.List() für
Admins ungefiltert alle Konten lieferte.
- pop3_accounts: neue Spalte tenant_id (ALTER TABLE ADD COLUMN IF NOT EXISTS)
- Store.List() filtert nach tenant_id, außer für superadmin
- Store.Create() setzt tenant_id beim Anlegen
- delete/start-import/progress prüfen zusätzlich tenantAccessAllowed()
domain_admin sah und konnte IMAP-Konten (inkl. Credentials) fremder
Tenants auflisten, löschen, synchronisieren und umkonfigurieren, da
Store.List() für Admins ungefiltert alle Konten lieferte und die
Einzelhandler nur den Owner, nicht den Tenant prüften.
- Store.List() filtert jetzt nach tenant_id, außer für superadmin
- Store.Create() setzt tenant_id beim Anlegen
- Alle Einzelhandler (delete/start-import/progress/sync/update)
prüfen zusätzlich tenantAccessAllowed()
ldap_url kommt via LEFT JOIN tenant_ldap und ist NULL für Mandanten
ohne LDAP-Konfiguration. Scan in *string schlug fehl ("cannot scan
NULL into *string") und ließ GET /api/admin/quotas mit 500 fehlschlagen
("Quota-Daten konnten nicht geladen werden").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Permanenter Link zur Admin-Login-Seite unterhalb der bestehenden
Links (Passwort vergessen / Registrieren), statt nur als Hinweis
nach einem fehlgeschlagenen Admin-Login-Versuch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IMAP- und POP3-Importer haben Mails immer nur in emails_global
indexiert (TenantID nie gesetzt, idxMgr.Global() statt
ForTenant(tenantID)). Dadurch fehlten neue Mails ab dem letzten
Server-Neustart im Tenant-Index (Suche zeigte veraltete Ergebnisse).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Signup ohne Invite-Token gibt 400 zurück (war: optional)
- Use() statt Peek() vor User-Erstellung: verhindert TOCTOU bei parallelen
Requests mit demselben Token und Enumeration via "Token noch gültig?"
- invite_used Audit-Eintrag ergänzt
- Doppeltes IsConfigured()-Check entfernt
- Frontend: ohne ?invite= im URL wird Formular nicht gerendert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Serves the static OpenAPI YAML via go:embed. Completes the last
open acceptance criterion for PROJ-13. PROJ-44 marked Deployed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SEC: requireMailAccess auf GET /api/threads/{threadID} — superadmin/domain_admin konnten Mail-Metadaten lesen
- SEC: requireMailAccess auf POST /api/export/ediscovery — superadmin/domain_admin konnten bis zu 10k EML exportieren
- SEC: V1-API user-role Keys müssen 'contact=' angeben — verhindert vollständige Tenant-Enumeration
- SEC: Domain-Regex-Validierung in handleCertACME vor filepath.Join und certbot-Aufruf
- docs: README und config.test.yml auf Manticore Search aktualisiert (kein Xapian mehr)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PMG mit German-Locale erzeugt "So, 24 Aug 2025 00:05:17 +0200" statt
"Sun, ..." — Go's net/mail und alle bisherigen Fallbacks scheitern daran.
Fix: Wochentag-Präfix (≤3 Zeichen vor dem ersten Komma) abschneiden
und erneut mit den numerischen Offset-Formaten parsen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mailparser: weitere Layouts (Timezone +02:00 mit Doppelpunkt, ohne Sekunden)
storage: GetReceivedAts() für Batch-Lookup von received_at
search_handlers: received_at als Fallback wenn pm.Date.IsZero()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js/Turbopack kann sich den Workspace-Root falsch ableiten wenn
im Parent-Verzeichnis des Build-Dirs eine package-lock.json liegt
(z.B. durch alten Deploy-Stand). __dirname als expliziter Root behebt
die Warnung dauerhaft.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
toLocaleDateString → toLocaleString mit dateStyle+timeStyle, zeigt
jetzt z.B. "11.05.2026, 00:05" statt nur "11.05.2026".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
resolveTenant() now tries RCPT TO first, then falls back to parsing
To/Cc/From headers. Needed because BCC-journaled mails arrive with
RCPT TO = the archive's own BCC address, not the real recipient's domain.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- scheduler.go: BUG-1 fix — preserve stored uid_validity when server returns 0
- scheduler.go: BUG-2 fix — replace inline switch with DecideResync() call
- scheduler.go: SetAuditLogger wired; imap_uidvalidity_reset audit event
- cmd_reindex.go: read existing attachment_text before IndexSync to prevent
Manticore REPLACE INTO from wiping OCR text written by the OCR worker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Strukturbug auf 132 gefunden: Tenant-User (Rolle user) sahen ihren
OCR-Text nicht, obwohl ocr_chars>0 in PostgreSQL stand. Ursache:
- OCR-Worker hat in den per Job.TenantID gewaehlten Index geschrieben.
Beim Reprocess via CLI kam TenantID aus dem Submitter-Kontext und
konnte vom in emails.tenant_id gespeicherten Wert abweichen.
- /ocr-text-Endpoint hat fuer die Index-Auswahl session.TenantID
benutzt. Bei Admin/Auditor (nil Session-Tenant) wurde immer global
gelesen, auch wenn die Mail einem Tenant gehoert.
Fix: Beide Stellen lesen jetzt die TenantID **immer** aus
storage.GetTenantForMail(emails.tenant_id) und routen den
Manticore-Index entsprechend. ACL-Check im Endpoint bleibt
unveraendert auf session.TenantID == mail.tenant_id — die
Tenant-Isolation wird nicht aufgeweicht.
Edge cases:
- Mail mit tenant_id NULL: GetTenantForMail liefert nil -> globaler
Index (vorher und nachher gleich).
- DB-Fehler beim Lookup: faellt auf nil zurueck -> globaler Index,
liefert leeren Text fuer Tenant-Mails -> 404. Safe (keine
Querleckage zwischen Tenants).
CALL SNIPPETS liefert einen anderen MySQL-Pakettyp als SELECT, den der
Go-Treiber (go-sql-driver/mysql) mit "malformed packet" ablehnt.
SELECT SNIPPET(text, query) FROM table ist die korrekte Alternative
fuer Manticore 25.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Die enrichedHit-Struct in search_handlers.go fehlten die PROJ-44-Felder
Snippet und MatchField, sodass die vom Index berechneten Snippets in der
API-Response verworfen wurden.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Manticore 25.0.0 wirft SNIPPETS() expects exactly 3 arguments wenn
zusätzliche Options-Strings übergeben werden. Standard-Marker <b>/<b/>
sind Manticore-Default, daher sind keine Options nötig.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bisher haben nur der SMTP-Pfad und der Boot-Backfill ocrWorker.Submit
gerufen. IMAP- und POP3-Importer riefen nur idx.IndexSync auf —
neue Mails blieben dadurch dauerhaft in ocr_status='pending' (auf 132
44 Tage 54 Mails so haengen geblieben).
Fix: Importer-Strukturen bekommen einen optionalen ocrSubmit-Callback,
in main.go via SetOCRSubmit gehookt. Kein Import von internal/ocr in
die Importer-Packages -> kein Risiko von Cycles. Submit ist
non-blocking; bei Mails ohne Attachments markiert der Worker selbst
'skipped'.
GET /api/mails/{id} liefert jetzt ocr_status + ocr_chars fuer das GUI-
Badge und den konditionalen Download-Button. Die externe v1-API
(/api/v1/mails/{id}) bekommt zusaetzlich ocr_status, damit CRM-
Integrationen entscheiden koennen, ob ein OCR-Text-Pull lohnt.
Hit-Struct um Snippet + MatchField erweitert. enrichHitsWithSnippets
fuellt diese pro Treffer: detectMatchField probt subject>body>
attachment_text>attachment_names>from_addr>to_addr; buildSnippet ruft
CALL SNIPPETS mit <b>-Markern. Snippet-Fehler droppen den Treffer nicht.
AttachmentTextReader-Interface + Manticore-Implementation
GetAttachmentText liefert den indexierten OCR-Text fuer den neuen
/ocr-text-Endpoint.
DB-Schema bekommt eine idempotente ocr_chars BIGINT-Spalte (Default 0).
SetOCRResult schreibt status und chars atomar; GetOCRMeta liest beide
mit COALESCE-Defaults. Der OCR-Worker ersetzt jeden SetOCRStatus-Call
durch SetOCRResult und uebergibt die extrahierte Zeichenzahl bei 'done'.
- Zweite Zeile unter dem Betreff zeigt Manticore-Snippet
mit <b>-Highlights, gerendert via dangerouslySetInnerHTML
ueber sanitizeSnippet
- Quellen-Badge je match_field (Subject, Body, PDF-Anhang,
Dateiname, Absender, Empfaenger) als kleines Tailwind-Pill
- Nur sichtbar wenn das Backend ein snippet zurueckliefert
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- OcrBadge neben dem Verifikations-Status im Mail-Header
- "OCR-Text"-Button (lucide FileText) in der Action-Leiste,
sichtbar nur bei ocr_status=done und ocr_chars>0
- Tooltip via title-Attribut zeigt erkannte Zeichenzahl
- Pending-/Not-Available-Antworten werden als Alert angezeigt
("OCR laeuft noch, bitte gleich nochmal versuchen")
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rendert OCR-Status als shadcn Badge mit passender Farbe
(done=gruen, failed=rot, skipped=grau, pending=blau)
- disabled und undefined rendern null, damit die Komponente
unbedingt eingebunden werden kann
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- MailDetail um ocr_status/ocr_chars erweitert
- SearchHit um snippet + match_field erweitert
- Neue API-Funktionen getOCRTextDownloadURL und downloadMailOCRText
inkl. 202/404-Handling fuer pending/not-available
- src/lib/sanitize.ts: sanitizeSnippet escaped HTML und laesst nur
<b>-Tags fuer Manticore-Highlights durch
- Re-exports in src/lib/api/index.ts ergaenzt
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Vorheriger Fix (int64-Cast) erzeugte für die obere uint64-Hälfte negative
Werte. Manticore weist negative IDs beim INSERT/REPLACE zurück
("Negative document ids are not allowed"), nur SELECT akzeptiert sie.
Lösung: Bit-Mask 0x7FFFFFFFFFFFFFFF — Top-Bit immer 0, Result in
[0, 2^63-1]. 63-Bit-Hash-Space reicht für jede realistische Mail-Anzahl.
Manticore akzeptiert in `id`-bigint nur signed int64. Der mysql-Treiber
serialisiert Parameter als Dezimal-String → uint64-Werte > int64.MaxValue
führten zu "number ... is out of range". Fix: int64(h.Sum64())
verlustfreier Bit-Cast — bestehende Dokumente bleiben erreichbar.
Auch: PROJ-35-Spec auf In Progress + Implementation Notes/Pitfalls/QA-Block,
INDEX.md-Status-Update.
Mit systemd ProtectSystem=strict ist /tmp fuer den Service read-only.
ocr.SetTempDir(storage_path/ocr-tmp) nutzt einen RW-Pfad innerhalb der
ohnehin freigegebenen ReadWritePaths.