Append-only per Trigger (nicht nur GRANT/REVOKE): audit_events_prevent_mutation()
wirft bei jedem UPDATE/DELETE auf audit_events eine Exception, unabhaengig
von der verbindenden Rolle (Akzeptanzkriterium 1).
internal/audit/four_eyes.go: Vier-Augen-Prinzip fuer sicherheitskritische
Entscheidungen (Loeschbestaetigung, Rechtevergabe), 1:1 nach archivdms-
Vorbild. Request erzeugt einen Klartext-Code (wird ausserhalb des Systems an
eine ZWEITE Person uebermittelt) und speichert nur dessen SHA-256-Hash.
Confirm sperrt die Zeile mit FOR UPDATE (Akzeptanzkriterium 2 — serialisiert
zwei gleichzeitige Bestaetigungsversuche, verhindert doppelte Ausfuehrung),
weist eine Bestaetigung durch dieselbe Person wie die anfordernde ab
(ErrSameActor, echtes Vier-Augen-Prinzip statt nur Code-Pruefung), und
vergleicht den Code timing-safe (Akzeptanzkriterium 3).
internal/audit/timingsafe.go: timingSafeEqual als projektweite Referenz-
implementierung (subtle.ConstantTimeCompare) fuer sicherheitsrelevante
Vergleiche — andere Module (z.B. Archive CMP-06 Freigabelinks) uebernehmen
dasselbe Muster laut IAM-02-Konvention.
Nebenbei behoben: AUD-01s eigener Test nutzte einen festen Tenant-Slug mit
DELETE-basiertem Cleanup — seit dem neuen Append-only-Trigger schlaegt dieses
Cleanup lautlos fehl, wodurch Zeilen sich ueber Testlaeufe hinweg summierten
und die Zaehl-Assertion brach. Auf eindeutigen Slug pro Lauf umgestellt
(direkte, notwendige Folge dieser Kachel, keine Umgestaltung von AUD-01
selbst).
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Direkter UPDATE/DELETE-Versuch von der Datenbank abgewiesen —
TestAppendOnly_RejectsUpdateAndDelete: beide Operationen scheitern,
Eintrag bleibt unveraendert erhalten. PASS.
2. Vier-Augen-Prinzip mit FOR-UPDATE-Lock race-frei unter parallelen
Anfragen — TestFourEyes_ConcurrentConfirmIsRaceFree: zwei gleichzeitige
Bestaetigungsversuche fuer denselben Vorgang, genau einer erfolgreich,
der andere ErrAlreadyDecided. PASS.
3. Timing-safe Vergleich per Laufzeitmessung stichprobenartig verifiziert —
TestTimingSafeEqual_NoEarlyExitTiming: Mismatch am Anfang (603µs) vs. am
Ende (574µs) ueber 20000 Iterationen, kein Hinweis auf Short-Circuit-
Vergleich (Ratio innerhalb Faktor 3 Toleranz). PASS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>