Commit Graph
4 Commits
Author SHA1 Message Date
duffyduckandClaude Opus 5 2ca6ed2f70 Neuberechnung der Kette als eigene Gegenbuch-Alarmbedingung (R186 Frage b)
Der Tester fragte praezise: Loest cleanup -> rehash OHNE erneutes Siegeln,
ueber NICHT versiegelten Inhalt, etwas Automatisches aus - oder nur die
Prosa in verify, die ein Mensch lesen muss?

Gemessen statt behauptet: Es loeste bereits aus, aber als Nebenwirkung.
Ein Rehash aendert jeden Hash, also stimmt der beglaubigte Kettenkopf
nicht mehr und der bestehende Vergleich schlug an. Das funktioniert, ist
aber ein Zufallstreffer - verschoebe sich der Anker, waere der Melder
lautlos weg. Und die Meldung hiess "Eintrag wurde veraendert" statt "die
Kette wurde neu berechnet", also Wirkung statt Ursache.

Jetzt haengt der Alarm an der Sache selbst: Das Gegenbuch fuehrt
rehashCount/rehashLast im Buch mit und meldet jede neue Neuberechnung
seit der letzten Beglaubigung mit exit 2, samt Zeitpunkt, Zeilenzahl und
dem Befund, der unmittelbar davor galt. Dieselbe Lehre wie R184-01 (Gate
am Ausloeser statt an der Wirkung) und R185-01 (Wurzelwechsel statt
valid).

Reihenfolge geaendert, und das war noetig: Der neue Melder steht VOR dem
Kopf-Hash-Vergleich, sonst haette immer die unpraezisere Meldung
gewonnen. Und der Kopf-Vergleich wird nach einer bestaetigten
Neuberechnung uebersprungen - sonst waere die Bestaetigung wertlos, weil
ein Rehash den Kopf zwangslaeufig aendert. Beim Bauen aufgefallen, nicht
im Entwurf.

NOTARY_REHASH_ACK wird mit der ID des Rehash-Eintrags bestaetigt, nicht
mit true; IDs steigen streng, ein stehen gelassener Wert passt beim
naechsten Vorgang nicht mehr. Ein fehlender beglaubigter Eintrag
alarmiert weiterhin immer: bestaetigt wird die Neuberechnung, nicht das
Verschwinden von Zeilen.

Getestet mit echtem Gegenbuch gegen ein echtes CRM, SSH-signiertes
lokales Buch: Waesche ohne Datenbankzugriff -> CRM meldet valid:true und
chainGaps:[], Gegenbuch exit 2 mit der Neuberechnung als Ursache. Falsche
Ack-ID weiter exit 2, richtige exit 0, Folgelauf ruhig.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 18:30:44 +02:00
duffyduckandClaude Opus 5 e81a83ae8f Siegelwechsel ist ein Alarm, kein Hinweis (Pentest R185-01/-02)
R185-01 (MEDIUM): Die Flanke, die wir selbst gemeldet hatten, hat der
Tester live bestaetigt. seal-backlog war beim ZWEITEN Aufruf genauso
gegatet wie beim ersten ({"confirm":"SEAL"} -> 200), und das Ereignis
landete nur im Audit-Log, nicht im Alarmkanal. Sein Punkt: der
automatische Rueckhalt des Gegenbuchs haengt an `valid` - und `valid`
ueberlebt ein ersetzendes Siegel per Konstruktion. Angriff: Altzeile per
DB-Zugriff loeschen, neu siegeln, Luecke ist beglaubigt, valid wieder
true. Live reproduziert.

Zwei Schichten, in seiner Reihenfolge:

1. Alarmkanal. Neuer SecurityEventType AUDIT_SEAL_CHANGED (Migration
   20260826120000). Erstes Siegeln HIGH, Ersetzen CRITICAL - geht damit
   ueber sendPendingCriticalAlerts sofort per Mail raus. Die Details
   halten Wurzel vorher/nachher und den vollstaendigen Vorbefund fest.
2. Gate. Steht bereits ein Siegel, verlangt der Endpunkt
   {"confirm":"RESEAL"} statt SEAL, mit einem Text, der sagt, was dabei
   verloren geht. Ein Austausch der Beweisgrundlage soll nicht dasselbe
   Wort haben wie das Einrichten.

Und im Gegenbuch selbst: dort stand fuer den Wurzelwechsel ein
console.warn, waehrend der Rueckgabecode auf 0 blieb - also exakt das
Muster, das wir dem CRM zweimal angekreidet haben (R179, R183-02), im
Werkzeug, das dagegen gebaut wurde. Jetzt exit 2, mit alter und neuer
Wurzel samt Blattzahl; "10 Blaetter -> 9 Blaetter" zeigt die Loeschung
sofort. Auch die Erstsiegelung meldet sich, statt stillschweigend
uebernommen zu werden.

Aufloesbar gemacht: der Alarm bricht ab, BEVOR angehaengt wird - ohne
Bestaetigungsweg haette auch ein legitimes Siegeln fuer immer alarmiert
(R183-03-Falle). Neu ist NOTARY_SEAL_ACK, bewusst nicht "true", sondern
die Wurzel selbst (mind. 16 Zeichen): ein stehen gelassener Wert passt
beim naechsten Wechsel nicht mehr und kann keinen weiteren Austausch
durchwinken.

R185-02 (LOW): GET /api/audit-logs?action=<x> gab ungueltige Enum-Werte
roh an die Spalte -> 500. Zweifach schlecht: fehlende Validierung und
Fehler-Orakel (200 vs 500 verraet die Enum-Mitglieder). Jetzt 400 mit
der erlaubten Menge im Klartext. Mitgenommen: sensitivity, Datumsfelder,
Zahlenfelder, Textlaengen und ein Deckel auf limit (200), ueber den sich
sonst die ganze Tabelle an der Seitenlogik vorbei ziehen liess. Beide
Endpunkte.

Getestet ueber HTTP gegen eine Wegwerf-DB, inkl. echtem Gegenbuch-Lauf
mit SSH-signiertem lokalem Repo. Zusaetzlich nachgeholt, was der Tester
nicht herstellen konnte: vollstaendig unsigniertes Protokoll ->
kein_siegel statt der frueheren falschen Entwarnung nicht_noetig, und
seal-backlog nennt den fehlenden Schluessel als naechsten Schritt.
Gegenrichtung geprueft, R183-03 bleibt behoben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:41:11 +02:00
duffyduckandClaude Opus 5 1eb65809ec Gegenbuch: Dienstkonto statt Token, .env mit gueltigen Werten
Blocker behoben: Ich hatte ein dauerhaftes API-Token vorausgesetzt - das gibt
es in OpenCRM nicht. Zugangstoken leben 15 Minuten, der Gegenbuch-Container
waere nach dem ersten Durchlauf gestorben. Aufgefallen erst durch die Frage des
Betreibers, woher er den Token nimmt.

Loesung: Das Gegenbuch meldet sich bei jedem Lauf selbst an, mit einem eigenen
Benutzerkonto, dessen Rolle ausschliesslich audit:read traegt. Damit kann es
nur Pruefwerte lesen - keine Kundendaten, keine Aenderungen. CRM_TOKEN bleibt
fuer Tests moeglich, ist aber nicht mehr der Normalweg. Fehlerfaelle (falsches
Passwort, Anmelde-Bremse, CRM nicht erreichbar) werden unterschieden und im
Klartext gemeldet.

.env.example nennt jetzt zu jedem Schalter die gueltigen Werte - bisher liess
sich nur raten, ob es prod oder production heisst. Einschliesslich des Falls
"erst nur Staging testen, Prod spaeter dazunehmen".

Beide READMEs um "Zugang einrichten" ergaenzt: Rolle mit nur audit:read,
Benutzer damit, Zugangsdaten in die .env. Mit dem Hinweis, dass jede Anmeldung
im Audit-Log erscheint - gewollt, denn so faellt auch auf, wenn das Gegenbuch
aufhoert zu arbeiten.

Verifiziert gegen eine Attrappe, die wie das echte CRM eine Anmeldung verlangt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 19:31:46 +02:00
duffyduckandClaude Opus 5 a62c51b7c7 Gegenbuch als Docker-Setup, lokales Buch auf eigener Maschine
Betreiber-Entscheidung: Das Gegenbuch laeuft auf einer eigenen Maschine fuer
Prod und Staging; ein externes Git-Repository entfaellt, das Buch liegt lokal.
Die Trennung, auf die es ankommt, ist damit gegeben - wer OpenCRM uebernimmt,
kommt nicht ans Buch.

Richtung bewusst so herum: Das Gegenbuch holt ueber HTTPS mit einem Token, das
nur audit:read kann. OpenCRM kennt weder Adresse noch Schluessel des
Gegenbuchs. Kein SSH-Zugang zum CRM noetig.

tools/audit-notary/ enthaelt jetzt Dockerfile, entrypoint.sh,
docker-compose.yml und .env.example. Zwei Dienste (prod, staging) mit
getrennten Verzeichnissen und Schluesseln, gesteuert ueber COMPOSE_PROFILES -
dasselbe Muster wie beim Caddy-Profil im Hauptstack. Der Signaturschluessel
wird beim ersten Start auf der Gegenbuch-Maschine erzeugt.

Lokaler Betrieb ist jetzt ein vollwertiger Modus statt eines Testschalters. Die
Erfolgsmeldung benennt bei jedem Lauf, was abgedeckt ist und was nicht - statt
der frueheren pauschalen Formulierung "kein Manipulationsschutz", die im
Einsatz auf eigener Maschine schlicht falsch war.

Verifiziert mit echtem Docker-Build gegen eine CRM-Attrappe: Genesis ohne
Bestaetigung -> Code 4; mit Bestaetigung Normalbetrieb exit 0; Eintrag
veraendert -> Alarm exit 2; Eintraege geloescht -> Alarm exit 2; Siegel
verschwunden -> Alarm.

Behoben beim Bauen: useradd -u 1000 || true verschluckte, dass UID 1000 im
Node-Image vergeben ist - der Container startete gar nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 18:51:45 +02:00