Siegel ueber null Blaettern meldet nicht mehr "intakt"

Ein Bestandssiegel, das zum Zeitpunkt des Siegelns keinen Altbestand
vorfand, ist rechnerisch tadellos und schuetzt nichts. Gemeldet wurde
trotzdem "intakt" - formal richtig, aber es liest sich als
Schutzzusage. Der Pentester hat Stagings Leersiegel genau so
missverstanden und hielt es fuer zahnhaltig.

Das ist der rote Faden im Kleinen: ein Signal, das beruhigt, wo nichts
abgesichert ist. Deshalb ein eigener Zustand "leer" mit eigenem Text -
gewertet wie "nicht_noetig", kippt `valid` also nicht, sagt aber auch
nichts zu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 17:05:24 +02:00
co-authored by Claude Opus 5
parent 23505afc05
commit 909e523634
5 changed files with 35 additions and 3 deletions
@@ -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'
+14 -2
View File
@@ -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<number>();
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';
+15
View File
@@ -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**.
@@ -50,6 +50,7 @@ const AMPEL_TITEL: Record<Ampel, string> = {
const SIEGEL_TEXT: Record<IntegrityResult['backlogSealStatus'], string> = {
intakt: 'versiegelt und intakt',
leer: 'Siegel vorhanden, aber ohne Inhalt',
kein_siegel: 'nicht versiegelt',
nicht_noetig: 'nicht nötig (alles signiert)',
gebrochen: 'GEBROCHEN',
+1 -1
View File
@@ -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;