Commit Graph
3 Commits
Author SHA1 Message Date
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