Gegenbuch: Pruefmodus schreibt nicht mehr, Widerspruch aufgeloest (R181)
R181-01: Der Pruefmodus sagte zu, nichts zu veraendern und kein Schreibrecht zu brauchen - und pushte trotzdem, weil ankerNachziehen() in jedem Modus lief. Ein read-only Audit mutierte damit still das geteilte Substrat. R181-02: Ein Widerspruch zwischen den eigenen Fixes. R180-01 erhebt die Serversperre auf refs/notary/* zur tragenden Pflicht, R180-02 verlangt dort Schreibrecht zur Selbstheilung. Sobald je ein Anker fehlte, bekam jeder read-only pruefende Auditor dauerhaft einen Fehler auf einer voellig gueltigen Kette, den er nicht beheben konnte. Fix: Reparieren nur im Notar-Schreiblauf, im Pruefmodus wird der fehlende Anker gemeldet. Eigener Rueckgabecode 5: "Anker unvollstaendig" ist nicht "nicht feststellbar" - die Kette ist gueltig, nur das Substrat-Gedaechtnis unvollstaendig, ein benannter reparierbarer Defekt. Dieselbe Trennung wie bei Genesis/Adoption. Empirisch beantwortet: Die Notar-Identitaet laesst sich eng auf das Anlegen von refs/notary/* beschraenken, ohne Loeschen oder Ueberschreiben - serverseitig unterscheidbar an der Null-OID. Mit pre-receive-Hook verifiziert: Backfill greift, Loeschen und Force-Overwrite bleiben abgewiesen. Hook als Beispiel in der README. Verifiziert: read-only --check mit fehlendem Anker -> Code 5, nichts gepusht; Notar-Schreiblauf unter derselben ACL -> Anker nachgetragen, exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,39 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ Erledigt
|
||||||
|
|
||||||
|
- [x] **🧮 Gegenbuch: Pruefmodus schreibt nicht mehr, Widerspruch aufgeloest (Pentest R181)** (2026-08-18)
|
||||||
|
- **R181-01 (LOW→MED)** – Der Pruefmodus sagte zu, nichts zu veraendern und
|
||||||
|
kein Schreibrecht zu brauchen – und pushte trotzdem: `ankerNachziehen()`
|
||||||
|
lief in JEDEM Modus. Ein „read-only“ Audit mutierte damit still das
|
||||||
|
geteilte Substrat. Fix: Nachziehen nur im Schreiblauf.
|
||||||
|
- **R181-02 (MEDIUM) – ein Widerspruch zwischen meinen EIGENEN Fixes.**
|
||||||
|
R180-01 erhebt die Serversperre auf `refs/notary/*` zur tragenden Pflicht;
|
||||||
|
R180-02 verlangt genau dort Schreibrecht zur Selbstheilung. Ergebnis:
|
||||||
|
Sobald je ein Anker fehlte, bekam jeder read-only pruefende Auditor
|
||||||
|
dauerhaft einen Fehler auf einer **voellig gueltigen** Kette – und konnte
|
||||||
|
ihn nicht beheben.
|
||||||
|
Fix: Reparieren ist Sache des Notar-Laufs; im Pruefmodus wird der fehlende
|
||||||
|
Anker gemeldet, nicht repariert.
|
||||||
|
- **Eigener Rueckgabecode 5** (seine Frage b): „Anker unvollstaendig“ ist
|
||||||
|
NICHT „nicht feststellbar“. Die Kette ist geprueft und gueltig, nur das
|
||||||
|
Substrat-Gedaechtnis ist unvollstaendig – ein benannter, reparierbarer
|
||||||
|
Defekt mit klarer Handlungsanweisung. Ihn in Code 3 zu werfen hiesse, eine
|
||||||
|
Anweisung als Ungewissheit zu melden; dieselbe Trennung wie bei
|
||||||
|
Genesis/Adoption.
|
||||||
|
- **Seine Frage (a) empirisch beantwortet:** Die Notar-Identitaet laesst sich
|
||||||
|
eng auf das ANLEGEN von `refs/notary/*` beschraenken, ohne ihr Loeschen
|
||||||
|
oder Ueberschreiben zu geben – serverseitig unterscheidbar an der Null-OID
|
||||||
|
beim Anlegen. Mit `pre-receive`-Hook verifiziert: Backfill greift (exit 0),
|
||||||
|
Loeschen und Force-Overwrite werden weiterhin abgewiesen. Der Hook steht
|
||||||
|
als Beispiel in der README.
|
||||||
|
- Verifiziert: read-only `--check` mit fehlendem Anker → **Code 5, nichts
|
||||||
|
gepusht** (Anker-Anzahl vorher/nachher identisch); Notar-Schreiblauf unter
|
||||||
|
derselben ACL → „Fehlende Anker nachgetragen: 1“, exit 0.
|
||||||
|
- Seine Non-Findings uebernommen: Die Backfill-Zuordnung ist gegen das
|
||||||
|
PIN-Modell robust – jeder Commit mit echter Ledger-Aenderung ist non-merge
|
||||||
|
und muss keyA-signiert sein, sig-exempte Merges tragen keinen Inhalt in die
|
||||||
|
Zuordnungsliste.
|
||||||
|
|
||||||
- [x] **🛑 Gegenbuch: Anker belegen nichts mehr, Anker-Verlust wird laut (Pentest R180-01/-02)** (2026-08-18)
|
- [x] **🛑 Gegenbuch: Anker belegen nichts mehr, Anker-Verlust wird laut (Pentest R180-01/-02)** (2026-08-18)
|
||||||
- **R180-01 (MEDIUM)** – Genau die Unsicherheit, die ich selbst benannt
|
- **R180-01 (MEDIUM)** – Genau die Unsicherheit, die ich selbst benannt
|
||||||
hatte, bestaetigt: Der Code nahm den **hoechsten noch vorhandenen**
|
hatte, bestaetigt: Der Code nahm den **hoechsten noch vorhandenen**
|
||||||
|
|||||||
@@ -103,6 +103,19 @@ Standardzustand.
|
|||||||
> einen niedrigeren stehen lässt, senkt damit den Vergleichswert selbst – und
|
> einen niedrigeren stehen lässt, senkt damit den Vergleichswert selbst – und
|
||||||
> in-repo ist das von „gab es nie" nicht zu unterscheiden.
|
> in-repo ist das von „gab es nie" nicht zu unterscheiden.
|
||||||
>
|
>
|
||||||
|
> **Die Notar-Identität braucht dort eine eng gefasste Ausnahme:** Sie muss
|
||||||
|
> `refs/notary/*` **anlegen** dürfen, aber ausdrücklich **nicht löschen oder
|
||||||
|
> überschreiben**. Sonst hebelt eine kompromittierte Notar-Identität genau den
|
||||||
|
> Schutz aus, um den es hier geht. Server-seitig ist das unterscheidbar – ein
|
||||||
|
> `pre-receive`-Hook sieht beim Anlegen die Null-OID als alten Wert:
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> # refs/notary/*: nur anlegen, niemals ändern oder löschen
|
||||||
|
> case "$ref" in refs/notary/*)
|
||||||
|
> [ "$old" = "0000000000000000000000000000000000000000" ] || exit 1 ;;
|
||||||
|
> esac
|
||||||
|
> ```
|
||||||
|
>
|
||||||
> Deshalb begründen Anker **keine** Unversehrtheit. Sie können ein
|
> Deshalb begründen Anker **keine** Unversehrtheit. Sie können ein
|
||||||
> Zurückspulen *widerlegen* (wenn ein hoher Anker nicht mehr im Kopf steckt),
|
> Zurückspulen *widerlegen* (wenn ein hoher Anker nicht mehr im Kopf steckt),
|
||||||
> aber nie belegen, dass nichts fehlt. Ein `--check` aus frischem Klon erkennt
|
> aber nie belegen, dass nichts fehlt. Ein `--check` aus frischem Klon erkennt
|
||||||
@@ -198,6 +211,7 @@ Deshalb gilt jetzt:
|
|||||||
| 0 | alles in Ordnung, Checkpoint angehängt (bzw. Prüfung bestanden) |
|
| 0 | alles in Ordnung, Checkpoint angehängt (bzw. Prüfung bestanden) |
|
||||||
| 1 | Betriebsfehler (Konfiguration, Commit oder Push fehlgeschlagen) |
|
| 1 | Betriebsfehler (Konfiguration, Commit oder Push fehlgeschlagen) |
|
||||||
| 2 | **Befund** – Widerspruch zwischen CRM und Gegenbuch, oder ungültige Signatur |
|
| 2 | **Befund** – Widerspruch zwischen CRM und Gegenbuch, oder ungültige Signatur |
|
||||||
|
| 5 | **Anker unvollständig** – die Kette ist gültig, aber `refs/notary/seq-N` fehlt. Reparierbar durch einen Notar-Schreiblauf |
|
||||||
| 4 | **Wächter-Gedächtnis fehlt** – Erstinbetriebnahme unbestätigt, oder Speicher nach der Etablierung verloren |
|
| 4 | **Wächter-Gedächtnis fehlt** – Erstinbetriebnahme unbestätigt, oder Speicher nach der Etablierung verloren |
|
||||||
| 3 | beglaubigter Stand nicht abschließend feststellbar – Remote fehlt/unerreichbar, erste Beobachtung, Zurückspulen nicht ausschließbar, **oder** Checkpoint erstellt aber nicht verankert |
|
| 3 | beglaubigter Stand nicht abschließend feststellbar – Remote fehlt/unerreichbar, erste Beobachtung, Zurückspulen nicht ausschließbar, **oder** Checkpoint erstellt aber nicht verankert |
|
||||||
|
|
||||||
@@ -210,8 +224,14 @@ Code 3 heißt „ich weiß es nicht" – und das ist ausdrücklich kein Freibrie
|
|||||||
node notary.mjs --check
|
node notary.mjs --check
|
||||||
```
|
```
|
||||||
|
|
||||||
Führt alle Kontrollen aus, hängt aber nichts an und braucht kein Schreibrecht.
|
Führt alle Kontrollen aus, **verändert nichts** und braucht kein Schreibrecht –
|
||||||
Geeignet für jemanden, der die Kette unabhängig nachvollziehen will.
|
auch dann nicht, wenn dabei ein fehlender Anker auffällt. Der wird gemeldet
|
||||||
|
(Code 5), nicht repariert; Reparieren ist Sache des Notar-Laufs.
|
||||||
|
|
||||||
|
Das ist kein Detail: Die Serversperre für `refs/notary/*` lehnt Schreibzugriffe
|
||||||
|
ja gerade ab. Würde der Prüfmodus dort nachtragen wollen, bekäme jeder
|
||||||
|
read-only prüfende Auditor dauerhaft einen Fehler auf einer völlig gültigen
|
||||||
|
Kette – und könnte ihn nicht beheben.
|
||||||
|
|
||||||
## Anhängen ist selbst ein Beglaubigungsakt
|
## Anhängen ist selbst ein Beglaubigungsakt
|
||||||
|
|
||||||
|
|||||||
@@ -497,30 +497,58 @@ const beobachtungMerken = () => {
|
|||||||
// senkte damit den Substrat-Hoechststand still um eins und riss genau das
|
// senkte damit den Substrat-Hoechststand still um eins und riss genau das
|
||||||
// Fenster auf, das R180-01 ausnutzt. Deshalb wird vor allem anderen
|
// Fenster auf, das R180-01 ausnutzt. Deshalb wird vor allem anderen
|
||||||
// nachgezogen, was fehlt.
|
// nachgezogen, was fehlt.
|
||||||
|
let fehlendeAnker = [];
|
||||||
|
|
||||||
function ankerNachziehen(bisherLaenge) {
|
function ankerNachziehen(bisherLaenge) {
|
||||||
if (NUR_LOKAL || !PUSH) return;
|
|
||||||
const vorhanden = new Set(ankerRefs.map((a) => a.seq));
|
const vorhanden = new Set(ankerRefs.map((a) => a.seq));
|
||||||
const fehlend = [];
|
fehlendeAnker = [];
|
||||||
for (let n = 1; n <= bisherLaenge; n++) if (!vorhanden.has(n)) fehlend.push(n);
|
for (let n = 1; n <= bisherLaenge; n++) if (!vorhanden.has(n)) fehlendeAnker.push(n);
|
||||||
if (!fehlend.length) return;
|
if (!fehlendeAnker.length) return;
|
||||||
|
|
||||||
|
// Im PRUEFMODUS wird NICHT nachgezogen (Pentest R181-01/-02).
|
||||||
|
//
|
||||||
|
// Zwei Gruende, und beide sind grundsaetzlich:
|
||||||
|
// 1. Der Pruefmodus sagt zu, nichts zu veraendern und kein Schreibrecht zu
|
||||||
|
// brauchen. Ein Audit, das dabei still das Substrat mutiert, bricht
|
||||||
|
// genau diese Zusage.
|
||||||
|
// 2. `refs/notary/*` MUSS serverseitig gegen Schreibzugriff gesperrt sein
|
||||||
|
// (R180-01) – dieselbe Sperre lehnt den Nachtrag ab. Ein read-only
|
||||||
|
// pruefender Auditor bekaeme dadurch dauerhaft einen Fehler auf einer
|
||||||
|
// voellig gueltigen Kette, den er gar nicht beheben kann.
|
||||||
|
//
|
||||||
|
// Der fehlende Anker wird gemeldet, nicht repariert. Reparieren ist Sache
|
||||||
|
// des Notar-Schreiblaufs, dessen Identitaet in der Serversperre eine
|
||||||
|
// Ausnahme fuer das ANLEGEN von `refs/notary/*` braucht (nicht fuers
|
||||||
|
// Loeschen oder Ueberschreiben – sonst hebelt eine kompromittierte
|
||||||
|
// Notar-Identitaet den Schutz aus R180-01 selbst aus).
|
||||||
|
if (NUR_PRUEFEN || NUR_LOKAL || !PUSH) return;
|
||||||
|
|
||||||
// Die n-te Aenderung am Gegenbuch entspricht Checkpoint n.
|
// Die n-te Aenderung am Gegenbuch entspricht Checkpoint n.
|
||||||
const commits = gitStill('log', '--reverse', '--format=%H', ref, '--', LEDGER_NAME);
|
const commits = gitStill('log', '--reverse', '--format=%H', ref, '--', LEDGER_NAME);
|
||||||
const liste = commits.ok ? commits.out.split('\n').filter(Boolean) : [];
|
const liste = commits.ok ? commits.out.split('\n').filter(Boolean) : [];
|
||||||
let nachgezogen = 0;
|
let nachgezogen = 0;
|
||||||
for (const n of fehlend) {
|
const nachgetragen = new Set();
|
||||||
|
for (const n of fehlendeAnker) {
|
||||||
const sha = liste[n - 1];
|
const sha = liste[n - 1];
|
||||||
if (!sha) continue;
|
if (!sha) continue;
|
||||||
if (gitStill('push', REMOTE, `${sha}:refs/notary/seq-${n}`).ok) nachgezogen++;
|
if (gitStill('push', REMOTE, `${sha}:refs/notary/seq-${n}`).ok) {
|
||||||
|
nachgezogen++;
|
||||||
|
nachgetragen.add(n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (nachgezogen) console.log(`Fehlende Anker nachgetragen: ${nachgezogen}`);
|
if (nachgezogen) console.log(`Fehlende Anker nachgetragen: ${nachgezogen}`);
|
||||||
if (nachgezogen < fehlend.length) {
|
fehlendeAnker = fehlendeAnker.filter((n) => !nachgetragen.has(n));
|
||||||
|
if (fehlendeAnker.length) {
|
||||||
console.error(
|
console.error(
|
||||||
`${fehlend.length - nachgezogen} Anker-Ref(s) fehlen und ließen sich nicht nachtragen.\n` +
|
`${fehlendeAnker.length} Anker-Ref(s) fehlen und ließen sich nicht nachtragen: ` +
|
||||||
|
`${fehlendeAnker.join(', ')}\n` +
|
||||||
'Ohne sie ist der beglaubigte Höchststand nicht aus dem Server rekonstruierbar –\n' +
|
'Ohne sie ist der beglaubigte Höchststand nicht aus dem Server rekonstruierbar –\n' +
|
||||||
'genau die Eigenschaft, auf der dieser Schutz beruht.',
|
'genau die Eigenschaft, auf der dieser Schutz beruht.\n' +
|
||||||
|
'Wahrscheinlichste Ursache: Die Serversperre für refs/notary/* lässt auch die\n' +
|
||||||
|
'Notar-Identität nicht anlegen. Sie braucht dort eine Ausnahme zum ANLEGEN –\n' +
|
||||||
|
'ausdrücklich nicht zum Löschen oder Überschreiben.',
|
||||||
);
|
);
|
||||||
process.exit(3);
|
process.exit(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,6 +694,22 @@ if (letzter) {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
if (NUR_PRUEFEN) {
|
if (NUR_PRUEFEN) {
|
||||||
const sicher = abschliessendSicher(bisher.length);
|
const sicher = abschliessendSicher(bisher.length);
|
||||||
|
// Eigener Code fuer den fehlenden Anker (Pentest R181-02, Frage b).
|
||||||
|
//
|
||||||
|
// „Anker fehlt“ ist NICHT dasselbe wie „nicht feststellbar“: Die Kette ist
|
||||||
|
// geprueft und gueltig, nur das Substrat-Gedaechtnis ist unvollstaendig –
|
||||||
|
// ein bekannter, benannter, durch einen Notar-Lauf reparierbarer Defekt.
|
||||||
|
// Ihn in Code 3 zu werfen hiesse, eine konkrete Handlungsanweisung als
|
||||||
|
// Ungewissheit zu melden; dieselbe Trennung wie bei Genesis/Adoption.
|
||||||
|
if (fehlendeAnker.length) {
|
||||||
|
console.log(
|
||||||
|
`Anker unvollständig: refs/notary/seq-${fehlendeAnker.join(', seq-')} fehlt/fehlen.\n` +
|
||||||
|
' Die Kette selbst ist geprüft und gültig – aber der beglaubigte Höchststand ist\n' +
|
||||||
|
' ohne diese Refs nicht vollständig aus dem Server rekonstruierbar.\n' +
|
||||||
|
' Behebbar durch einen Notar-Schreiblauf; im Prüfmodus wird bewusst nichts\n' +
|
||||||
|
' gepusht (dieser Modus verändert nichts und braucht kein Schreibrecht).',
|
||||||
|
);
|
||||||
|
}
|
||||||
console.log(
|
console.log(
|
||||||
`OK: ${bisher.length} Checkpoint(s) geprüft – Signaturen gültig, Reihe lückenlos, ` +
|
`OK: ${bisher.length} Checkpoint(s) geprüft – Signaturen gültig, Reihe lückenlos, ` +
|
||||||
`Arbeitsdatei deckungsgleich mit dem signierten Stand` +
|
`Arbeitsdatei deckungsgleich mit dem signierten Stand` +
|
||||||
@@ -673,6 +717,7 @@ if (NUR_PRUEFEN) {
|
|||||||
OHNE_ANKER() + REWIND_VORBEHALT(),
|
OHNE_ANKER() + REWIND_VORBEHALT(),
|
||||||
);
|
);
|
||||||
beobachtungMerken();
|
beobachtungMerken();
|
||||||
|
if (fehlendeAnker.length) process.exit(5);
|
||||||
process.exit(sicher ? 0 : 3);
|
process.exit(sicher ? 0 : 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user