Audit-Haerten: Fork, Feldabdeckung, Refresh-Rauschen, Route (Pentest R166)
R166-01 (HIGH): Der GET_LOCK-Ansatz gab die Sperre im finally INNERHALB des Transaktions-Callbacks frei, also vor dem COMMIT. Im Fenster Release-Commit las der naechste Schreiber ein noch nicht sichtbares Kettenende - zwei Zeilen hingen am selben Vorgaenger. Meine vorherige Messung war zu schwach: sie suchte Luecken zwischen Nachbarn, nicht Forks. Fix: einzeiliger Mutex AuditChainLock mit FOR UPDATE (InnoDB-Zeilensperren fallen erst beim COMMIT) plus isolationLevel ReadCommitted. Belegt im Direktvergleich mit geweitetem Fenster: Release-vor-Commit forkt, Zeilensperre nicht. R166-02 (MEDIUM): Der Hash deckte nur 7 Felder ab. changesBefore/After, success, ipAddress, resourceLabel, dataSubjectId, userId/customerId waren ungeschuetzt - ein Einzeledit dort blieb unsichtbar. Fix: hashVersion + generateHashV2 ueber alle Inhaltsspalten. Bestandszeilen behalten Version 1 und bleiben ohne Rehash gueltig. Verifiziert: 5/5 zuvor ungeschuetzte Felder werden jetzt erkannt. R166-03 (LOW): "kein Cookie" (normaler Erstbesuch) wurde als HIGH/abgelehnt gefuehrt - jetzt eigener Ausgang mit LOW. Nur echte Ablehnung bleibt HIGH. R166-04 (LOW, pre-existing): GET /retention-policies wurde von GET /:id verschluckt. Konkrete Routen jetzt vor der Parameter-Route. Design-Empfehlungen: runRetentionCleanup schreibt ein Loeschungs-Manifest (ID-Bereich, Anzahl, Policy, Cutoff) als eigenen verketteten Eintrag - Luecken ausserhalb bleiben erklaerungsbeduerftig. rehashAll schreibt einen Marker. Verifiziert: 50 parallele Schreiber -> 50/50, 0 Forks, alle V2, manipuliert 0, Luecken unveraendert 7. tsc + vite build gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,43 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🛡️ Audit-Haerten: Fork, Feldabdeckung, Refresh-Rauschen, Route (Pentest R166)** (2026-08-18)
|
||||
- **R166-01 (HIGH) – Kette forkte weiter.** Mein GET_LOCK-Ansatz gab die
|
||||
Sperre im `finally` INNERHALB des Transaktions-Callbacks frei, also VOR dem
|
||||
COMMIT. Im Fenster Release↔Commit las der naechste Schreiber ein noch nicht
|
||||
sichtbares Kettenende → zwei Zeilen am selben Vorgaenger. Meine
|
||||
„100 parallel → 0 Brueche“-Messung war zu schwach: sie suchte Luecken
|
||||
zwischen Nachbarn, nicht Forks, und das Fenster ist lokal sehr schmal.
|
||||
Fix: einzeiliger Mutex `AuditChainLock` + `FOR UPDATE`; InnoDB-Zeilensperren
|
||||
fallen erst beim COMMIT. Dazu `isolationLevel: ReadCommitted`, damit der
|
||||
Lesevorgang den frisch festgeschriebenen Stand sieht.
|
||||
**Belegt** im Direktvergleich mit kuenstlich geweitetem Fenster:
|
||||
Release-vor-Commit → Fork, Zeilensperre → kein Fork.
|
||||
- **R166-02 (MEDIUM) – Hash deckte nur 7 Felder.** `changesBefore/After`
|
||||
(die eigentliche Nutzlast), `success`, `ipAddress`, `resourceLabel`,
|
||||
`dataSubjectId`, `userId`/`customerId` waren NICHT gehasht – ein Einzeledit
|
||||
dort blieb unsichtbar. Fix: `hashVersion` (Migration `20260818150000`) +
|
||||
`generateHashV2` ueber alle Inhaltsspalten. Bestandszeilen behalten
|
||||
Version 1 und bleiben ohne Rehash gueltig. `rehashAll` schreibt V2.
|
||||
Verifiziert: Manipulation an success/changesAfter/ipAddress/resourceLabel/
|
||||
dataSubjectId wird jetzt **5/5 erkannt**, Altbestand weiter gueltig.
|
||||
- **R166-03 (LOW)** – „kein Cookie“ (normaler Erstbesuch) wurde als
|
||||
`HIGH / abgelehnt` gefuehrt. Jetzt eigener Ausgang: LOW + Label
|
||||
„ohne vorliegenden Token“. Nur echte Ablehnung bleibt HIGH.
|
||||
- **R166-04 (LOW, pre-existing)** – `GET /retention-policies` wurde von
|
||||
`GET /:id` verschluckt. Konkrete Routen jetzt konsequent vor der
|
||||
Parameter-Route, mit Warnhinweis im Code.
|
||||
- **Design-Empfehlungen umgesetzt:** `runRetentionCleanup` schreibt ein
|
||||
Loeschungs-Manifest (Bereich `fromId`–`toId`, Anzahl, Policy, Cutoff) als
|
||||
eigenen verketteten Eintrag – Luecken ausserhalb bleiben damit
|
||||
erklaerungsbeduerftig; `rehashAll` schreibt einen Marker, dass die
|
||||
Beweiskraft der Vergangenheit zurueckgesetzt wurde.
|
||||
- Verifiziert: 50 parallele Schreiber → 50/50, 0 Forks, alle V2;
|
||||
manipuliert 0, Luecken unveraendert 7. `tsc` + `vite build` gruen.
|
||||
- **Offen (bewusst nicht umgesetzt):** externer Anker (HMAC mit Schluessel
|
||||
ausserhalb der DB). Adressiert Full-DB-Compromise, erfordert aber
|
||||
Schluesselverwaltung/Rotation im Deployment → Entscheidung des Betreibers.
|
||||
|
||||
- [x] **🔎 Audit-Pruefung: „manipuliert“ von „Luecke“ getrennt + Retention fuer Routine-Auth** (2026-08-18)
|
||||
- **Problem 1 (Deutbarkeit):** `verifyIntegrity` warf zwei voellig
|
||||
unterschiedliche Befunde in einen Topf und meldete beides als
|
||||
|
||||
Reference in New Issue
Block a user