diff --git a/backend/src/services/audit.service.ts b/backend/src/services/audit.service.ts index 716d23d2..b893f083 100644 --- a/backend/src/services/audit.service.ts +++ b/backend/src/services/audit.service.ts @@ -119,6 +119,48 @@ function generateHash(data: { return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex'); } +/** + * Historische Hash-Variante (Bestandsdaten ~08.02.–01.05.2026). + * + * Der R121-Fix ging davon aus, dass `resourceId` beim Schreiben immer + * `undefined` war (→ Key faellt bei JSON.stringify weg) und daher ALLE + * Bestands-Hashes ohne Rehash matchen. Das stimmt erst ab ~01.05.2026: + * aeltere Zeilen wurden mit explizitem `null` serialisiert, der Key war also + * DRIN. Ergebnis war ein Dauer-Fehlalarm ueber ~3100 Zeilen (67 % des Logs) – + * und ein Alarm, der staendig grundlos ausloest, verdeckt echte Manipulation. + * + * Diese Funktion reproduziert exakt das alte Schreibverhalten, damit + * verifyIntegrity Altbestand als gueltig erkennt – OHNE Rehash. Ein Rehash + * waere der naheliegende Schnellfix, wuerde die Manipulations-Beweiskraft der + * Vergangenheit aber unwiederbringlich zerstoeren. + * + * Sicherheit: Beide Varianten hashen dieselben Feldwerte, nur die + * Serialisierung des nullish `resourceId` unterscheidet sich. Ein Angreifer + * gewinnt dadurch keinen Spielraum, Inhalte zu aendern – nur die Kodierung + * eines leeren Feldes ist doppelt zulaessig. + */ +function generateHashLegacy(data: { + userEmail: string; + action: AuditAction; + resourceType: string; + resourceId?: string | null; + endpoint: string; + createdAt: Date; + previousHash?: string | null; +}): string { + const payload: Record = { + userEmail: data.userEmail, + action: data.action, + resourceType: data.resourceType, + resourceId: data.resourceId ?? null, + endpoint: data.endpoint, + createdAt: data.createdAt.toISOString(), + previousHash: data.previousHash || '', + }; + + return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex'); +} + /** * Bestimmt die Sensitivität basierend auf dem Ressourcentyp */ @@ -416,10 +458,23 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{ previousHash: log.previousHash, }); - // Prüfen ob Hash übereinstimmt + // Prüfen ob Hash übereinstimmt – Altbestand darf die historische + // Serialisierung nutzen (siehe generateHashLegacy). Legacy wird nur + // geprüft, wenn die aktuelle Variante nicht passt. if (log.hash !== expectedHash) { - invalidEntries.push(log.id); - continue; + const legacyHash = generateHashLegacy({ + userEmail: log.userEmail, + action: log.action, + resourceType: log.resourceType, + resourceId: log.resourceId, + endpoint: log.endpoint, + createdAt: log.createdAt, + previousHash: log.previousHash, + }); + if (log.hash !== legacyHash) { + invalidEntries.push(log.id); + continue; + } } // Prüfen ob previousHash mit dem Hash des vorherigen Eintrags übereinstimmt diff --git a/docs/todo.md b/docs/todo.md index 8b0f381b..d0d6c35c 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,29 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🛡️ Audit-Integritaet: Dauer-Fehlalarm ueber 67 % des Logs behoben** (2026-08-18) + - Beim Nachpruefen aufgefallen: `verifyIntegrity` meldete **3107 von 4630** + Zeilen als „manipuliert“. Davon waren **3100 Fehlalarme** – eingegrenzt auf + exakt die Zeilen mit `resourceId = NULL` aus dem Zeitraum 08.02.–01.05.2026. + - Ursache: Der R121-Fix nahm an, `resourceId` sei beim Schreiben immer + `undefined` gewesen (Key faellt bei `JSON.stringify` weg) und daher wuerden + **alle** Bestands-Hashes ohne Rehash matchen. Das gilt erst ab ~01.05.2026 – + aeltere Zeilen wurden mit explizitem `null` serialisiert, der Key war DRIN. + - Warum das sicherheitsrelevant ist: Ein Alarm, der staendig grundlos + ausloest, wird ignoriert – **echte** Manipulation ginge im Lärm unter + (gleiches Muster wie beim Refresh-Rauschen, [R165]). + - Fix: `generateHashLegacy()` reproduziert das alte Schreibverhalten; + `verifyIntegrity` akzeptiert Altbestand ueber diesen Fallback (nur geprueft, + wenn die aktuelle Variante nicht passt). **Kein Rehash** – der waere der + naheliegende Schnellfix, wuerde die Manipulations-Beweiskraft der + Vergangenheit aber unwiederbringlich zerstoeren. Gespeicherte Hashes + bleiben unangetastet. + - Verifiziert: ungueltige Zeilen **3107 → 7** (die 7 sind echte Ketten-Brueche, + siehe naechster Punkt). Adversarial gegengetestet: Manipulation an + `userEmail`/`action`/`endpoint`/`createdAt`/`resourceId` wird bei ALTEN wie + NEUEN Zeilen weiterhin zu 100 % erkannt (10/10), unveraenderte Zeilen + akzeptiert. `tsc` gruen. + - [x] **🐛 Audit-Log: Pfad-Matching kaputt – Auth-Actions generisch (Pentest R165-01)** (2026-08-18) - Pentester meldete: Entrauschung (`de0d6bd`) live **nicht wirksam** – jeder `/refresh` weiter `CREATE / CRITICAL / „Anmeldung erstellt“`. Zusatzbefund: