Externer Anker: Audit-Kette HMAC-signiert (Hash-Version 3)

Schliesst den nach R166/R167 verbliebenen Grenzfall: Bis Version 2 war die
Kette selbsttragend - wer die DB schreiben kann, konnte jede Zeile aendern und
alle Folgehashes konsistent nachziehen, die Pruefung meldete "gueltig".

Version 3 signiert denselben Inhalt per HMAC-SHA256 mit AUDIT_HMAC_KEY, einem
Schluessel ausserhalb der Datenbank. Ohne ihn laesst sich keine gueltige
Signatur erzeugen; reiner DB-Schreibzugriff genuegt nicht mehr.

Fail-safe: Ohne Schluessel wird weiter Version 2 geschrieben, es faellt nichts
aus. Signierte Zeilen gelten dann als nicht pruefbar (unverifiableEntries) und
ausdruecklich nicht als manipuliert. AUDIT_HMAC_KEY_OLD erlaubt einen
Schluesselwechsel ohne Rehash.

Restluecke der Versionsgrenze geschlossen: Wird die FRUEHESTE Zeile einer Stufe
herabgestuft, wandert MIN(id) mit - die Grenze allein haette den Downgrade
durchgewunken (der erste Testlauf fiel genau darauf durch). Der Nachfolger ist
jedoch HMAC-signiert und sein previousHash ohne Schluessel nicht faelschbar;
eine unerklaerte Luecke vor einer signierten Zeile gilt deshalb als Befund.

Verifiziert: Inhalt geaendert -> erkannt; Downgrade 3->2 auf der fruehesten
V3-Zeile -> erkannt; dasselbe auf der letzten V3-Zeile (kein Nachfolger) ->
erkannt; ohne Schluessel 0 manipuliert / 2 nicht pruefbar; 40 parallele
Schreiber -> 40/40, 0 Forks, alle V3. tsc + vite build gruen.

AUDIT_HMAC_KEY in .env.example dokumentiert. Der Schluessel selbst liegt nur
lokal in .env (gitignored).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 18:50:57 +02:00
co-authored by Claude Opus 5
parent 1a349d142e
commit 044a12f73e
5 changed files with 173 additions and 27 deletions
+33
View File
@@ -97,6 +97,39 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **⚓ Externer Anker: Audit-Kette HMAC-signiert (Hash-Version 3)** (2026-08-18)
- Schliesst den nach R166/R167 verbliebenen Grenzfall: Bis Version 2 war die
Kette selbsttragend wer die DB schreiben kann, konnte jede Zeile aendern
und alle Folgehashes konsistent nachziehen, die Pruefung meldete „gueltig“.
- Version 3 signiert denselben Inhalt per **HMAC-SHA256** mit `AUDIT_HMAC_KEY`
einem Schluessel, der NICHT in der Datenbank liegt. Ohne ihn laesst sich
keine gueltige Signatur erzeugen; reiner DB-Schreibzugriff genuegt nicht mehr.
- **Fail-safe:** Ohne Schluessel schreibt das Audit-Log weiter Version 2, es
faellt nichts aus. Signierte Zeilen gelten dann als **nicht pruefbar**
(eigener Topf `unverifiableEntries`) und ausdruecklich NICHT als
manipuliert eine fehlende Konfiguration darf kein Fehlalarm ueber das
gesamte Log sein.
- **Schluesselwechsel ohne Rehash:** `AUDIT_HMAC_KEY_OLD` wird bei der
Pruefung zusaetzlich akzeptiert. Verifiziert: mit KEY_OLD 0 Befunde, ohne
KEY_OLD werden die alt signierten Zeilen erwartungsgemaess auffaellig.
- **Restluecke der Versionsgrenze geschlossen (selbst gefunden):** Wird die
FRUEHESTE Zeile einer Stufe herabgestuft, wandert `MIN(id)` mit die
Grenze allein haette den Downgrade durchgewunken (mein erster Testlauf fiel
genau darauf durch). Der Nachfolger ist jedoch HMAC-signiert, sein
`previousHash` ist ohne Schluessel nicht faelschbar. Eine unerklaerte Luecke
vor einer signierten Zeile gilt deshalb jetzt als Befund, nicht als
struktureller Zufall.
- Verifiziert: Inhalt geaendert + Hash beliebig → erkannt; Downgrade 3→2 mit
gueltigem V2-Hash auf der **fruehesten** V3-Zeile → erkannt; dasselbe auf
der **letzten** V3-Zeile (kein Nachfolger) → erkannt; ohne Schluessel
0 manipuliert / 2 nicht pruefbar; 40 parallele Schreiber → 40/40, 0 Forks,
alle V3. `tsc` + `vite build` gruen.
- **Betrieb:** `AUDIT_HMAC_KEY` in `.env.example` dokumentiert
(`openssl rand -hex 32`). Schluessel sichern geht er verloren, sind die
damit signierten Eintraege dauerhaft nicht mehr pruefbar (aber nicht als
manipuliert gemeldet). Der Dev-Schluessel liegt nur lokal in `.env`
(gitignored) und ist NICHT der Prod-Schluessel.
- [x] **🔒 hashVersion-Downgrade geschlossen (Pentest R167-01, HIGH)** (2026-08-18)
- Der Pentester hat den Angriffsweg, den ich beim Uebergeben von R166-02
selbst als naechsten benannt hatte, als **exploitbar bewiesen** (PoC gegen