Files
schalli f0c763d3e2
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Successful in 47s
Tessera CI/CD / Build & Publish Images (push) Successful in 27s
docs(16): record the objectGUID sweep defect found by UAT test 2
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>
2026-08-11 11:06:27 +02:00

77 lines
3.1 KiB
Markdown

---
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.