Files
vendoo/tools/verify-security-foundation-1.37.0.mjs
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

68 lines
5.4 KiB
JavaScript

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('<script>'), 'login.html enthält Inline-Script');
assert(read('public/login.html').includes('/login.js?v=1.37.0'), 'Externes Login-Script fehlt');
assert(!read('public/app.js').includes('onclick='), 'app.js erzeugt Inline-Eventhandler');
for (const file of ['lib/ebay-api.mjs','lib/etsy-api.mjs']) {
const oauth = read(file);
assert(oauth.includes("setSecret(key, value, { source: 'oauth' })"), `${file} schreibt OAuth-Tokens nicht in den Secret-Speicher`);
assert(!oauth.includes("join(__dirname, '..', '.env')"), `${file} schreibt weiterhin direkt in .env`);
}
assert(read('server.mjs').includes("setSecret('LOCAL_IMAGE_TOKEN', token, { source: 'token-regeneration' })"), 'FLUX-Token-Regeneration nutzt den Secret-Speicher nicht');
const manifest = JSON.parse(read('app/modules/security/module.json'));
assert(manifest.status === 'native' && manifest.provides.includes('security.secrets'), 'Security-Modul ist nicht nativ');
const pkg = JSON.parse(read('package.json'));
assert(pkg.version === '1.37.0', `Paketversion ist ${pkg.version}`);
const syntax = spawnSync(process.execPath, ['--check', join(root, 'public/login.js')], { encoding:'utf8' });
assert(syntax.status === 0, `login.js Syntaxfehler: ${syntax.stderr}`);
} finally { rmSync(temp, { recursive:true, force:true }); }
if (failures.length) { console.error(`Security-Foundation-Gate 1.37.0 fehlgeschlagen (${failures.length}):`); failures.forEach(item => console.error(`- ${item}`)); process.exit(1); }
console.log('Security-Foundation-Gate 1.37.0 erfolgreich: verschlüsselte Secret-Migration, Maskierung, Inputschutz, strikte Script-CSP, Request-/Correlation-IDs und Telemetrie geprüft.');