Gegenbuch: verifizierender Leser statt Absichtserklaerung (Pentest R175)

R175-01 (HIGH): Die erste Fassung signierte zwar, prueft aber nie. Sie las ihre
Wahrheit per readFileSync aus der lokalen Arbeitsdatei, nirgends gab es ein
git verify-commit - das -S war write-only ohne Konsument. Live reproduziert:
DB-Tail abgeschnitten und die lokale Ledger-Zeile angepasst -> "OK, Checkpoint
beglaubigt", exit 0, kein Alarm.

Fix: Wahrheitsquelle ist der signierte Commit-Baum (bevorzugt der Remote-Kopf);
jeder Commit mit Gegenbuch-Aenderung muss eine gueltige Signatur tragen; weicht
die Arbeitsdatei vom signierten Stand ab, wird abgebrochen; lokale, nie
gepushte Commits gelten nicht als beglaubigt; die Signatur des frisch
erzeugten Commits wird gegengeprueft. Dazu ein Pruefmodus --check fuer
Auditoren.

R175-02 (MEDIUM): /checkpoint fuhr je Aufruf ein volles verifyIntegrity (O(n),
0,85 s bei 16k Zeilen) - authentifizierte DoS-Verstaerkung. Gebraucht wurde nur
die Siegel-Wurzel. Jetzt Kopf-Hash aus der Kopfzeile, Wurzel aus dem juengsten
gueltigen Marker, sealLeafCount statt des teuren Status.

R175-03: writeFileSync lief vor dem Commit, eine verwaiste Zeile wurde vom
Folgelauf zementiert. Jetzt Ruecknahme bei Fehlschlag, und NOTARY_SIGN=false
verlangt zusaetzlich NOTARY_INSECURE_ACK.

Verifiziert mit echtem SSH-Signaturschluessel: stilles Waschen -> Alarm;
erfundene Zeile -> Alarm; unsignierter Commit -> Alarm; Commit-Fehlschlag ->
zurueckgerollt; Reflex-Schalter verweigert; Pruefmodus haengt nichts an.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 16:13:54 +02:00
co-authored by Claude Opus 5
parent f3ded9afbc
commit 375d4ae1e2
4 changed files with 293 additions and 76 deletions
+38 -2
View File
@@ -43,6 +43,32 @@ Als Cronjob, stündlich:
nur geliehen wer Schreibrecht auf das Repository erlangt, schreibt die
Historie sonst einfach um. Bei GitHub/GitLab: Branch-Protection auf `main`.
## Der entscheidende Punkt: es wird tatsächlich geprüft
Die erste Fassung dieses Skripts hat signiert aber **nie eine Signatur
geprüft**. Es las seine Wahrheit aus der lokalen Arbeitsdatei. Wer den
Notar-Rechner beschreiben konnte, hat ihm damit beliebige „beglaubigte"
Zeilen untergeschoben und einen abgeschnittenen Datenbestand als neue Wahrheit
festgeschrieben, ohne Alarm. Signieren ohne prüfenden Leser ist wertlos.
Deshalb gilt jetzt:
- Wahrheitsquelle ist der **signierte Commit-Baum** (bevorzugt der Remote-Kopf),
nicht die Arbeitsdatei
- **Jeder** Commit, der das Gegenbuch ändert, muss eine gültige Signatur tragen
- Weicht die Arbeitsdatei vom signierten Stand ab, wird abgebrochen
- Geschrieben wird erst nach erfolgreichem, signiertem Commit scheitert er,
wird die Datei zurückgerollt
## Prüfmodus für Auditoren
```bash
node notary.mjs --check
```
Führt alle Kontrollen aus, hängt aber nichts an und braucht kein Schreibrecht.
Geeignet für jemanden, der die Kette unabhängig nachvollziehen will.
## Was das Skript erkennt
| Angriff | Erkennung |
@@ -51,6 +77,10 @@ Historie sonst einfach um. Bei GitHub/GitLab: Branch-Protection auf `main`.
| Einträge am Ende abgeschnitten | aktuelle höchste ID kleiner als die beglaubigte |
| Bestandssiegel verschwunden | vorher beglaubigt, jetzt nicht mehr vorhanden |
| Gegenbuch selbst gekürzt | Lücke in der fortlaufenden Nummer |
| Gegenbuch lokal manipuliert | Arbeitsdatei weicht vom signierten Stand ab |
| Untergeschobener Commit | Commit ohne gültige Signatur in der Historie |
| Nie gepushte lokale Commits | Abgleich gegen den Remote-Kopf |
| Bestandssiegel-Blätter entfernt | beglaubigte Blattzahl auf null gefallen |
Bei jedem dieser Fälle bricht das Skript mit **Exit-Code 2** ab und **hängt
nichts an** der manipulierte Zustand wird also nicht als neue Wahrheit
@@ -67,5 +97,11 @@ im Log unter.
CRM dort ist ja nichts konfiguriert. Der Ausfall muss auf dem
Gegenbuch-Rechner überwacht werden (z. B. Alarm, wenn der jüngste Commit
älter als zwei Intervalle ist).
- **`NOTARY_SIGN=false`** existiert nur für Tests ohne hinterlegten Schlüssel.
Im Betrieb niemals setzen.
- **`NOTARY_SIGN=false`** existiert nur für Tests ohne hinterlegten Schlüssel
und verlangt zusätzlich `NOTARY_INSECURE_ACK=…`. Der Reflex „Cron hakt, also
Signatur abschalten" soll bewusst nicht ausreichen er würde eine
ungeschützte Kette festschreiben.
- **Der Remote-Server muss Force-Push verhindern.** Das Skript prüft die
Signaturen, aber wenn jemand die Historie am Server überschreiben kann,
ersetzt er auch die signierten Commits durch eigene. Branch-Protection ist
Teil des Schutzes, nicht Zubehör.