aere-quantum/verify-layer/proba-sidecar.mjs

192 lines
19 KiB
JavaScript

'use strict';
// Proba sidecar-ului B3: inregistreaza evenimente, verifica lantul, si controale NEGATIVE - o intrare manipulata rupe lantul exact la
// seq-ul ei, un hash schimbat se prinde, adaugarea peste un lant rupt e refuzata. Plus moatul: fiecare plic inregistrat e VALID
// pentru verificatorul AIP-23 comun (zero cod de verificare nou). Din 2026-09-29 (revizuirea adversariala, pista B), si:
// R un lant REFACUT de la geneza de cine poate scrie fisierul iese INTREG la verify-log (limita spusa), dar e prins fata de capul
// atestat (verify-log --attested); la fel un jurnal TAIAT si un cap atestat manipulat; un jurnal care continua capul trece
// C doi scriitori concurenti nu rup lantul (lacatul)
// N un rand care nu e JSON iese RUPT la seq-ul lui, nu cadere; --attested care nu e digest e refuzat
// node proba-sidecar.mjs -> 0 toate cum trebuia, 1 cel putin una rea, 2 cel putin una SARITA (verificatorul AIP-23 lipseste)
// Mediu: AERE_SIDECAR_MODUL (copia masurata, pentru controlul negativ), AERE_VERIFY_PROOF (verificatorul AIP-23; implicit cel din
// depozitul de dezvoltare, tools/aere-proof-protocol/verify.mjs, sau verify-proof.mjs din aere-node/tools intr-o copie publica).
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import crypto from 'node:crypto';
import { execFileSync, spawn } from 'node:child_process'; import { fileURLToPath, pathToFileURL } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const SIDE = process.env.AERE_SIDECAR_MODUL ? path.resolve(process.env.AERE_SIDECAR_MODUL) : path.join(AICI, 'sidecar.mjs');
const { verificaJurnal, hashIntrare } = await import(pathToFileURL(SIDE).href);
const VERIFY = process.env.AERE_VERIFY_PROOF ? path.resolve(process.env.AERE_VERIFY_PROOF) : path.resolve(AICI, '..', 'aere-proof-protocol', 'verify.mjs');
const areVerificator = fs.existsSync(VERIFY);
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'avl-'));
let rele = 0, bune = 0, sarite = 0;
const cer = (n, c) => { console.log(` [${c ? 'OK ' : 'RAU '}] ${n}`); if (c) bune++; else rele++; };
const sari = (n) => { console.log(` [SARIT] ${n} (verificatorul AIP-23 nu e la ${VERIFY}; dati AERE_VERIFY_PROOF)`); sarite++; };
function side(args) { try { return { cod: 0, out: execFileSync(process.execPath, [SIDE, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }) }; } catch (e) { return { cod: e.status ?? 1, out: (e.stdout || '') + (e.stderr || '') }; } }
const linii = (p) => fs.readFileSync(p, 'utf8').split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));
const valid = (f) => { try { execFileSync(process.execPath, [VERIFY, f], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); return true; } catch { return false; } };
const pk = await import(pathToFileURL(path.resolve(path.dirname(SIDE), '..', 'proof-kinds', 'proof-kinds.mjs')).href).catch(() => import(pathToFileURL(path.resolve(AICI, '..', 'proof-kinds', 'proof-kinds.mjs')).href));
try {
const log = path.join(T, 'audit.log');
const bin = path.join(T, 'aere-node'); fs.writeFileSync(bin, 'BINAR-VIU');
const sh = '0x' + crypto.createHash('sha256').update(fs.readFileSync(bin)).digest('hex');
const artefact = path.join(T, 'artefact.tar'); fs.writeFileSync(artefact, 'ARTEFACT-DESF');
cer('record runtime -> seq 0', side(['record', '--kind', 'runtime', '--artifact', bin, '--attested', sh, '--host', 'h1', '--log', log, '--at', '2026-09-26T09:00:00Z']).out.includes('seq=0'));
cer('record deployment -> seq 1', side(['record', '--kind', 'deployment', '--name', 'app', '--version', '2.0', '--content-file', artefact, '--log', log, '--at', '2026-09-26T09:01:00Z']).out.includes('seq=1'));
cer('verify-log -> INTACT cod 0', side(['verify-log', '--log', log]).cod === 0);
// moatul: fiecare plic e VALID pentru verificatorul AIP-23
const intrari = linii(log);
if (areVerificator) {
let toateValide = true;
for (const e of intrari) { const f = path.join(T, `e${e.seq}.json`); fs.writeFileSync(f, JSON.stringify(e.proof)); if (!valid(f)) toateValide = false; }
cer('moat: fiecare plic inregistrat e VALID pentru verificatorul AIP-23 comun', toateValide);
} else sari('moat: fiecare plic inregistrat e VALID pentru verificatorul AIP-23 comun');
// CONTROL NEGATIV 1: manipulez CONTINUTUL unei intrari trecute (fara sa refac hash-ul) -> lantul se rupe la ea
const man1 = intrari.map((e) => ({ ...e })); man1[0].proof = { ...man1[0].proof, statement: { ...man1[0].proof.statement, host: 'ATACATOR' } };
cer('CONTROL: continut manipulat -> lant RUPT la seq 0', verificaJurnal(man1).rupt === 0);
// CONTROL NEGATIV 2: schimb un hash din mijloc -> ruptura la intrarea urmatoare (prev nu mai leaga)
const man2 = intrari.map((e) => ({ ...e })); man2[0].hash = '0x' + 'ff'.repeat(32);
cer('CONTROL: hash schimbat -> lant RUPT', verificaJurnal(man2).ok === false);
// CONTROL NEGATIV 3: adaugarea peste un lant rupt e REFUZATA
const logRupt = path.join(T, 'rupt.log'); fs.writeFileSync(logRupt, JSON.stringify(man1[0]) + '\n');
cer('CONTROL: record peste lant rupt -> refuzat (cod != 0)', side(['record', '--kind', 'runtime', '--artifact', bin, '--attested', sh, '--log', logRupt, '--at', '2026-09-26T09:02:00Z']).cod !== 0);
// bundle: chainOk true pe jurnalul bun
const bout = path.join(T, 'bundle.json');
cer('bundle -> chainOk true', side(['bundle', '--log', log, '--out', bout, '--host', 'h1', '--at', '2026-09-26T09:03:00Z']).cod === 0 && JSON.parse(fs.readFileSync(bout, 'utf8')).chainOk === true);
// attest-head: capul devine un plic AIP-23 valid pentru verificatorul comun; notarizabil -> finalitate PQ
const hout = path.join(T, 'head.json');
cer('attest-head -> plic scris', side(['attest-head', '--log', log, '--out', hout, '--host', 'h1', '--at', '2026-09-26T09:04:00Z']).cod === 0 && fs.existsSync(hout));
const ph = JSON.parse(fs.readFileSync(hout, 'utf8')); ph.statement.head = '0x' + 'ee'.repeat(32);
const hrau = path.join(T, 'head-rau.json'); fs.writeFileSync(hrau, JSON.stringify(ph));
if (areVerificator) {
cer('attest-head: capul e VALID pentru verificatorul AIP-23 (integritate)', valid(hout));
// CONTROL NEGATIV 4: manipulez capul din plic dupa hash -> verificatorul da integritate PICAT
cer('CONTROL: cap manipulat in plic -> verificator INVALID', !valid(hrau));
} else { sari('attest-head: capul e VALID pentru verificatorul AIP-23 (integritate)'); sari('CONTROL: cap manipulat in plic -> verificator INVALID'); }
// ---- R: lantul fata de un cap atestat --------------------------------------------------------------------------------------
cer('R: jurnalul neatins CONTINUA capul atestat (cod 0)', side(['verify-log', '--log', log, '--attested', hout]).cod === 0);
// un scriitor cu acces la fisier reface TOT lantul de la geneza cu o intrare schimbata: verify-log singur nu are cum sa vada
const refacut = []; let prev = '0x' + '00'.repeat(32);
for (const e of intrari) {
const proof = e.seq === 0 ? { ...e.proof, statement: { ...e.proof.statement, host: 'ATACATOR' } } : e.proof;
const hash = hashIntrare(e.seq, prev, proof); refacut.push({ seq: e.seq, prev, proof, hash }); prev = hash;
}
const logRef = path.join(T, 'refacut.log'); fs.writeFileSync(logRef, refacut.map((e) => JSON.stringify(e)).join('\n') + '\n');
cer('R: lant refacut de la geneza -> verify-log singur iese INTACT (limita, spusa in README)', side(['verify-log', '--log', logRef]).cod === 0);
const rR = side(['verify-log', '--log', logRef, '--attested', hout]);
cer('R CONTROL: acelasi lant refacut fata de capul atestat -> NU continua, istoria rescrisa (cod 1)', rR.cod === 1 && /rewritten/.test(rR.out));
const logTaiat = path.join(T, 'taiat.log'); fs.writeFileSync(logTaiat, JSON.stringify(intrari[0]) + '\n');
const rT = side(['verify-log', '--log', logTaiat, '--attested', hout]);
cer('R CONTROL: jurnal taiat sub capul atestat -> NU continua, intrari scoase (cod 1)', rT.cod === 1 && /removed/.test(rT.out));
const logMai = path.join(T, 'continuat.log'); fs.copyFileSync(log, logMai);
side(['record', '--kind', 'deployment', '--name', 'app', '--version', '2.1', '--content-file', artefact, '--log', logMai, '--at', '2026-09-26T09:05:00Z']);
const rC = side(['verify-log', '--log', logMai, '--attested', hout]);
cer('R: jurnal continuat dupa cap -> continua, o intrare scrisa dupa (cod 0)', rC.cod === 0 && /1 written after/.test(rC.out));
const capM = JSON.parse(fs.readFileSync(hout, 'utf8')); capM.statement.count = 1; const capMf = path.join(T, 'cap-man.json'); fs.writeFileSync(capMf, JSON.stringify(capM));
const rM = side(['verify-log', '--log', log, '--attested', capMf]);
cer('R CONTROL: cap atestat manipulat (count schimbat, statementHash vechi) -> refuzat (cod 1)', rM.cod === 1 && /modified attestation/.test(rM.out));
// ---- N: intrari nevalide -------------------------------------------------------------------------------------------------
const logN = path.join(T, 'necitibil.log'); fs.writeFileSync(logN, JSON.stringify(intrari[0]) + '\n{nu e json\n');
const rN = side(['verify-log', '--log', logN]);
cer('N: rand care nu e JSON -> RUPT la seq 1, nu cadere (cod 1)', rN.cod === 1 && /BROKEN at seq 1/.test(rN.out));
cer('N CONTROL: --attested care nu e digest -> refuzat (cod 2), nimic scris', side(['record', '--kind', 'runtime', '--artifact', bin, '--attested', 'abc', '--log', path.join(T, 'n2.log')]).cod === 2 && !fs.existsSync(path.join(T, 'n2.log')));
// ---- C: doi scriitori concurenti pe acelasi jurnal ------------------------------------------------------------------------
// fiecare scriitor e un proces care adauga in bucla stransa prin modul (o pornire de proces pe inregistrare ar lasa fereastra de
// cursa prea rara ca sa se vada: masurat, controlul fara lacat iesea verde asa); la prima adaugare refuzata scriitorul iese cu 1
const logC = path.join(T, 'concurent.log'); const N = 150;
const scriitor = (id) => new Promise((rez) => {
const cod = `const s = await import(${JSON.stringify(pathToFileURL(SIDE).href)});
for (let i = 0; i < ${N}; i++) { try { s.adaugaPlicuri(${JSON.stringify(logC)}, [{ v: 1, kind: 'proba-concurenta', scriitor: '${id}', i }]); } catch { process.exitCode = 1; break; } }`;
const p = spawn(process.execPath, ['--input-type=module', '-e', cod], { stdio: 'ignore' });
p.on('exit', (c) => rez(c));
});
const coduri = await Promise.all([scriitor('a'), scriitor('b')]);
const vC = verificaJurnal(fs.readFileSync(logC, 'utf8').split('\n').filter((l) => l.trim()).map((l) => { try { return JSON.parse(l); } catch { return { necitibil: true }; } }));
cer(`C: doi scriitori concurenti x ${N} -> lant INTACT cu ${2 * N} intrari, niciun scriitor refuzat`, coduri.every((c) => c === 0) && vC.ok && vC.count === 2 * N && !fs.existsSync(logC + '.lock'));
// record --kind proof: leaga plicuri AIP-23 gata facute (provenienta agentilor AI in jurnalul inviolabil); cu Proof-of-AI si
// decizia de agent cand modulele lor sunt alaturi (depozitul de dezvoltare), altfel cu doua plicuri proof-kinds
const log2 = path.join(T, 'agent.log');
const poaiP = path.resolve(AICI, '..', 'proof-of-ai', 'proof-of-ai.mjs'); const polP = path.resolve(AICI, '..', 'agent-policy', 'agent-policy.mjs');
let plic1, plic2;
if (fs.existsSync(poaiP) && fs.existsSync(polP)) {
const { buildProofOfAi } = await import(pathToFileURL(poaiP).href);
const { definePolicy, checkAction, decisionEnvelope } = await import(pathToFileURL(polP).href);
plic1 = buildProofOfAi({ model: { name: 'claude', version: 'opus-4.8' }, prompt: 'rezuma', output: 'rezumat', tools: ['search'], agentId: 'agent-7', createdAt: '2026-09-26T09:10:00Z' });
const { policy, policyHash } = definePolicy({ agentId: 'agent-7', spend: { amount: '100', windowSeconds: 3600, asset: 'AERE' }, tools: ['search'], recipients: null });
const act = { kind: 'payment', to: '0x1', amount: '50', at: 1 };
plic2 = decisionEnvelope({ policyHash, action: act, decision: checkAction(policy, act, []), createdAt: '2026-09-26T09:11:00Z' });
} else {
plic1 = pk.buildProof('execution', { program: 'agent-7', input: 'rezuma', output: 'rezumat', createdAt: '2026-09-26T09:10:00Z' });
plic2 = pk.buildProof('identity', { subjectId: 'agent-7', publicKey: 'cheie-publica-de-proba', createdAt: '2026-09-26T09:11:00Z' });
}
const p1F = path.join(T, 'p1.json'); fs.writeFileSync(p1F, JSON.stringify(plic1));
const p2F = path.join(T, 'p2.json'); fs.writeFileSync(p2F, JSON.stringify(plic2));
cer('record --kind proof (primul plic) -> seq 0', side(['record', '--kind', 'proof', '--proof-file', p1F, '--host', 'agent', '--log', log2, '--at', '2026-09-26T09:10:00Z']).out.includes('seq=0'));
cer('record --kind proof (al doilea plic) -> seq 1', side(['record', '--kind', 'proof', '--proof-file', p2F, '--host', 'agent', '--log', log2, '--at', '2026-09-26T09:11:00Z']).out.includes('seq=1'));
cer('lantul de provenienta al agentului e INTACT', side(['verify-log', '--log', log2]).cod === 0);
// CONTROL NEGATIV 5: plic manipulat (statementHash nu se potriveste) -> record refuzat
const rauP = JSON.parse(JSON.stringify(plic1)); rauP.statement.createdAt = '2030-01-01T00:00:00Z';
const rauPF = path.join(T, 'p-rau.json'); fs.writeFileSync(rauPF, JSON.stringify(rauP));
cer('CONTROL: record --kind proof cu plic manipulat -> refuzat', side(['record', '--kind', 'proof', '--proof-file', rauPF, '--log', log2, '--at', '2026-09-26T09:12:00Z']).cod !== 0);
// ---- adaptorul de runtime (scan docker | kubernetes) ----------------------------------------------------------------------
const SECRET = 'AERE-SINTETIC-supersecret-XYZ-123'; const PAROLA = 'AERE-SINTETIC-hunter2-parola';
const dockerExport = [
{ Name: '/app-api', Image: 'sha256:' + 'ab'.repeat(32), Path: '/usr/local/bin/api', Args: ['--data-path=/var/lib/api', `--db-password=${PAROLA}`],
Config: { Image: 'example/api:3', Env: [`SECRET_TOKEN=${SECRET}`, 'JAVA_OPTS=-Xmx4g', 'PATH=/usr/bin'] },
Mounts: [{ Source: '/root/chei', Destination: '/var/lib/api' }], State: { Running: true, StartedAt: '2026-09-27T00:00:00Z' }, RestartCount: 0 },
{ Name: '/sidecar-ntp', Image: 'sha256:' + 'cd'.repeat(32), Path: 'chronyd', Args: [], Config: { Image: 'ntp:4', Env: [] }, Mounts: [], State: { Running: true, StartedAt: '2026-09-27T00:01:00Z' }, RestartCount: 2 },
{ Name: '/oprit', Image: 'sha256:' + 'ef'.repeat(32), Path: 'x', Args: [], Config: { Image: 'x:1', Env: [] }, Mounts: [], State: { Running: false } },
];
const dExp = path.join(T, 'docker.json'); fs.writeFileSync(dExp, JSON.stringify(dockerExport));
const log3 = path.join(T, 'runtime.log'); const des3 = log3 + '.descriptori.jsonl';
const s1 = side(['scan', '--source', 'docker', '--input', dExp, '--host', 'gazda-1', '--log', log3, '--at', '2026-09-27T01:00:00Z']);
cer('scan docker -> 2 plicuri (numai containerele care ruleaza; cel oprit NU)', s1.cod === 0 && linii(log3).length === 2 && !fs.readFileSync(des3, 'utf8').includes('oprit'));
cer('scan: jurnalul de runtime e INTACT', side(['verify-log', '--log', log3]).cod === 0);
if (areVerificator) {
let valide3 = true;
for (const e of linii(log3)) { const f = path.join(T, `r${e.seq}.json`); fs.writeFileSync(f, JSON.stringify(e.proof)); if (!valid(f)) valide3 = false; }
cer('scan: fiecare plic de runtime e VALID pentru verificatorul AIP-23 comun', valide3);
} else sari('scan: fiecare plic de runtime e VALID pentru verificatorul AIP-23 comun');
const textLog = fs.readFileSync(log3, 'utf8'); const textDes = fs.readFileSync(des3, 'utf8');
cer('CONFIDENTIALITATE: valoarea de mediu si parola din argumente NU apar nici in jurnal, nici in descriptori', !textLog.includes(SECRET) && !textDes.includes(SECRET) && !textLog.includes(PAROLA) && !textDes.includes(PAROLA));
cer('control pozitiv al confidentialitatii: NUMELE variabilei (SECRET_TOKEN) e in descriptor', textDes.includes('SECRET_TOKEN') && !textDes.includes('/root/chei'));
const desLinii = textDes.split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));
const shaDe = (o) => '0x' + crypto.createHash('sha256').update(Buffer.from(JSON.stringify(o, Object.keys(o).sort()), 'utf8')).digest('hex');
cer('descriptorii re-hashati = digestul din plicurile lantului', desLinii.length === 2 && desLinii.every((d) => shaDe(d.descriptor) === d.sha256 && linii(log3)[d.seq].proof.statement.sha256 === d.sha256));
// CONTROL NEGATIV 6: un descriptor schimbat dupa inregistrare nu mai are digestul din lant
const falsificat = { ...desLinii[0].descriptor, imageId: 'sha256:' + '00'.repeat(32) };
cer('CONTROL: descriptor falsificat -> digest diferit de cel din lant', shaDe(falsificat) !== linii(log3)[0].proof.statement.sha256);
// kubernetes: un pod cu doua containere, unul ruleaza, unul asteapta
const k8s = { items: [{ metadata: { namespace: 'prod', name: 'api-7f' },
spec: { containers: [{ name: 'api', image: 'api:3', command: ['node'], args: ['srv.js', `--token=${PAROLA}`], env: [{ name: 'DB_PASS', value: SECRET }], volumeMounts: [{ mountPath: '/data' }] }, { name: 'init-side', image: 'side:1' }] },
status: { containerStatuses: [{ name: 'api', image: 'api:3', imageID: 'docker-pullable://api@sha256:' + '12'.repeat(32), restartCount: 1, state: { running: { startedAt: '2026-09-27T00:05:00Z' } } },
{ name: 'init-side', image: 'side:1', imageID: '', restartCount: 0, state: { waiting: { reason: 'PodInitializing' } } }] } }] };
const kExp = path.join(T, 'k8s.json'); fs.writeFileSync(kExp, JSON.stringify(k8s));
const log4 = path.join(T, 'k8s.log');
const s2 = side(['scan', '--source', 'kubernetes', '--input', kExp, '--host', 'cluster-a', '--log', log4, '--at', '2026-09-27T01:01:00Z']);
const t4 = fs.readFileSync(log4 + '.descriptori.jsonl', 'utf8');
cer('scan kubernetes -> 1 plic (containerul care asteapta NU), fara valori, cu numele DB_PASS', s2.cod === 0 && linii(log4).length === 1 && !t4.includes(SECRET) && !t4.includes(PAROLA) && t4.includes('DB_PASS'));
// CONTROL NEGATIV 7: un export fara niciun container care ruleaza nu se inregistreaza ca "zero desfasurari"
const gol = path.join(T, 'gol.json'); fs.writeFileSync(gol, JSON.stringify([dockerExport[2]]));
cer('CONTROL: export fara containere care ruleaza -> refuzat (cod 2), nimic scris', side(['scan', '--source', 'docker', '--input', gol, '--log', path.join(T, 'gol.log')]).cod === 2 && !fs.existsSync(path.join(T, 'gol.log')));
// CONTROL NEGATIV 8: forma gresita (un obiect in loc de tabloul lui docker inspect) -> refuzat
const rau = path.join(T, 'rau.json'); fs.writeFileSync(rau, JSON.stringify({ items: [] }));
cer('CONTROL: intrare care nu e docker inspect -> refuzata (cod 2)', side(['scan', '--source', 'docker', '--input', rau, '--log', path.join(T, 'rau.log')]).cod === 2);
} catch (e) { console.log(` [RAU ] proba a cazut: ${e.message}`); rele++; }
finally { fs.rmSync(T, { recursive: true, force: true }); }
console.log(`\nB3 sidecar (verify layer): ${bune}/${bune + rele + sarite} cum trebuia${sarite ? `, ${sarite} SARITE` : ''} (sidecar: ${SIDE})`);
process.exitCode = rele ? 1 : sarite ? 2 : 0;