# SRC-11 — Feld-Whitelist-Query-Builder für Suchindex-Zugriff: Prüfprotokoll Datum: 2026-09-01 Host: 192.168.1.131 (Build/Test/Lint), rsync + ssh Paket: `mail/internal/search` (`fields.go`, `facets.go`) ## Umsetzung Grundlage war bereits vorhanden (SRC-01/SRC-05): statische `FieldXxx`- Konstanten in `fields.go`, Suchanfragen ausschließlich über Manticores strukturierte HTTP-JSON-API (kein SQL-String-Zusammenbau). Was fehlte, war Akzeptanzkriterium 2: die Facetten-Whitelist war eine `[]string` (`FacetFields`), gegen die `isFacetField` per Schleife prüfte — eine klassische "Whitelist-Funktion", genau das Muster, das `known-issues-archivmail.md` #12 und `known-issues-archivdms.md` #10 als unzureichend benennen (ein vergessener/fehlerhafter Eintrag in der Liste lässt unbemerkt alles durch). **Neu:** `FacetField` ist ein eigener, geschlossener Typ (`fields.go`). `FacetField.IsValid()` entscheidet über ein erschöpfendes `switch/case` auf den vier Konstanten (`FacetFieldSender`, `FacetFieldMailbox`, `FacetFieldAttachmentType`, `FacetFieldTag`) — keine Liste mehr, die durchsucht wird und die man vergessen könnte zu pflegen. `ParseFacetField` ist die einzige vorgesehene Stelle, um aus einer externen Zeichenkette (z. B. künftig ein HTTP-Query-Parameter) ein `FacetField` zu machen. `FacetFilter.Field` ist jetzt `FacetField` statt `string`. `buildFilteredMust` (einzige Stelle, die Filter-Feldnamen in eine Suchanfrage einbaut) prüft `f.Field.IsValid()` statt Listenmitgliedschaft. `isFacetField` (die alte Listenfunktion) ist entfernt — es gibt keine Liste mehr, die die Zulässigkeitsentscheidung trifft, nur noch das `switch/case` in `IsValid()`. ## Pflichtprüfung 1: Versuch, ein nicht in der Whitelist enthaltenes Feld anzufragen, wird abgewiesen statt stillschweigend ignoriert `TestBuildFilteredMust_RejectsUnknownField` (`search/src11_test.go`): zwei Fälle — ein reales Suchfeld, das aber KEIN Facettenfeld ist (`tenant_slug`), und ein frei erfundenes Feld (inkl. eines absichtlich SQL-injection-artigen Strings, um zu zeigen, dass er nicht einmal in die Fehlermeldung unverarbeitet "verschwindet", sondern sauber als Fehler zurückkommt) — beide werden mit Fehler abgelehnt, kein stillschweigendes Ignorieren. `TestBuildFilteredMust_AcceptsAllWhitelistedFields` stellt sicher, dass die Prüfung nicht zu streng ist (alle vier realen Facettenfelder funktionieren). Ergebnis: **BESTANDEN**. ## Pflichtprüfung 2: Code-Review bestätigt: kein dynamischer Spalten-/Tabellenname wird per String-Zusammenbau erzeugt `TestNoDynamicFieldNameConstruction` (`search/src11_test.go`): automatisiertes Code-Review — `facets.go` und `fields.go` enthalten in keiner Codezeile (Kommentarzeilen ausgenommen, dort nur erklärender Text über den zu vermeidenden Fehler) ein `fmt.Sprintf`. Ergänzt um `TestFacetField_ClosedSetEvenViaDirectTypeConversion` (Akzeptanzkriterium 2 wörtlich: die Whitelist ist NICHT die einzige Absicherung — selbst ein `FacetField`-Wert, der nicht über `ParseFacetField` entstanden ist, sondern durch direkte Typkonvertierung, wird von `IsValid()` zuverlässig abgelehnt) und `TestParseFacetField_OnlyAcceptsKnownStrings`. Ergebnis: **BESTANDEN**. ## Akzeptanzkriterien 1. **Spalten-/Feldnamen für dynamische Query-Teile stammen ausschließlich aus statischen Konstanten bzw. einem geschlossenen Enum/Switch-Typ**: `FacetField` + die vier `FacetFieldXxx`-Konstanten, durch Pflichtprüfung 2 belegt. 2. **Whitelist ist nicht die einzige Absicherung**: `IsValid()` ist ein erschöpfendes `switch/case`, keine Listen-Iteration mehr — durch Pflichtprüfung 1+2 belegt. 3. **Entscheidung dokumentiert: Mail-eigene Implementierung, keine geteilte Utility mit dem DMS-Board**: siehe unten. ### Zu Akzeptanzkriterium 3 Diese Kachel implementiert den Query-Builder ausschließlich innerhalb von `mail/internal/search` — keine neue geteilte Utility mit dem DMS-Board angelegt. Konsistent mit der bereits im Ticket-Prompt genannten, vorab getroffenen Entscheidung (`nexarch-state.json` → `bewusst_nicht_zentralisiert`), Suche/OCR zwischen Mail und DMS nicht zu zentralisieren. ## Build/Vet/Lint/Test — Gesamtmodul ``` go build ./... → OK go vet ./... → OK golangci-lint run ./... → 0 issues go test ./... -p 1 (TEST_TENANT_DSN, TEST_MANTICORE_URL gesetzt) → alle Pakete ok ``` Keine Regression — insbesondere `mail/internal/savedsearch` (Konsument von `search.FacetFilter`) unverändert grün: die Typänderung von `Field string` zu `Field FacetField` ist für bestehende Aufrufer, die den untypisierten String-Konstanten `FieldSender` usw. übergeben, verhalten sich unverändert (Go erlaubt die implizite Umwandlung untypisierter Konstanten). ## Ergebnis SRC-11 erfüllt alle Akzeptanzkriterien mit echten, ausgeführten Nachweisen. Freigeschaltet: QA-04 (zusammen mit ARC-06).