diff --git a/backend/src/controllers/auditLog.controller.ts b/backend/src/controllers/auditLog.controller.ts index 78670655..c5cb7aaa 100644 --- a/backend/src/controllers/auditLog.controller.ts +++ b/backend/src/controllers/auditLog.controller.ts @@ -262,6 +262,10 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) { ? ' ⚠ Bestandssiegel ENTFERNT: Es liegen versiegelte Blattwerte vor, aber kein gültiger ' + 'Siegel-Marker mehr. Der Marker wurde gelöscht oder unbrauchbar gemacht – Änderungen am ' + 'Altbestand wären dadurch wieder unsichtbar. Das ist KEIN Normalzustand.' + : result.backlogSealStatus === 'leer' + ? ' Hinweis: Es besteht ein Bestandssiegel, das aber NICHTS umschließt – ' + + 'zum Zeitpunkt des Siegelns gab es keine unsignierten Alteinträge. Es sichert ' + + 'also nichts ab. Das ist kein Fehler, aber auch keine Zusage.' : result.backlogSealStatus === 'nicht_noetig' ? ' Ein Bestandssiegel wird hier nicht gebraucht: Es gibt keine unsignierten Alteinträge.' : result.backlogSealStatus === 'kein_siegel' diff --git a/backend/src/services/audit.service.ts b/backend/src/services/audit.service.ts index b0bbca15..1e6e6369 100644 --- a/backend/src/services/audit.service.ts +++ b/backend/src/services/audit.service.ts @@ -914,7 +914,7 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{ * `kein_siegel` = nie erstellt. `entfernt` = Blaetter vorhanden, aber kein * gueltiger Marker mehr – der Anker wurde herausgeloest (Pentest R174-01). */ - backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen' | 'entfernt' | 'nicht_noetig'; + backlogSealStatus: 'kein_siegel' | 'intakt' | 'leer' | 'gebrochen' | 'entfernt' | 'nicht_noetig'; /** Altbestands-Zeilen, deren Inhalt vom Siegel abweicht. */ backlogTampered: number[]; /** Gesiegelte Zeilen, die nicht mehr existieren – Beweismaterial entfernt. */ @@ -1061,7 +1061,7 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{ const backlogMissing: number[] = []; // Luecken, die das Siegel als bereits vorhanden beglaubigt (siehe unten). const beglaubigteLuecken = new Set(); - let backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen' | 'entfernt' | 'nicht_noetig' = 'kein_siegel'; + let backlogSealStatus: 'kein_siegel' | 'intakt' | 'leer' | 'gebrochen' | 'entfernt' | 'nicht_noetig' = 'kein_siegel'; let backlogSealRoot: string | null = null; const siegelKandidaten = await prisma.auditLog.findMany({ @@ -1164,6 +1164,17 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{ ? 'intakt' : 'gebrochen'; + // Ein Siegel ueber NULL Blaettern ist rechnerisch tadellos und schuetzt + // nichts: es gab zum Zeitpunkt des Siegelns keinen Altbestand. „intakt“ + // zu melden waere formal richtig und trotzdem irrefuehrend - es liest + // sich als Schutzzusage. Der Pentester hat Stagings Leersiegel genau so + // missverstanden, und das ist der rote Faden im Kleinen: ein Signal, das + // beruhigt, wo nichts abgesichert ist. Eigener Zustand, gleiche Wertung + // wie „nicht noetig“ - kein Befund, aber auch keine Zusage. + if (backlogSealStatus === 'intakt' && blaetter.length === 0) { + backlogSealStatus = 'leer'; + } + // --------------------------------------------------------------- // Beglaubigte Alt-Luecken // @@ -1325,6 +1336,7 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{ // unsichtbar (R174-01). const siegelInOrdnung = backlogSealStatus === 'intakt' || + backlogSealStatus === 'leer' || backlogSealStatus === 'kein_siegel' || backlogSealStatus === 'nicht_noetig'; diff --git a/docs/todo.md b/docs/todo.md index 32f3d02f..24c2dcba 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,21 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🏷️ Siegel über null Blättern meldet nicht mehr „intakt"** (2026-08-26) + - Ein Bestandssiegel, das zum Zeitpunkt des Siegelns keinen Altbestand + vorfand, ist rechnerisch tadellos und schützt **nichts**. Gemeldet wurde + trotzdem `intakt` – formal richtig, aber es liest sich als Schutzzusage. + - Der Pentester hat Stagings Leersiegel genau so missverstanden und hielt es + für zahnhaltig. Das ist der rote Faden im Kleinen: ein Signal, das + beruhigt, wo nichts abgesichert ist. + - Eigener Zustand `leer` mit eigenem Text („besteht ein Bestandssiegel, das + aber NICHTS umschließt … kein Fehler, aber auch keine Zusage"). + Wertung wie `nicht_noetig`: kippt `valid` nicht. + - Dateien: `backend/src/services/audit.service.ts`, + `backend/src/controllers/auditLog.controller.ts`, + `frontend/src/services/api.ts`, + `frontend/src/pages/settings/AuditIntegrityCard.tsx` + - [x] **🔒 `audit:export` gatete nichts – Export hing an `audit:read`** (2026-08-26) - **Bei der Gegenprobe zur neuen Rolle `Gegenbuch` gefunden:** Ein Konto mit ausschließlich `audit:read` bekam auf `GET /audit-logs/export` **200**. diff --git a/frontend/src/pages/settings/AuditIntegrityCard.tsx b/frontend/src/pages/settings/AuditIntegrityCard.tsx index 9c4f046c..f4863c07 100644 --- a/frontend/src/pages/settings/AuditIntegrityCard.tsx +++ b/frontend/src/pages/settings/AuditIntegrityCard.tsx @@ -50,6 +50,7 @@ const AMPEL_TITEL: Record = { const SIEGEL_TEXT: Record = { intakt: 'versiegelt und intakt', + leer: 'Siegel vorhanden, aber ohne Inhalt', kein_siegel: 'nicht versiegelt', nicht_noetig: 'nicht nötig (alles signiert)', gebrochen: 'GEBROCHEN', diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 5bb50e6b..bd279248 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1750,7 +1750,7 @@ export interface IntegrityResult { attestedGaps: number[]; /** Signierte Zeilen, die ohne AUDIT_HMAC_KEY nicht pruefbar sind. */ unverifiableEntries: number[]; - backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen' | 'entfernt' | 'nicht_noetig'; + backlogSealStatus: 'kein_siegel' | 'intakt' | 'leer' | 'gebrochen' | 'entfernt' | 'nicht_noetig'; backlogTampered: number[]; backlogMissing: number[]; backlogSealCount: number;