Files
tessera-ctl/.planning/quick/260811-f9i-fix-objectguid-existence-sweep-to-use-eq/PLAN.md
T
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

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

  1. EqualityFilter aus ldapts importieren; in syncBoundGroupsForTenant() den String-Filter durch ein EqualityFilter-Objekt mit dem rohen 16-Byte-Buffer ersetzen. Beide Schleifen (Base-DNs und domainRoots) teilen sich weiterhin denselben Filterwert.
  2. 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.
  3. 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.
  4. Regressionstests ergänzen: die Existenzprüfung übergibt ein Filterobjekt mit attribute: 'objectGUID' und einem Buffer-Wert, der byteweise dem gespeicherten ldapObjectGuid entspricht — 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.