aere-quantum/verify-layer/control-negativ-sidecar.mjs

131 lines
11 KiB
JavaScript

// control-negativ-sidecar.mjs (2026-09-29, revizuirea adversariala, pista B): controlul negativ al verificarilor R, C si N din
// proba-sidecar.mjs. Trei stari pe caz: PRINS (proba a ajuns la capat si verificarile numite sunt rosii), SCAPAT, STRICAT (copia nu
// s-a incarcat, ancora nu apare exact o data, sau proba nu a ajuns la capat).
// V0 sidecar-ul de dinainte, din git (sarit si spus daca revizia nu e in istoric) -> R CONTROL x3, N, N CONTROL, C rosii
// P1 lacatul scos (scriitorii nu se mai asteapta) -> C rosu
// P2 capul atestat nu se mai compara cu intrarea count-1 -> R CONTROL (istoria rescrisa) rosu
// P3 lungimea jurnalului nu se mai compara cu count -> R CONTROL (intrari scoase) rosu
// P4 statementHash-ul capului atestat nu se mai reface -> R CONTROL (cap manipulat) rosu
// P5 un rand care nu e JSON arunca din nou (cadere) -> N rosu
// P6 --attested nu se mai cere digest -> N CONTROL rosu
// P7 semnatura capului nu se mai verifica (punctul 34, 2026-09-29) -> S CONTROL (semnatura stricata) rosu
// P8 semnatarul capului nu se mai compara cu --signer -> S CONTROL (alta cheie) rosu
// P9 un cap nesemnat trece cand --signer il cere semnat -> S CONTROL (nesemnat) rosu
// P10 o cheie clasica sub schema ml-dsa-65 trece (2026-10-03) -> S CONTROL (cheie clasica) rosu
// B15 (2026-10-18), adaptoarele pe furnizori de cloud, masurate cu proba-adaptoare-cloud.mjs pe aceeasi copie plantata, trei
// plantari pe adaptor (9 adaptoare, 27 de plantari):
// PR-<src> filtrul "ruleaza" scos: incarcatura OPRITA intra in jurnal -> "<src>: incarcatura OPRITA" rosu
// PN-<src> numele variabilelor inlocuite cu perechile nume=valoare: valoarea ajunge in jurnal -> "<src>: CONFIDENTIALITATE" rosu
// PF-<src> validarea formei scoasa: exportul altui CLI nu mai e refuzat PE NUME -> "<src>: CONTROL: export de alt CLI" rosu
// Plantarile cloud ruleaza fara verificatorul AIP-23 (AERE_VERIFY_PROOF pe o cale inexistenta): verificarea de VALID nu e tinta lor si
// ar dubla timpul; proba iese tot 1 cand are o verificare rosie.
// Copiile stau LANGA original (importul lui proof-kinds e relativ) si se sterg; originalul trebuie sa ramana octet cu octet.
// node control-negativ-sidecar.mjs -> 0 toate prinse, 1 una scapata, 2 STRICAT
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const SRC = path.join(AICI, 'sidecar.mjs');
const PROBA = path.join(AICI, 'proba-sidecar.mjs');
const PROBA_CLOUD = path.join(AICI, 'proba-adaptoare-cloud.mjs');
const REV = process.env.AERE_SIDECAR_REV_VECHE || 'b2b08a5e';
const sha = (f) => crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex');
const inainte = sha(SRC);
let prinse = 0, stricate = 0, total = 0;
const T = {
refacut: 'R CONTROL: acelasi lant refacut fata de capul atestat',
taiat: 'R CONTROL: jurnal taiat sub capul atestat',
capMan: 'R CONTROL: cap atestat manipulat',
necitibil: 'N: rand care nu e JSON',
digest: 'N CONTROL: --attested care nu e digest',
concurent: 'C: doi scriitori concurenti',
semnStricata: 'S CONTROL: o semnatura de cap stricata',
altaCheie: 'S CONTROL: capul semnat de alta cheie',
nesemnat: 'S CONTROL: un cap nesemnat',
cheieClasica: 'S CONTROL: un cap semnat cu o cheie clasica',
};
const SUMAR = { [PROBA]: /B3 sidecar \(verify layer\): \d+\/\d+ cum trebuia/, [PROBA_CLOUD]: /B15 adaptoare cloud: \d+\/\d+ cum trebuia/ };
function caz(id, text, tinte, proba = PROBA, env = {}) {
total++;
const copie = path.join(AICI, `.plantat-sidecar-${id}.mjs`);
try {
fs.writeFileSync(copie, text);
const r = spawnSync(process.execPath, [proba], { encoding: 'utf8', timeout: 400000, env: { ...process.env, AERE_SIDECAR_MODUL: copie, ...env } });
const out = (r.stdout || '') + (r.stderr || '');
if (!SUMAR[proba].test(out) || !out.includes(copie)) { stricate++; console.log(` STRICAT ${id}: proba nu a ajuns la capat pe copie (${out.trim().split('\n').pop()?.slice(0, 140)})`); return; }
const rosii = out.split('\n').filter((l) => l.includes('[RAU ]'));
const lipsa = tinte.filter((x) => !rosii.some((l) => l.includes(x)));
if (r.status === 1 && !lipsa.length) { prinse++; console.log(` PRINS ${id}: ${rosii.length} verificari rosii, intre ele: ${tinte.map((t) => t.split(':')[0]).join(', ')}`); }
else console.log(` SCAPAT ${id}: cod ${r.status}, au ramas verzi: ${lipsa.join(' | ') || '-'}`);
} finally { fs.rmSync(copie, { force: true }); fs.rmSync(copie + '.lock', { force: true }); }
}
const planta = (t, a, b) => (t.split(a).length === 2 ? t.replace(a, () => b) : null);
const CALE = `${REV}:tools/aere-verify-layer/sidecar.mjs`;
if (spawnSync('git', ['cat-file', '-e', CALE], { cwd: AICI }).status !== 0) console.log(` SARIT V0: revizia ${REV} nu e in istoricul acestui depozit; NEMASURAT aici`);
else {
const g = spawnSync('git', ['-c', 'core.autocrlf=false', 'show', CALE], { cwd: AICI, encoding: 'utf8' });
if (g.status !== 0 || g.stdout.includes('verificaFataDeCap')) { stricate++; total++; console.log(' STRICAT V0: revizia veche nu se citeste sau are deja reparatia'); }
else caz('V0', g.stdout, [T.refacut, T.taiat, T.capMan, T.necitibil, T.digest]);
}
const nou = fs.readFileSync(SRC, 'utf8');
const NU = "process.env.AERE_PLANTA_NICIODATA === 'da'";
const P = [
['P1', " try { return fn(); } finally { try { fs.unlinkSync(lacat); } catch { /* deja ridicat */ } }\n}", ` try { return fn(); } finally { try { fs.unlinkSync(lacat); } catch { /* deja ridicat */ } }\n}\nconst cuLacatAdevarat = cuLacat;\ncuLacat = (p, fn) => (${NU} ? cuLacatAdevarat(p, fn) : fn());`, [T.concurent]],
['P2', 'if (!(h === String(st.head).toLowerCase() /*PAZA:cap-rescris*/)) return', `if (!(h === String(st.head).toLowerCase() /*PAZA:cap-rescris*/) && ${NU}) return`, [T.refacut]],
['P3', 'if (!(intrari.length >= st.count /*PAZA:cap-scurt*/)) return', `if (!(intrari.length >= st.count /*PAZA:cap-scurt*/) && ${NU}) return`, [T.taiat]],
['P4', "=== cap.statementHash.toLowerCase() /*PAZA:cap-hash*/)) return", `=== cap.statementHash.toLowerCase() /*PAZA:cap-hash*/) && ${NU}) return`, [T.capMan]],
['P5', "try { return JSON.parse(l); } catch { return { necitibil: true }; }", `try { return JSON.parse(l); } catch (e) { if (${NU}) return { necitibil: true }; throw e; }`, [T.necitibil]],
['P6', 'if (!eDigest(attested)) {', `if (!eDigest(attested) && ${NU}) {`, [T.digest]],
['P7', 'if (!(ok /*PAZA:cap-semnatura*/)) return', `if (!(ok /*PAZA:cap-semnatura*/) && ${NU}) return`, [T.semnStricata]],
['P8', 'if (!(asteptat !== null && idCheie(asteptat) === id /*PAZA:cap-semnatar*/)) return', `if (!(asteptat !== null && idCheie(asteptat) === id /*PAZA:cap-semnatar*/) && ${NU}) return`, [T.altaCheie]],
['P9', 'if (!(!semnatarPem /*PAZA:cap-nesemnat*/)) return', `if (!(!semnatarPem /*PAZA:cap-nesemnat*/) && ${NU}) return`, [T.nesemnat]],
['P10', "if (!(!!pub && pub.asymmetricKeyType === 'ml-dsa-65' /*PAZA:cap-schema*/)) return", `if (!(!!pub && pub.asymmetricKeyType === 'ml-dsa-65' /*PAZA:cap-schema*/) && ${NU}) return`, [T.cheieClasica]],
];
for (const [id, a, b, tinte] of P) {
let t = planta(nou, a, b);
// P1 cere ca `cuLacat` sa poata fi reasignat: declaratia de functie devine o variabila
if (t && id === 'P1') t = planta(t, 'function cuLacat(p, fn) {', 'let cuLacat = function (p, fn) {');
if (!t) { stricate++; total++; console.log(` STRICAT ${id}: ancora nu apare exact o data`); continue; }
caz(id, t, tinte);
}
// ---- B15: adaptoarele pe furnizori de cloud ---------------------------------------------------------------------------------------
// scurgerea de nume -> valori: ancora e chiar apelul care reduce lista la nume, inlocuit cu lista intreaga (perechi nume/valoare)
const SCURGERI = {
'aws-ecs': ['numeDin([...(cd?.environment || []), ...(cd?.secrets || []), ...(over?.environment || [])] /*NUME:aws-ecs*/)', '[...(cd?.environment || []), ...(over?.environment || [])]'],
'aws-ec2': ["numeDin(i.Tags, 'Key' /*NUME:aws-ec2*/)", '(i.Tags || [])'],
'aws-lambda': ['Object.keys(f.Environment?.Variables || {}).sort() /*NUME:aws-lambda*/', '(f.Environment?.Variables || {})'],
'azure-vm': ['tagKeys: Object.keys(v.tags || {}).sort() /*NUME:azure-vm*/,', 'tagKeys: v.tags || {}, customData: v.osProfile?.customData || null,'],
'azure-container': ['numeDin(c.environmentVariables /*NUME:azure-container*/)', '(c.environmentVariables || [])'],
'azure-containerapp': ['numeDin(c.env /*NUME:azure-containerapp*/)', '(c.env || [])'],
'azure-webapp': ['numeDin(sc?.appSettings /*NUME:azure-webapp*/)', '(sc?.appSettings || [])'],
'gcp-compute': ["numeDin(i.metadata?.items, 'key' /*NUME:gcp-compute*/)", '(i.metadata?.items || [])'],
'gcp-run': ['numeDin(c.env /*NUME:gcp-run*/)', '(c.env || [])'],
};
const FARA_VERIFICATOR = { AERE_VERIFY_PROOF: path.join(AICI, 'nu-exista-verify-proof.mjs') };
for (const src of Object.keys(SCURGERI)) {
const PC = [
[`PR-${src}`, `/*RULEAZA:${src}*/))`, `/*RULEAZA:${src}*/) && ${NU})`, [`${src}: incarcatura OPRITA`]],
[`PN-${src}`, SCURGERI[src][0], SCURGERI[src][1], [`${src}: CONFIDENTIALITATE`]],
[`PF-${src}`, `/*FORMA:${src}*/))`, `/*FORMA:${src}*/) && ${NU})`, [`${src}: CONTROL: export de alt CLI`]],
];
for (const [id, a, b, tinte] of PC) {
let t = planta(nou, a, b);
// ECS filtreaza pe doua niveluri (task si container), si un task STOPPED are si containerele STOPPED: filtrul scos inseamna
// amandoua (masurat 2026-10-18: cu numai nivelul de task scos, plantarea era mascata de cel de container si SCAPA)
if (t && id === 'PR-aws-ecs') t = planta(t, '/*RULEAZA:aws-ecs-container*/))', `/*RULEAZA:aws-ecs-container*/) && ${NU})`);
if (!t) { stricate++; total++; console.log(` STRICAT ${id}: ancora nu apare exact o data`); continue; }
caz(id, t, tinte, PROBA_CLOUD, FARA_VERIFICATOR);
}
}
const ramase = fs.readdirSync(AICI).filter((f) => f.startsWith('.plantat-sidecar-'));
if (sha(SRC) !== inainte || ramase.length) { stricate++; console.log(' STRICAT originalul s-a schimbat sau au ramas copii: ' + ramase.join(',')); }
console.log(`\ncontrolul negativ al sidecar-ului: prinse ${prinse}/${total}, stricate ${stricate}`);
process.exitCode = stricate ? 2 : prinse === total ? 0 : 1;