Gegenbuch: Unsicherheit erreicht jetzt den Rueckgabecode (Pentest R179)
R179-b: Der Rewind-Vorbehalt stand in der Ausgabe, der Exit blieb 0. Die eigene README sagt "fuer Cron gilt: jeder Code ausser 0 gehoert gemeldet" - der Zustand "ich bin an dieser Stelle blind" erreichte die Ueberwachung also nie. Dieselbe Klasse wie R174, eine Ebene hoeher. Fix: Code 0 nur bei belegter Gewissheit (Rewind-Schutz zugesichert oder Mindesthoehe erfuellt), sonst Code 3 - bewusst nicht mit dem Manipulationsalarm 2 verschmolzen. R179-01: NOTARY_MIN_SEQ ist eine Untergrenze, kein Ist-Stand. Ein veralteter Wert liess einen Teil-Rewind darueber lautlos durch, und die blosse Praesenz einer Zahl blendete den Vorbehalt aus - MIN_SEQ=0 war ein Freibrief, ein Tippfehler wurde still verschluckt. Ein veralteter Anker erzeugte damit ein selbstbewussteres Ergebnis als gar keiner. Fix: Der Vorbehalt haengt allein an NOTARY_REWIND_PROTECTED=true und benennt bei gesetztem MIN_SEQ dessen Grenze; MIN_SEQ <= 0 oder unparsbar fuehrt zu exit 1 statt stiller Annahme. Verifiziert gegen den Pentest-Aufbau (10 Checkpoints, Rewind auf 7, DB passend gekuerzt, frischer Klon): nicht gesetzt -> exit 3 (vorher 0); 10 und 8 -> Alarm 2; 7 veraltet -> 0 mit Vorbehalt (vorher ohne); 0 und xyz -> exit 1 (vorher stilles 0); Rewind-Schutz zugesichert -> 0 ohne Vorbehalt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,9 +61,23 @@ Deshalb zusätzlich:
|
||||
NOTARY_MIN_SEQ=42 node notary.mjs --check
|
||||
```
|
||||
|
||||
Ohne `NOTARY_MIN_SEQ` weist die Erfolgsmeldung ausdrücklich darauf hin, dass
|
||||
ein Zurückspulen nicht erkennbar war. Ein „OK" ohne diesen Zusatz bedeutet
|
||||
mehr als eines mit.
|
||||
**`NOTARY_MIN_SEQ` ersetzt die serverseitige Sperre nicht.** Der Wert ist eine
|
||||
*Untergrenze* und immer nur so frisch wie deine letzte Beobachtung. Wer
|
||||
stündlich beglaubigt, aber wöchentlich prüft, läuft mit einem Wert herum, der
|
||||
um rund 168 hinterherhinkt – ein Zurückspulen in dieses Fenster bliebe
|
||||
unbemerkt und der gewaschene Stand würde sogar ausdrücklich bestätigt.
|
||||
|
||||
Ist der Rewind-Schutz auf dem Server tatsächlich eingerichtet, bestätige das:
|
||||
|
||||
```bash
|
||||
NOTARY_REWIND_PROTECTED=true
|
||||
```
|
||||
|
||||
**Nur diese Zusicherung** blendet den Vorbehalt aus – nicht die bloße Angabe
|
||||
einer Zahl. Solange sie fehlt, endet jeder Lauf mit **Code 3** statt 0:
|
||||
„nicht abschließend feststellbar". Das ist Absicht, denn ein Cronjob liest den
|
||||
Rückgabecode, nicht den Fließtext – ein Hinweis, der nur in der Ausgabe steht,
|
||||
erreicht die Überwachung nie.
|
||||
|
||||
## Der entscheidende Punkt: es wird tatsächlich geprüft
|
||||
|
||||
@@ -117,7 +131,7 @@ Deshalb gilt jetzt:
|
||||
| 0 | alles in Ordnung, Checkpoint angehängt (bzw. Prüfung bestanden) |
|
||||
| 1 | Betriebsfehler (Konfiguration, Commit oder Push fehlgeschlagen) |
|
||||
| 2 | **Befund** – Widerspruch zwischen CRM und Gegenbuch, oder ungültige Signatur |
|
||||
| 3 | beglaubigter Stand nicht feststellbar (Remote fehlt oder unerreichbar) |
|
||||
| 3 | beglaubigter Stand nicht abschließend feststellbar – Remote fehlt/unerreichbar, **oder** ein Zurückspulen lässt sich nicht ausschließen |
|
||||
|
||||
Für Cron gilt: **jeder** Code außer 0 gehört gemeldet. Code 2 ist der Alarm,
|
||||
Code 3 heißt „ich weiß es nicht" – und das ist ausdrücklich kein Freibrief.
|
||||
|
||||
@@ -73,7 +73,35 @@ let PIN = (process.env.NOTARY_SIGNER_FINGERPRINT || '').trim() || null;
|
||||
// Wer die zuletzt gesehene seq kennt (aus Monitoring, Protokoll, Mail), kann
|
||||
// sie hier mitgeben – dann faellt das Zurueckspulen auch ohne lokalen Zustand
|
||||
// auf.
|
||||
const MIN_SEQ = Number.parseInt(process.env.NOTARY_MIN_SEQ || '', 10);
|
||||
const MIN_SEQ_ROH = (process.env.NOTARY_MIN_SEQ || '').trim();
|
||||
let MIN_SEQ = Number.NaN;
|
||||
if (MIN_SEQ_ROH) {
|
||||
MIN_SEQ = Number.parseInt(MIN_SEQ_ROH, 10);
|
||||
// Laut ablehnen statt still verschlucken (Pentest R179-01): ein Tippfehler
|
||||
// ergab frueher `NaN`, wurde ignoriert – und weil die blosse PRAESENZ eines
|
||||
// Werts den Vorbehalt ausblendete, sah das Ergebnis anschliessend
|
||||
// selbstbewusster aus als ganz ohne Angabe. `0` war sogar ein Freibrief.
|
||||
if (!Number.isFinite(MIN_SEQ) || MIN_SEQ <= 0) {
|
||||
console.error(
|
||||
`NOTARY_MIN_SEQ ist kein sinnvoller Wert: "${MIN_SEQ_ROH}".\n` +
|
||||
'Erwartet wird die zuletzt bekannte Checkpoint-Nummer als positive Zahl.\n' +
|
||||
'Lieber weglassen als raten – dann sagt die Prüfung ehrlich, dass ein ' +
|
||||
'Zurückspulen nicht erkennbar war.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Zusicherung des Betreibers, dass der Server non-fast-forward-Pushes
|
||||
// verbietet (Pentest R179-01/-b).
|
||||
//
|
||||
// NUR diese Zusicherung darf den Rewind-Vorbehalt ausblenden – nicht die
|
||||
// blosse Anwesenheit einer Zahl in NOTARY_MIN_SEQ. Der Grund: MIN_SEQ ist eine
|
||||
// UNTERGRENZE und immer nur so frisch wie die letzte Beobachtung. Wer stuendlich
|
||||
// beglaubigt, aber woechentlich prueft, laeuft mit einem Wert herum, der um
|
||||
// ~168 hinterherhinkt – jeder Rewind in dieses Fenster passiert lautlos, und
|
||||
// der gewaschene Stand wird sogar ausdruecklich bestaetigt.
|
||||
const REWIND_GESCHUETZT = process.env.NOTARY_REWIND_PROTECTED === 'true';
|
||||
if (!SIGN && process.env.NOTARY_INSECURE_ACK !== 'mir-ist-klar-dass-das-ungeschuetzt-ist') {
|
||||
console.error(
|
||||
'NOTARY_SIGN=false deaktiviert den gesamten Schutz des Gegenbuchs.\n' +
|
||||
@@ -178,12 +206,29 @@ const OHNE_ANKER = () =>
|
||||
// signierten Stand unerkennbar (R178-01). Das gehoert in die Erfolgsmeldung,
|
||||
// sonst liest sich „OK“ als mehr Gewissheit, als vorhanden ist.
|
||||
const REWIND_VORBEHALT = () =>
|
||||
Number.isFinite(MIN_SEQ)
|
||||
REWIND_GESCHUETZT
|
||||
? ''
|
||||
: '\n Hinweis: Ein Zurückspulen der Reihe auf einen älteren, ebenfalls signierten Stand ' +
|
||||
'ist hieraus NICHT erkennbar.\n' +
|
||||
' Dagegen hilft serverseitiger Rewind-Schutz (non-fast-forward verbieten) oder ' +
|
||||
'NOTARY_MIN_SEQ mit der zuletzt bekannten Nummer.';
|
||||
: '\n Offen: Ein Zurückspulen der Reihe auf einen älteren, ebenfalls signierten Stand ' +
|
||||
'lässt sich hier nicht ausschließen.' +
|
||||
(Number.isFinite(MIN_SEQ)
|
||||
? `\n NOTARY_MIN_SEQ=${MIN_SEQ} deckt nur ab, was UNTERHALB dieser Nummer fehlt – ` +
|
||||
'ein Rewind oberhalb bliebe unbemerkt, und der Wert hinkt naturgemäß hinterher.'
|
||||
: '\n Ohne NOTARY_MIN_SEQ gibt es dafür gar keinen Bezugspunkt.') +
|
||||
'\n Verlässlich hilft nur serverseitiger Rewind-Schutz (non-fast-forward verbieten);' +
|
||||
' ist er eingerichtet, mit NOTARY_REWIND_PROTECTED=true bestätigen.';
|
||||
|
||||
|
||||
// Rueckgabecode statt blosser Prosa (Pentest R179-b).
|
||||
//
|
||||
// Der Vorbehalt stand in stdout, der Exit blieb 0 – und Cron greppt den Code,
|
||||
// nicht den Text. Der Zustand „Rewind nicht ausschliessbar“ erreichte die
|
||||
// Automation damit nie. Code 0 gibt es deshalb nur noch bei belegter
|
||||
// Gewissheit: entweder ist serverseitiger Rewind-Schutz zugesichert, oder eine
|
||||
// mitgegebene Mindesthoehe wurde erfuellt. Sonst Code 3 – „nicht abschliessend
|
||||
// feststellbar“, dieselbe Bedeutung wie beim unerreichbaren Remote und
|
||||
// bewusst NICHT mit dem Manipulationsalarm (2) verschmolzen.
|
||||
const abschliessendSicher = (anzahl) =>
|
||||
REWIND_GESCHUETZT || (Number.isFinite(MIN_SEQ) && anzahl >= MIN_SEQ);
|
||||
|
||||
const alarm = (text) => { console.error(`ALARM: ${text}`); process.exit(2); };
|
||||
|
||||
@@ -384,13 +429,14 @@ if (letzter) {
|
||||
// 5) Anhaengen – mit Ruecknahme, falls der signierte Commit scheitert.
|
||||
// ---------------------------------------------------------------------------
|
||||
if (NUR_PRUEFEN) {
|
||||
const sicher = abschliessendSicher(bisher.length);
|
||||
console.log(
|
||||
`OK: ${bisher.length} Checkpoint(s) geprüft – Signaturen gültig, Reihe lückenlos, ` +
|
||||
`Arbeitsdatei deckungsgleich mit dem signierten Stand` +
|
||||
(letzter ? `, CRM stimmt mit Checkpoint ${letzter.seq} überein (maxId ${letzter.maxId}).` : '.') +
|
||||
OHNE_ANKER() + REWIND_VORBEHALT(),
|
||||
);
|
||||
process.exit(0);
|
||||
process.exit(sicher ? 0 : 3);
|
||||
}
|
||||
|
||||
const eintrag = {
|
||||
@@ -461,5 +507,7 @@ if (PUSH && !NUR_LOKAL) {
|
||||
|
||||
console.log(
|
||||
`OK: Checkpoint ${eintrag.seq} beglaubigt (maxId ${eintrag.maxId}).` + OHNE_ANKER() +
|
||||
REWIND_VORBEHALT() +
|
||||
`\n Zuletzt bekannte Nummer für die nächste Prüfung: NOTARY_MIN_SEQ=${eintrag.seq}`,
|
||||
);
|
||||
process.exit(abschliessendSicher(eintrag.seq) ? 0 : 3);
|
||||
|
||||
Reference in New Issue
Block a user