Pentest R121: Audit-Verify-Fehlalarm bei leerem resourceId beheben

POST /api/audit-logs/verify meldete ~73% der Einträge als
"manipuliert" – kein echtes Tampering, sondern ein Serialisierungs-
Bug in generateHash: resourceId war beim Schreiben oft undefined
(Middleware-Einträge ohne Route-ID). JSON.stringify lässt einen
undefined-Wert weg → Hash ohne resourceId-Key. In der DB steht der
Wert aber als NULL; verifyIntegrity/rehashAll lasen null zurück und
JSON.stringify({resourceId:null}) schrieb ihn rein → anderer Hash
→ Fehlalarm für jede Zeile mit leerem resourceId. Die vom Pentester
gefundene durationMs-Korrelation war nur ein Proxy (Middleware-
Einträge haben durationMs UND oft kein resourceId).

Fix: generateHash lässt nullish resourceId weg – reproduziert exakt
das historische Schreibverhalten (undefined → Key weg). Kein Caller
übergibt je null explizit (verifiziert), daher matchen alle
Bestands-Hashes ohne Rehash; Feldreihenfolge unverändert.

Empirisch gegen echte DB bewiesen: aktuelle Hash-Ära (id>4141)
482/482 valide (vorher wurden alle 289 leeren-resourceId-Zeilen
falsch geflaggt).

Separat/vorbestehend (NICHT dieser Fix): Einträge vor Commit
fd55742 ("complete new audit system") nutzen ein altes Hash-Schema
und brauchen einmalig POST /api/audit-logs/rehash zum Re-Baselinen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 09:13:18 +02:00
co-authored by Claude Opus 4.7
parent 094887f1d3
commit e775b0e439
2 changed files with 44 additions and 7 deletions
+23 -7
View File
@@ -91,16 +91,32 @@ function generateHash(data: {
createdAt: Date;
previousHash?: string | null;
}): string {
const content = JSON.stringify({
// WICHTIG Pentest R121 (Verify-Fehlalarm, ~73% "manipuliert"):
// Beim Schreiben war `resourceId` bei middleware-generierten Einträgen
// oft `undefined`. JSON.stringify LÄSST einen undefined-Wert weg der
// Hash wurde also OHNE `resourceId`-Key gebildet. In der DB landet der
// Wert aber als `NULL`. verifyIntegrity/rehashAll lasen ihn als `null`
// zurück, und `JSON.stringify({resourceId: null})` schreibt
// `"resourceId":null` REIN → anderer Hash → falscher Tamper-Alarm für
// jede Zeile mit leerem resourceId.
//
// Fix: nullish resourceId weglassen reproduziert exakt das historische
// Schreibverhalten (undefined → Key weg). Kein Caller hat je `null`
// explizit übergeben (verifiziert), daher matchen ALLE Bestands-Hashes
// ohne Rehash. Feldreihenfolge bleibt identisch zur Alt-Serialisierung.
const payload: Record<string, unknown> = {
userEmail: data.userEmail,
action: data.action,
resourceType: data.resourceType,
resourceId: data.resourceId,
endpoint: data.endpoint,
createdAt: data.createdAt.toISOString(),
previousHash: data.previousHash || '',
});
return crypto.createHash('sha256').update(content).digest('hex');
};
if (data.resourceId !== null && data.resourceId !== undefined) {
payload.resourceId = data.resourceId;
}
payload.endpoint = data.endpoint;
payload.createdAt = data.createdAt.toISOString();
payload.previousHash = data.previousHash || '';
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
}
/**