import { EventEmitter } from 'node:events'; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawnSync } from 'node:child_process'; const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const failures = []; const assert = (condition, message) => { if (!condition) failures.push(message); }; const expectThrow = async (fn, code, message) => { try { await fn(); failures.push(message); } catch (error) { if (code && error.code !== code) failures.push(`${message} (${error.code || error.message})`); } }; const read = rel => readFileSync(join(root, rel), 'utf8'); const temp = mkdtempSync(join(tmpdir(), 'vendoo-security-137-')); try { const configPath = join(temp, 'vendoo.env'); process.env.VENDOO_DATA_DIR = temp; process.env.VENDOO_CONFIG_PATH = configPath; process.env.VENDOO_MASTER_KEY_FILE = join(temp, 'master.key'); process.env.VENDOO_SECRETS_PATH = join(temp, 'secrets.enc.json'); delete process.env.VENDOO_MASTER_KEY; writeFileSync(configPath, 'OPENAI_API_KEY=sk-test-secret-123456\nSMTP_PASS=mail-secret-987\nPORT=8124\n'); const secrets = await import(`../lib/secret-store.mjs?test=${Date.now()}`); const migration = secrets.migrateRuntimeConfigSecrets(); assert(migration.migrated.includes('OPENAI_API_KEY') && migration.migrated.includes('SMTP_PASS'), 'Runtime-Secrets wurden nicht migriert'); assert(readFileSync(configPath, 'utf8').includes('PORT=8124'), 'Nicht geheime Runtime-Konfiguration ging verloren'); assert(!readFileSync(configPath, 'utf8').includes('sk-test-secret'), 'Klartext-Secret blieb in der Runtime-Konfiguration'); const encrypted = readFileSync(process.env.VENDOO_SECRETS_PATH, 'utf8'); assert(!encrypted.includes('sk-test-secret') && !encrypted.includes('mail-secret'), 'Secret-Speicher enthält Klartext'); assert(secrets.getSecret('OPENAI_API_KEY') === 'sk-test-secret-123456', 'Entschlüsselung fehlgeschlagen'); assert(secrets.getSecretMetadata('OPENAI_API_KEY').masked.endsWith('3456'), 'Maskierung ist fehlerhaft'); assert(secrets.secretStoreStatus().encrypted >= 2, 'Verschlüsselte Secret-Anzahl ist falsch'); await expectThrow(() => secrets.setSecret('OPENAI_API_KEY', 'line1\nline2'), 'SECRET_VALUE_INVALID', 'Mehrzeiliges Secret wurde akzeptiert'); await expectThrow(() => secrets.setSecret('UNKNOWN_SECRET', 'x'), 'SECRET_NAME_INVALID', 'Unbekanntes Secret wurde akzeptiert'); const logger = await import(`../lib/structured-logger.mjs?test=${Date.now()}`); const req = { method:'GET', path:'/api/test', user:null, get(name){ return name === 'x-vendoo-request-id' ? 'test-request-1' : ''; } }; const res = new EventEmitter(); res.statusCode=200; res.setHeader=()=>{}; logger.requestContextMiddleware(req,res,()=>{}); res.emit('finish'); const telemetry = logger.getRequestTelemetry(); assert(req.requestId === 'test-request-1' && req.correlationId === 'test-request-1', 'Request-/Correlation-ID fehlt'); assert(telemetry.total === 1 && telemetry.recent[0].path === '/api/test', 'Request-Telemetrie fehlerhaft'); const server = read('server.mjs'); for (const marker of ["scriptSrcAttr: [\"'none'\"]", "id: 'security.secrets.read'", "id: 'security.secrets.update'", "policy: 'security.secrets.manage'", 'requestContextMiddleware', 'objectSchema']) assert(server.includes(marker), `Security-Serververtrag fehlt: ${marker}`); assert(!server.includes("scriptSrc: [\"'self'\", \"'unsafe-inline'\""), 'Inline-Skripte sind in CSP noch erlaubt'); assert(!read('public/index.html').includes('onclick='), 'index.html enthält Inline-Eventhandler'); assert(!read('public/login.html').includes('