Quick task 260811-f9i: plan and summary of the fix, STATE.md row, and the UAT test 2 result. Test 2 was the read-only A2 check against the real directory -- it turned assumption A2 from "unverified" into "false as implemented" and surfaced a defect that would have deleted every AD-bound group on the first real sync. Also records why the defect survived review: the spec mocks built their expected filter with the same escape helper the production code used, so the test asserted self-consistency rather than directory behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.3 KiB
quick_id, slug, date, status, relates_to, severity
| quick_id | slug | date | status | relates_to | severity |
|---|---|---|---|---|---|
| 260811-f9i | fix-objectguid-existence-sweep-to-use-eq | 2026-08-11 | planned | 16-ad-gruppen-synchronisation | critical |
Quick Task: objectGUID-Existenzprüfung auf EqualityFilter mit Rohbytes umstellen
Problem
syncBoundGroupsForTenant() (apps/api/src/ldap/ldap.service.ts:1318) baut den
Filter der Existenzprüfung als String:
const filter = `(objectGUID=${LdapService.escapeLdapFilterBuffer(guidBuffer)})`;
escapeLdapFilterBuffer() erzeugt \1e\4b\4d…. ldapts wandelt diese Sequenzen
beim Parsen des Filter-Strings nicht in Rohbytes zurück, also steht auf dem Draht
ein anderer Vergleichswert als gemeint. Die Suche trifft nie.
Am 2026-08-11 read-only gegen das echte AD (balios.ctl.local) gemessen, Sonde
CN=Domain Admins,CN=Users,DC=ctl,DC=local, GUID 1e4b4df0b3caeb4c9b49fddf05227e10:
| Variante | Treffer |
|---|---|
(objectGUID=\1e\4b…) — heutiger Code |
0 |
(objectGUID=\1E\4B…) — Großbuchstaben |
0 |
new EqualityFilter({ attribute: 'objectGUID', value: <Buffer> }) |
1, korrekte DN |
EqualityFilter mit escaptem String als Wert |
0 |
Kontrolle (cn=Domain Admins) |
1 |
Der Domain Controller ist also in Ordnung; die Annahme A2 aus 16-RESEARCH.md
ist für die gewählte Umsetzung falsch.
Auswirkung
Der Ablauf in syncBoundGroupsForTenant() ist: schmale Suche über die
konfigurierten Base-DNs → kein Treffer → weite Suche über domainRoots
(WR-03-Absicherung) → kein Treffer → Gruppe gilt als im AD gelöscht
(ldap.service.ts:1432). Beide Suchen verwenden denselben kaputten Filter.
Folge: der erste echte Sync-Lauf hätte jede AD-gebundene Gruppe gelöscht, samt
GroupMembership und ModuleGrant per Kaskade, und vorher die
Standardgruppen-Markierung weitergereicht (D-06). Der Bericht hätte das als
reguläre Löschung ausgewiesen. Auf alpha ist nichts passiert — dort lief nie ein
Sync.
Tasks
EqualityFilterausldaptsimportieren; insyncBoundGroupsForTenant()den String-Filter durch einEqualityFilter-Objekt mit dem rohen 16-Byte-Buffer ersetzen. Beide Schleifen (Base-DNs unddomainRoots) teilen sich weiterhin denselben Filterwert.escapeLdapFilterBuffer()entfernen — nach Task 1 ohne Aufrufer, und die Methode ist genau die Falle, in die der Code gelaufen ist.escapeLdapFilterValue()(String-Werte, RFC 4515) bleibt unberührt, die ist korrekt und wird weiter genutzt.- Die Mocks in
ldap.service.spec.ts, die auf dem escapten String matchen (Zeilen 579/586, 1060, 1587), auf den Buffer-Wert des Filterobjekts umstellen. - Regressionstests ergänzen: die Existenzprüfung übergibt ein Filterobjekt mit
attribute: 'objectGUID'und einemBuffer-Wert, der byteweise dem gespeichertenldapObjectGuidentspricht — und kein String. Ein Test hält ausdrücklich fest, dass ein String-Filter der Form(objectGUID=…)nicht mehr vorkommt.
Erfolgskriterium
pnpm --filter @tessera/api test läuft grün, und ein Test schlägt fehl, sobald
jemand wieder einen escapten String als GUID-Filter übergibt.
Offen nach diesem Fix
Der Fix belegt die Filtermechanik, nicht das Verhalten des Sync-Laufs. UAT 1, 3 und 4 (Umbenennen und Löschen im AD) bleiben offen und sollen gegen einen Wegwerf-Domänencontroller nachgezogen werden.