Updater 3.4 – Lifecycle Hardening #28
@@ -10,6 +10,7 @@ import {
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { applyReleaseManifest, buildReleaseManifest, MANIFEST_FILE, readAndValidateManifest } from './release-manifest-lib.mjs';
|
||||
import { createPackageManifest, PACKAGE_MANIFEST_SCHEMA } from './updater-lifecycle-3.4.mjs';
|
||||
|
||||
const temp = mkdtempSync(join(tmpdir(), 'vendoo-release-manifest-'));
|
||||
const source = join(temp, 'source');
|
||||
@@ -21,13 +22,15 @@ const requiredBodies = new Map([
|
||||
['server.mjs', 'export const server = true;\n'],
|
||||
['lib/db.mjs', 'export const db = true;\n'],
|
||||
['db/schema.sql', 'CREATE TABLE test(id INTEGER PRIMARY KEY);\n'],
|
||||
['update-manifest.json', '{"version":"1.43.0","updaterVersion":"3.3.0"}\n'],
|
||||
['update-manifest.json', '{"version":"1.43.0","updaterVersion":"3.4.0"}\n'],
|
||||
['UPDATE_VENDOO.bat', '@echo off\r\n'],
|
||||
['scripts/updater/Vendoo.Updater.Preflight.ps1', 'Write-Output preflight\n'],
|
||||
['scripts/updater/Vendoo.Updater.UI.ps1', 'Write-Output ui\n'],
|
||||
['scripts/updater/Vendoo.Updater.Host.ps1', 'Write-Output host\n'],
|
||||
['scripts/updater/Vendoo.Updater.Worker.ps1', 'Write-Output worker\n'],
|
||||
['scripts/updater/Vendoo.Updater.Worker34.ps1', 'Write-Output worker34\n'],
|
||||
['tools/apply-release-manifest.mjs', 'export {};\n'],
|
||||
['tools/updater-lifecycle-3.4.mjs', 'export const lifecycle = true;\n'],
|
||||
['tools/check-release-payload-3.4.mjs', 'export const payloadGuard = true;\n'],
|
||||
]);
|
||||
|
||||
function write(root, rel, content) {
|
||||
@@ -36,10 +39,15 @@ function write(root, rel, content) {
|
||||
writeFileSync(target, content, 'utf8');
|
||||
}
|
||||
|
||||
function writeManifest(root, removedFiles = []) {
|
||||
const manifest = buildReleaseManifest(root, { version: '1.43.0', updaterVersion: '3.3.0', removedFiles });
|
||||
writeFileSync(join(root, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
||||
return manifest;
|
||||
function writePackageManifest(root, removedFiles = []) {
|
||||
const sourceManifest = buildReleaseManifest(root, { version: '1.43.0', updaterVersion: '3.4.0', removedFiles });
|
||||
const packageManifest = createPackageManifest(sourceManifest, {
|
||||
sourceCommit: '1'.repeat(40),
|
||||
baseCommit: '2'.repeat(40),
|
||||
createdAt: '2026-07-10T12:00:00Z',
|
||||
});
|
||||
writeFileSync(join(root, MANIFEST_FILE), `${JSON.stringify(packageManifest, null, 2)}\n`, 'utf8');
|
||||
return packageManifest;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -50,7 +58,8 @@ try {
|
||||
write(destination, 'db/schema.sql', 'OLD SCHEMA');
|
||||
write(destination, 'baseline-only.txt', 'must survive overlay');
|
||||
write(destination, 'obsolete.txt', 'must be removed explicitly');
|
||||
const manifest = writeManifest(source, ['obsolete.txt']);
|
||||
const manifest = writePackageManifest(source, ['obsolete.txt']);
|
||||
if (manifest.schema !== PACKAGE_MANIFEST_SCHEMA) throw new Error('Paketmanifest verwendet nicht Schema v2.');
|
||||
if (manifest.files.some(entry => entry.path === 'logs/secret.log')) throw new Error('Runtime-Log wurde in das Release-Manifest aufgenommen.');
|
||||
const result = applyReleaseManifest(source, destination);
|
||||
if (result.copied !== manifest.files.length || result.controlCopied !== 1 || result.removed !== 1) throw new Error('Release-Zähler sind falsch.');
|
||||
@@ -61,10 +70,18 @@ try {
|
||||
readAndValidateManifest(destination, { allowExtraFiles: true });
|
||||
|
||||
let controlDeletionBlocked = false;
|
||||
try { buildReleaseManifest(source, { version: '1.43.0', updaterVersion: '3.3.0', removedFiles: [MANIFEST_FILE] }); }
|
||||
try { buildReleaseManifest(source, { version: '1.43.0', updaterVersion: '3.4.0', removedFiles: [MANIFEST_FILE] }); }
|
||||
catch (error) { controlDeletionBlocked = /Steuerdatei/.test(error.message); }
|
||||
if (!controlDeletionBlocked) throw new Error('Löschung von release-manifest.json wurde nicht blockiert.');
|
||||
|
||||
const tamperedManifest = JSON.parse(readFileSync(join(source, MANIFEST_FILE), 'utf8'));
|
||||
tamperedManifest.baseCommit = '3'.repeat(40);
|
||||
writeFileSync(join(source, MANIFEST_FILE), `${JSON.stringify(tamperedManifest, null, 2)}\n`, 'utf8');
|
||||
let provenanceBlocked = false;
|
||||
try { readAndValidateManifest(source); } catch (error) { provenanceBlocked = /Payload-Hash/.test(error.message); }
|
||||
if (!provenanceBlocked) throw new Error('Manipulierte Paket-Provenienz wurde nicht blockiert.');
|
||||
writeFileSync(join(source, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
||||
|
||||
const corrupt = join(temp, 'corrupt');
|
||||
mkdirSync(corrupt, { recursive: true });
|
||||
for (const entry of manifest.files) {
|
||||
@@ -91,7 +108,7 @@ try {
|
||||
try { applyReleaseManifest(missing, destination); } catch (error) { missingBlocked = /fehlende Paketdatei|Pflichtdatei|verweist auf fehlende/.test(error.message); }
|
||||
if (!missingBlocked) throw new Error('Fehlende Pflichtdatei db/schema.sql wurde nicht blockiert.');
|
||||
|
||||
console.log(`Release-Manifest-Gate 3.3 erfolgreich: ${manifest.files.length} Dateien hashgeprüft, Overlay bewahrt Baseline, Löschungen sind explizit, Manipulation und fehlendes db/schema.sql werden blockiert.`);
|
||||
console.log(`Release-Manifest-Gate 3.4 erfolgreich: ${manifest.files.length} Dateien hashgeprüft, Paket-Provenienz validiert, Overlay bewahrt Baseline, Löschungen sind explizit, Manipulation und fehlendes db/schema.sql werden blockiert.`);
|
||||
} finally {
|
||||
rmSync(temp, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user