Siegel-Entfernung wird erkannt (Pentest R174-01, HIGH)
Der Siegelzustand hing ausschliesslich am Marker im Audit-Log - und den kann ein DB-Schreiber ohne Schluessel loeschen. Danach meldete die Pruefung kein_siegel, also Entwarnung, ununterscheidbar von "nie versiegelt". Die Blattwerte blieben verwaist liegen und wurden nie konsultiert, die zuvor erkannte V1-Faelschung war wieder unsichtbar. Besonders bitter im Tail-Fall: Steht der Marker am Ketten-Ende - genau der Zustand direkt nach dem einmaligen seal-backlog beim Deploy - reisst beim Loeschen nicht einmal eine Luecke. Ergebnis war valid:true und "Alle Eintraege unveraendert und lueckenlos verkettet", also null Spur. Meine Antwort auf die Frage des Pentesters war damit falsch: die Luecke reisst nur, solange der Marker nicht am Ende steht. Fix: Gegen-Check "Blaetter vorhanden, aber kein gueltiger Marker" -> neuer Status entfernt statt kein_siegel, mit ausdruecklicher Meldung. Ein gebrochenes oder entferntes Siegel kippt jetzt valid auf false, auch ohne beanstandete Einzelzeile. Der beruhigende Einstiegssatz entfaellt bei Siegelproblemen. Verifiziert ueber den echten HTTP-Pfad, exakt Szenario 4: Marker nachweislich das Ketten-Ende, Marker + Middleware-Decoy geloescht, 0 Ketten-Luecken -> valid:false, Status entfernt, Klartext-Warnung. Auch der Nicht-Tail-Fall geprueft. Wegwerf-Datenbanken danach geloescht, Dev unberuehrt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -176,9 +176,13 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||||
(!result.backlogTampered.length && !result.backlogMissing.length
|
||||
? ' Der Blattbestand des Siegels stimmt nicht mehr mit der signierten Wurzel überein.'
|
||||
: '')
|
||||
: result.backlogSealStatus === 'kein_siegel'
|
||||
? ' Hinweis: Der Altbestand ist nicht versiegelt – Änderungen daran wären nicht erkennbar.'
|
||||
: '';
|
||||
: result.backlogSealStatus === 'entfernt'
|
||||
? ' ⚠ 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 === 'kein_siegel'
|
||||
? ' Hinweis: Der Altbestand ist nicht versiegelt – Änderungen daran wären nicht erkennbar.'
|
||||
: '';
|
||||
|
||||
// Erneutes Siegeln kann legitim sein, verdient aber einen Blick: es
|
||||
// ersetzt die zuvor beglaubigte Wurzel (Pentest R173-03).
|
||||
@@ -188,11 +192,18 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||||
'Jeder Marker enthält den Befund vor dem Siegeln; bei Zweifeln dort vergleichen.'
|
||||
: '';
|
||||
|
||||
// Bei problematischem Siegel darf KEIN beruhigender Einstiegssatz stehen –
|
||||
// sonst entwertet er die nachfolgende Warnung (Pentest R174-01).
|
||||
const siegelProblem =
|
||||
result.backlogSealStatus === 'gebrochen' || result.backlogSealStatus === 'entfernt';
|
||||
|
||||
const message = tampered > 0
|
||||
? `${tampered} MANIPULIERTE Einträge gefunden` + (gaps > 0 ? ` (zusätzlich ${luecken})` : '')
|
||||
: gaps > 0
|
||||
? `Keine Manipulation. ${luecken} – Inhalte unverändert.`
|
||||
: 'Alle Einträge sind unverändert und lückenlos verkettet';
|
||||
: siegelProblem
|
||||
? 'Die Kette selbst ist rechnerisch stimmig, ABER:'
|
||||
: gaps > 0
|
||||
? `Keine Manipulation. ${luecken} – Inhalte unverändert.`
|
||||
: 'Alle Einträge sind unverändert und lückenlos verkettet';
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
|
||||
@@ -791,9 +791,10 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
||||
unverifiableEntries: number[];
|
||||
/**
|
||||
* Zustand des Bestandssiegels ueber den nicht signierbaren Altbestand.
|
||||
* `kein_siegel` = nie erstellt (oder Marker nicht pruefbar).
|
||||
* `kein_siegel` = nie erstellt. `entfernt` = Blaetter vorhanden, aber kein
|
||||
* gueltiger Marker mehr – der Anker wurde herausgeloest (Pentest R174-01).
|
||||
*/
|
||||
backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen';
|
||||
backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen' | 'entfernt';
|
||||
/** Altbestands-Zeilen, deren Inhalt vom Siegel abweicht. */
|
||||
backlogTampered: number[];
|
||||
/** Gesiegelte Zeilen, die nicht mehr existieren – Beweismaterial entfernt. */
|
||||
@@ -938,7 +939,7 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
||||
// ueber denselben Weg faelschbar wie zuvor die Manifeste (R171-01).
|
||||
const backlogTampered: number[] = [];
|
||||
const backlogMissing: number[] = [];
|
||||
let backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen' = 'kein_siegel';
|
||||
let backlogSealStatus: 'kein_siegel' | 'intakt' | 'gebrochen' | 'entfernt' = 'kein_siegel';
|
||||
let backlogSealRoot: string | null = null;
|
||||
|
||||
const siegelKandidaten = await prisma.auditLog.findMany({
|
||||
@@ -971,6 +972,21 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
||||
const siegel = gueltigeSiegel[0];
|
||||
const backlogSealCount = gueltigeSiegel.length;
|
||||
|
||||
// Gegen-Check (Pentest R174-01): Der Siegelzustand hing ausschliesslich am
|
||||
// Marker – und den kann ein DB-Schreiber OHNE Schluessel loeschen. Danach
|
||||
// meldete die Pruefung `kein_siegel`, also Entwarnung, ununterscheidbar von
|
||||
// „nie versiegelt“. Steht der Marker am Ketten-Ende (genau der Zustand
|
||||
// direkt nach dem einmaligen Siegeln beim Deploy), riss dabei nicht einmal
|
||||
// eine Luecke: das Ergebnis war `valid: true` trotz gefaelschter gesiegelter
|
||||
// Zeile. Die Blaetter blieben verwaist zurueck und wurden nie konsultiert.
|
||||
//
|
||||
// Deshalb gilt jetzt: Blaetter vorhanden, aber kein gueltiger Marker =
|
||||
// Siegel ENTFERNT und damit ein Befund – nicht „nie versiegelt“.
|
||||
const blattAnzahl = await prisma.auditBacklogSeal.count();
|
||||
if (!siegel && blattAnzahl > 0 && siegelSchluessel.length > 0) {
|
||||
backlogSealStatus = 'entfernt';
|
||||
}
|
||||
|
||||
if (siegel) {
|
||||
try {
|
||||
const meta = JSON.parse(siegel.changesAfter || '{}');
|
||||
@@ -1122,8 +1138,14 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
||||
// ohne Entdopplung zaehlte dieselbe Zeile zweimal (Pentest R171-03).
|
||||
const invalidEntries = [...new Set([...tamperedEntries, ...chainGaps])].sort((a, b) => a - b);
|
||||
|
||||
// Ein gebrochenes oder entferntes Siegel muss `valid` kippen, auch wenn keine
|
||||
// einzelne Zeile beanstandet ist – sonst bliebe der stille Anker-Verlust
|
||||
// unsichtbar (R174-01).
|
||||
const siegelInOrdnung =
|
||||
backlogSealStatus === 'intakt' || backlogSealStatus === 'kein_siegel';
|
||||
|
||||
return {
|
||||
valid: invalidEntries.length === 0,
|
||||
valid: invalidEntries.length === 0 && siegelInOrdnung,
|
||||
checkedCount: logs.length,
|
||||
invalidEntries,
|
||||
tamperedEntries,
|
||||
|
||||
Reference in New Issue
Block a user