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:
@@ -262,6 +262,10 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
|||||||
? ' ⚠ Bestandssiegel ENTFERNT: Es liegen versiegelte Blattwerte vor, aber kein gültiger ' +
|
? ' ⚠ Bestandssiegel ENTFERNT: Es liegen versiegelte Blattwerte vor, aber kein gültiger ' +
|
||||||
'Siegel-Marker mehr. Der Marker wurde gelöscht oder unbrauchbar gemacht – Änderungen am ' +
|
'Siegel-Marker mehr. Der Marker wurde gelöscht oder unbrauchbar gemacht – Änderungen am ' +
|
||||||
'Altbestand wären dadurch wieder unsichtbar. Das ist KEIN Normalzustand.'
|
'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'
|
: result.backlogSealStatus === 'nicht_noetig'
|
||||||
? ' Ein Bestandssiegel wird hier nicht gebraucht: Es gibt keine unsignierten Alteinträge.'
|
? ' Ein Bestandssiegel wird hier nicht gebraucht: Es gibt keine unsignierten Alteinträge.'
|
||||||
: result.backlogSealStatus === 'kein_siegel'
|
: result.backlogSealStatus === 'kein_siegel'
|
||||||
|
|||||||
@@ -914,7 +914,7 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
|||||||
* `kein_siegel` = nie erstellt. `entfernt` = Blaetter vorhanden, aber kein
|
* `kein_siegel` = nie erstellt. `entfernt` = Blaetter vorhanden, aber kein
|
||||||
* gueltiger Marker mehr – der Anker wurde herausgeloest (Pentest R174-01).
|
* 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. */
|
/** Altbestands-Zeilen, deren Inhalt vom Siegel abweicht. */
|
||||||
backlogTampered: number[];
|
backlogTampered: number[];
|
||||||
/** Gesiegelte Zeilen, die nicht mehr existieren – Beweismaterial entfernt. */
|
/** Gesiegelte Zeilen, die nicht mehr existieren – Beweismaterial entfernt. */
|
||||||
@@ -1061,7 +1061,7 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
|||||||
const backlogMissing: number[] = [];
|
const backlogMissing: number[] = [];
|
||||||
// Luecken, die das Siegel als bereits vorhanden beglaubigt (siehe unten).
|
// Luecken, die das Siegel als bereits vorhanden beglaubigt (siehe unten).
|
||||||
const beglaubigteLuecken = new Set<number>();
|
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;
|
let backlogSealRoot: string | null = null;
|
||||||
|
|
||||||
const siegelKandidaten = await prisma.auditLog.findMany({
|
const siegelKandidaten = await prisma.auditLog.findMany({
|
||||||
@@ -1164,6 +1164,17 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
|||||||
? 'intakt'
|
? 'intakt'
|
||||||
: 'gebrochen';
|
: '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
|
// Beglaubigte Alt-Luecken
|
||||||
//
|
//
|
||||||
@@ -1325,6 +1336,7 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
|||||||
// unsichtbar (R174-01).
|
// unsichtbar (R174-01).
|
||||||
const siegelInOrdnung =
|
const siegelInOrdnung =
|
||||||
backlogSealStatus === 'intakt' ||
|
backlogSealStatus === 'intakt' ||
|
||||||
|
backlogSealStatus === 'leer' ||
|
||||||
backlogSealStatus === 'kein_siegel' ||
|
backlogSealStatus === 'kein_siegel' ||
|
||||||
backlogSealStatus === 'nicht_noetig';
|
backlogSealStatus === 'nicht_noetig';
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,21 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ 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)
|
- [x] **🔒 `audit:export` gatete nichts – Export hing an `audit:read`** (2026-08-26)
|
||||||
- **Bei der Gegenprobe zur neuen Rolle `Gegenbuch` gefunden:** Ein Konto mit
|
- **Bei der Gegenprobe zur neuen Rolle `Gegenbuch` gefunden:** Ein Konto mit
|
||||||
ausschließlich `audit:read` bekam auf `GET /audit-logs/export` **200**.
|
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> = {
|
const SIEGEL_TEXT: Record<IntegrityResult['backlogSealStatus'], string> = {
|
||||||
intakt: 'versiegelt und intakt',
|
intakt: 'versiegelt und intakt',
|
||||||
|
leer: 'Siegel vorhanden, aber ohne Inhalt',
|
||||||
kein_siegel: 'nicht versiegelt',
|
kein_siegel: 'nicht versiegelt',
|
||||||
nicht_noetig: 'nicht nötig (alles signiert)',
|
nicht_noetig: 'nicht nötig (alles signiert)',
|
||||||
gebrochen: 'GEBROCHEN',
|
gebrochen: 'GEBROCHEN',
|
||||||
|
|||||||
@@ -1750,7 +1750,7 @@ export interface IntegrityResult {
|
|||||||
attestedGaps: number[];
|
attestedGaps: number[];
|
||||||
/** Signierte Zeilen, die ohne AUDIT_HMAC_KEY nicht pruefbar sind. */
|
/** Signierte Zeilen, die ohne AUDIT_HMAC_KEY nicht pruefbar sind. */
|
||||||
unverifiableEntries: number[];
|
unverifiableEntries: number[];
|
||||||
backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen' | 'entfernt' | 'nicht_noetig';
|
backlogSealStatus: 'kein_siegel' | 'intakt' | 'leer' | 'gebrochen' | 'entfernt' | 'nicht_noetig';
|
||||||
backlogTampered: number[];
|
backlogTampered: number[];
|
||||||
backlogMissing: number[];
|
backlogMissing: number[];
|
||||||
backlogSealCount: number;
|
backlogSealCount: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user