--- quick_id: 260811-f9i slug: fix-objectguid-existence-sweep-to-use-eq date: 2026-08-11 status: complete relates_to: 16-ad-gruppen-synchronisation severity: critical commits: - d2019dc fix(ldap): search objectGUID by raw bytes, not an escaped filter string --- # Summary: objectGUID-Existenzprüfung auf Rohbytes umgestellt ## Was gemacht wurde `syncBoundGroupsForTenant()` baut den Filter der Existenzprüfung jetzt als `EqualityFilter` über den rohen 16-Byte-Buffer statt als interpolierten `(objectGUID=\xx…)`-String. Beide Suchen — die schmale über die konfigurierten Base-DNs und die weite über die `domainRoots` (WR-03-Absicherung) — teilen sich denselben Filterwert, damit die Absicherung nicht erneut einen kaputten Filter erben kann. `escapeLdapFilterBuffer()` ist entfernt. An seiner Stelle steht ein Kommentar, der erklärt, warum die Methode nicht zurückkommen darf. `escapeLdapFilterValue()` (String-Werte nach RFC 4515) bleibt unverändert und weiter in Gebrauch. ## Wie der Fehler gefunden wurde Über UAT-Test 2 aus Phase 16 — read-only gegen das echte AD (`balios.ctl.local`), Sonde `CN=Domain Admins,CN=Users,DC=ctl,DC=local`, GUID `1e4b4df0b3caeb4c9b49fddf05227e10`: | Variante | Treffer | |---|---| | `(objectGUID=\1e\4b…)` — bisheriger Code | 0 | | `(objectGUID=\1E\4B…)` — Großbuchstaben | 0 | | `EqualityFilter` mit rohem Buffer | 1, korrekte DN | | `EqualityFilter` mit escaptem String als Wert | 0 | | Kontrolle `(cn=Domain Admins)` | 1 | Der Domain Controller war also nie das Problem. Annahme A2 aus `16-RESEARCH.md` galt für die gewählte Umsetzung nicht. ## Warum es durch Review und Tests gekommen ist Die Mocks in `ldap.service.spec.ts` haben den erwarteten Filter mit **derselben** `escapeLdapFilterBuffer()`-Funktion gebaut, die der Produktionscode benutzt hat. Damit war der Test tautologisch: er hat bestätigt, dass die Funktion sich selbst gegenüber konsistent ist, nicht dass ein Verzeichnis den Filter versteht. Der Code-Kommentar hat die Annahme sogar ausdrücklich als `[ASSUMED — nicht gegen ein echtes AD verifiziert]` markiert. Die neuen Tests prüfen jetzt die Form des Aufrufs statt seines Inhalts: der Filter muss ein Objekt mit `attribute: 'objectGUID'` und einem `Buffer`-Wert sein, und kein Suchaufruf des Laufs darf einen String-Filter mit `objectGUID=` tragen. ## Verifikation - `pnpm --filter @tessera/api test` — 568 Tests grün (40 Dateien) - `npx tsc --noEmit` — sauber - Gegenprobe: der alte String-Filter kurzzeitig wiederhergestellt → 8 Tests rot, darunter beide neuen. Die Regressionstests haben also Zähne. ## Was offen bleibt Der Fix belegt die Filtermechanik gegen ein echtes AD, nicht das Verhalten eines vollständigen Sync-Laufs. Offen bleiben aus `16-UAT.md`: - Test 1 (A1: GUID überlebt eine Umbenennung) — braucht Schreibrechte im AD - Test 3 (Umbenennung end-to-end) und Test 4 (Löschung end-to-end) - Der Teil von Test 7, der die Zahlenzeilen eines erfolgreichen Sync-Laufs prüft Nächster Schritt laut Absprache: ein Wegwerf-Domänencontroller im Container, gegen den Umbenennen und Löschen vollständig durchgespielt werden können.