diff --git a/backend/src/services/audit.service.ts b/backend/src/services/audit.service.ts index 886f29f3..4277141a 100644 --- a/backend/src/services/audit.service.ts +++ b/backend/src/services/audit.service.ts @@ -1297,6 +1297,34 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{ } tamperedEntries.push(...backlogTampered, ...backlogMissing); + // --------------------------------------------------------------------- + // Fehlender ANFANG des Protokolls. + // + // Die Verkettung wird zeilenweise gegen die Vorgaengerin geprueft - die + // erste Zeile hat keine, also fiel bisher NICHTS auf, wenn ein + // zusammenhaengender Anfang entfernt wurde. Kein Kettenbruch, kein Befund, + // `valid` blieb gruen. Das ist die stillste Loeschung von allen: Wer die + // aeltesten Eintraege loswerden will, muss nur vorne anfangen. + // + // Erkennbar ist es trotzdem: Die allererste Zeile eines Protokolls wird ohne + // Vorgaenger geschrieben und traegt deshalb einen leeren `previousHash`. + // Traegt die erste vorhandene Zeile einen Wert, hat es eine Vorgaengerin + // gegeben - und die ist weg. + // + // Nur bei Pruefung des GESAMTEN Bereichs; bei `fromId` ist ein gefuellter + // `previousHash` selbstverstaendlich und kein Befund. + if (fromId === undefined && logs.length > 0 && logs[0].previousHash) { + chainGaps.push(logs[0].id); + // Ein protokolliertes Loeschungs-Manifest erklaert auch diese Luecke; die + // Pruefung laeuft ueber denselben Weg wie bei jeder anderen. + if (!gapErklaert(0, logs[0].id)) { + unexplainedGaps.push(logs[0].id); + if (erwarteteVersion(logs[0].id) === 3) { + tamperedEntries.push(logs[0].id); + } + } + } + for (let i = 0; i < logs.length; i++) { const log = logs[i]; diff --git a/docs/todo.md b/docs/todo.md index 710b6a7a..8aac2938 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,28 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🕳️ Entfernter Protokoll-ANFANG wurde nicht erkannt** (2026-08-26) + - Gefunden bei der Vorbereitung des Prod-Siegels: Die Verkettung wird + zeilenweise gegen die Vorgängerin geprüft – die **erste** Zeile hat keine, + also fiel bisher **nichts** auf, wenn ein zusammenhängender Anfang des + Protokolls entfernt wurde. Kein Kettenbruch, kein Befund, `valid` blieb + grün. Die stillste Löschung von allen: Wer die ältesten Einträge loswerden + will, muss nur vorne anfangen. + - Erkennbar ist es trotzdem: Die allererste Zeile wird ohne Vorgänger + geschrieben und trägt einen leeren `previousHash`. Trägt die erste + **vorhandene** Zeile einen Wert, hat es eine Vorgängerin gegeben – und die + ist weg. Wird jetzt als Kettenlücke an dieser Zeile geführt, mit derselben + Manifest- und Beglaubigungslogik wie jede andere Lücke. + - Nur bei Prüfung des Gesamtbereichs; mit `fromId` ist ein gefüllter + `previousHash` selbstverständlich (gegengeprüft, kein Fehlalarm). + - Getestet über HTTP: vollständiges Protokoll → `valid:true`; erste drei + Zeilen entfernt → **vorher** unverändert `valid:true`, **jetzt** + `valid:false`, `chainGaps:[4]`, wegen Hash-Version 3 zusätzlich als + Manipulation eskaliert. + - Auf Prod ausgeschlossen: Das Protokoll beginnt bei ID 1 (07.05.2026, + Inbetriebnahme), kein Cleanup, keine Neuberechnung. + - Datei: `backend/src/services/audit.service.ts` + - [x] **⏱️ Neuberechnung der Kette ist jetzt eine eigene Gegenbuch-Alarmbedingung (Pentest R186, Frage b)** (2026-08-26) - Der Tester fragte präzise: Löst `cleanup` → `rehash` **ohne** erneutes Siegeln, über **nicht versiegelten** Inhalt, etwas **Automatisches** aus –