rehash/cleanup: Vorzustand sichern + Bestaetigung verlangen (Pentest R170-01)

POST /audit-logs/rehash rechnet die Kette mit dem HMAC-Schluessel neu und macht
sie damit wieder stimmig - auch wenn sie vorher berechtigte Beanstandungen
enthielt. Der Anker schuetzt gegen einen DB-Schreiber ohne Schluessel, nicht
gegen einen Admin mit audit:admin. Der bisherige Marker hielt nur fest, DASS
rehasht wurde, nicht WAS dabei verschwand.

Fix 1: Vor dem Rehash wird verifyIntegrity() erhoben und samt Kettenkopf im
Marker gesichert - Anzahl geprueft, Listen der manipulierten Zeilen, der
Ketten-Luecken, der Luecken ohne dokumentierte Loeschung, der nicht pruefbaren.
Dazu ausloesender Benutzer und IP statt pauschal "system". Der Marker entsteht
nach dem Rehash, ist Teil der neuen Kette und signiert.

Fix 2: rehash verlangt {"confirm":"REHASH"}, cleanup verlangt
{"confirm":"CLEANUP"}. Beide wurden bei blinder Methoden-Erkundung per POST
unbeabsichtigt ausgeloest; ein tastender Aufruf laeuft jetzt in 400.

Verifiziert: blinder POST -> 400 ohne Wirkung; mit Bestaetigung laeuft der
Rehash und der Marker enthaelt Ausloeser, Vorbefund (1 manipuliert, 7 Luecken
mit exakten IDs) und Kettenkopf.

Hinweis: Der Test hat auf der DEV-Datenbank real rehasht, die dortigen
historischen Beanstandungen sind damit geglaettet. Staging/Prod unberuehrt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 21:16:08 +02:00
co-authored by Claude Opus 5
parent 791711ca58
commit 32c9efacda
3 changed files with 102 additions and 5 deletions
+32 -1
View File
@@ -198,7 +198,26 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
*/
export async function rehashAll(req: AuthRequest, res: Response) {
try {
const result = await auditService.rehashAll();
// Ausdrueckliche Bestaetigung verlangen (Pentest R170-01).
//
// Ein Rehash macht die Kette rechnerisch stimmig und setzt damit die
// Beweiskraft der Vergangenheit zurueck - das darf kein Nebeneffekt eines
// versehentlichen oder tastenden POST sein. Genau so wurde der Endpunkt
// bei einer Methoden-Erkundung unbeabsichtigt ausgeloest.
if (req.body?.confirm !== 'REHASH') {
res.status(400).json({
success: false,
error:
'Rehash setzt die Beweiskraft der bestehenden Einträge zurück und ist nicht ' +
'umkehrbar. Zum Bestätigen {"confirm":"REHASH"} mitsenden.',
});
return;
}
const result = await auditService.rehashAll({
userEmail: req.user?.email,
ipAddress: req.ip || (req.socket as any)?.remoteAddress,
});
res.json({
success: true,
data: result,
@@ -259,6 +278,18 @@ export async function updateRetentionPolicy(req: AuthRequest, res: Response) {
*/
export async function runRetentionCleanup(req: AuthRequest, res: Response) {
try {
// Auch hier ausdrueckliche Bestaetigung: Cleanup loescht Audit-Zeilen
// endgueltig und reisst dabei die Kette auf (Pentest R170-01).
if (req.body?.confirm !== 'CLEANUP') {
res.status(400).json({
success: false,
error:
'Cleanup löscht Audit-Einträge endgültig. Zum Bestätigen ' +
'{"confirm":"CLEANUP"} mitsenden.',
});
return;
}
const result = await auditService.runRetentionCleanup();
res.json({