aere-proof-of-software/tools/proof-of-software/test/action.test.mjs
Aere Network 065f84e34a Aere Proof of Software 1.4.0: an attestation of what was built, from what, by whom and when, verifiable without trusting Aere Network
Every artifact's SHA-256 and size, the SBOM's digest, the git commit and tree of the source, a hybrid signature (classical + ML-DSA,
both required) and, when notarized, a first-seen time on Aere Network covered by the validators' post-quantum certificate. verify
recomputes everything from the files; --rebuild-from repeats an npm pack build from a clone the verifier chose; the SBOM is
re-derived from the committed package-lock.json or, new in 1.4.0, from the committed go.mod and go.sum; --signer requires the
signing keys you expect (1.4.0); a developer credential binds the keys to a person, judged against a trust root you choose. The
builder's declared date can only accuse, never acquit (1.4.0). Includes the GitHub Action and the hybrid signature library it uses.

Laid out as in the development repository (tools/proof-of-software/, sdk-pq-sign/, sdk/) so that nothing is rewritten for
publication. Tests and negative controls measured on 2026-09-29 are listed in README.md.
2026-09-29 22:46:57 +03:00

443 lines
36 KiB
JavaScript

// Probele actiunii GitHub Proof of Software, rulata LOCAL ca de un runner: intrarile in INPUT_* (cu liniuta, ca la runner),
// GITHUB_OUTPUT si GITHUB_STEP_SUMMARY in fisiere temporare, GITHUB_WORKSPACE pe un depozit git de proba cu un pachet npm
// mic, comis. Notarizarea merge la un server HTTP LOCAL (node:http pe 127.0.0.1), injectat prin AERE_POS_API_BASE; nicio
// cerere nu pleaca spre cloud.aere.network. Fiecare control negativ cere MOTIVUL numit (mesajul), nu doar un cod nenul.
// node aerenew/tools/proof-of-software/test/action.test.mjs
import assert from 'node:assert';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import http from 'node:http';
import crypto from 'node:crypto';
import { spawn, execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import * as pq from '../../../sdk-pq-sign/index.mjs';
import { INTRARI, IESIRI, linieIesire, Secrete, taieHex } from '../action/index.mjs';
import { packNpmFromTree, verify } from '../pos.mjs';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const DIR_POS = path.resolve(AICI, '..');
// AERE_POS_ACTIUNE_PROBA: numai pentru test/action-dovada.mjs, care ruleaza aceasta suita pe o COPIE a actiunii cu un paznic
// scos, ca sa arate ca proba lui iese rosie
const ACTIUNE = process.env.AERE_POS_ACTIUNE_PROBA || path.join(DIR_POS, 'action', 'index.mjs');
const POS = path.join(DIR_POS, 'pos.mjs');
const RADACINA = path.resolve(DIR_POS, '..', '..');
let treceri = 0; const esecuri = []; const nemasurate = [];
class Nemasurat extends Error {}
async function test(nume, fn) {
try { await fn(); treceri++; console.log(' OK ' + nume); } catch (e) {
if (e instanceof Nemasurat) { nemasurate.push(nume); console.log(' NEMASURAT ' + nume + ': ' + e.message); return; }
esecuri.push(nume); console.log(' ESEC ' + nume + ': ' + String(e.message || e).slice(0, 900));
}
}
// fiecare dosar temporar se tine minte si se sterge la capat (AERE_POS_PASTREAZA=1 le lasa, pentru depanare)
const facute = [];
const tmp = (p) => { const d = fs.mkdtempSync(path.join(os.tmpdir(), p)); facute.push(d); return d; };
const sha256 = (p) => '0x' + crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
const gitIn = (cwd, args) => execFileSync('git', ['-c', 'user.name=proba', '-c', 'user.email=proba@example.invalid', ...args], { cwd, stdio: 'pipe' }).toString().trim();
// ---------------------------------------------------------------- depozitul de proba
function depozitDeProba() {
const G = tmp('aere-pos-act-git-');
gitIn(G, ['init', '-q']); gitIn(G, ['config', 'core.autocrlf', 'false']);
fs.mkdirSync(path.join(G, 'pkg', 'lib'), { recursive: true }); fs.mkdirSync(path.join(G, 'altceva'));
fs.writeFileSync(path.join(G, 'pkg', 'package.json'), JSON.stringify({ name: 'proba-actiune', version: '1.2.3', main: 'lib/index.js', files: ['lib', 'LICENSE', 'README.md'] }, null, 1) + '\n');
fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 1;\n');
fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\nline two\n');
fs.writeFileSync(path.join(G, 'pkg', 'README.md'), '# proba\n\nun pachet de proba\n');
fs.writeFileSync(path.join(G, 'altceva', 'x.txt'), 'nu face parte din pachet\n');
gitIn(G, ['add', '-A']); gitIn(G, ['commit', '-q', '-m', 'pachet de proba']);
return G;
}
// ---------------------------------------------------------------- serverul de notarizare, LOCAL
const TX = '0x' + 'ab'.repeat(32);
let modServer = '200'; const cereri = [];
const server = http.createServer((req, res) => {
let b = ''; req.on('data', (c) => { b += c; });
req.on('end', () => {
cereri.push({ m: req.method, u: req.url, k: req.headers['x-api-key'], b });
const trimite = (cod, o) => { res.writeHead(cod, { 'content-type': 'application/json' }); res.end(JSON.stringify(o)); };
if (req.url === '/v1/leak') return trimite(200, { txHash: '0x' + 'ee'.repeat(32), block: 1 });
if (req.method !== 'POST' || req.url !== '/v1/notarize') return trimite(404, { error: 'not_found' });
let h = null; try { h = JSON.parse(b).hash; } catch { /* corp stricat */ }
if (modServer === '200') return trimite(200, { hash: h, txHash: TX, block: 4242, firstSeenAt: 1790000000, firstTime: true, contract: '0x' + '11'.repeat(20), chainId: 2800 });
if (modServer === '403') return trimite(403, { error: 'invalid api key ' + req.headers['x-api-key'] }); // ecoul cheii: redactarea trebuie sa il taie
if (modServer === '200-fara-tx') return trimite(200, { hash: h });
if (modServer === '302') { res.writeHead(302, { location: `http://127.0.0.1:${server.address().port}/v1/leak` }); return res.end(); }
return trimite(500, { error: 'mod necunoscut' });
});
});
await new Promise((r) => server.listen(0, '127.0.0.1', r));
const BAZA = `http://127.0.0.1:${server.address().port}/v1`;
// ---------------------------------------------------------------- rularea actiunii ca un runner
function parseaza(t) {
const o = {}; const L = t.split('\n');
for (let i = 0; i < L.length; i++) {
const l = L[i]; if (!l) continue;
const m = /^([^=<]+)<<(.+)$/.exec(l);
if (m) { const v = []; i++; while (i < L.length && L[i] !== m[2]) v.push(L[i++]); assert.ok(!(m[1] in o), 'output written twice: ' + m[1]); o[m[1]] = v.join('\n'); continue; }
const k = l.indexOf('='); assert.ok(k > 0, 'malformed output line: ' + l); assert.ok(!(l.slice(0, k) in o), 'output written twice'); o[l.slice(0, k)] = l.slice(k + 1);
}
return o;
}
function mediuCurat() { const e = {}; for (const [k, v] of Object.entries(process.env)) if (!/^(INPUT_|GITHUB_|RUNNER_|AERE_POS_)/i.test(k)) e[k] = v; return e; }
const toateRularile = [];
async function ruleaza(eticheta, { workspace, inputs = {}, env = {}, actiune = ACTIUNE, faraImplicite = false, secrete = false }) {
const T = tmp('aere-pos-act-run-');
const OUTF = path.join(T, 'github_output'), SUMF = path.join(T, 'step_summary');
fs.writeFileSync(OUTF, ''); fs.writeFileSync(SUMF, '');
// runner-ul pune in INPUT_* implicitele din action.yml; faraImplicite lasa actiunea sa le puna singura
const inp = faraImplicite ? { ...inputs } : { build: 'npm-pack', 'verify-rebuild': 'true', ...inputs };
const e = { ...mediuCurat(), GITHUB_WORKSPACE: workspace, GITHUB_OUTPUT: OUTF, GITHUB_STEP_SUMMARY: SUMF, RUNNER_TEMP: T, ...env };
for (const [k, v] of Object.entries(inp)) e['INPUT_' + k.toUpperCase()] = v;
cereri.length = 0;
const rez = await new Promise((res, rej) => {
const c = spawn(process.execPath, [actiune], { cwd: workspace, env: e, stdio: ['ignore', 'pipe', 'pipe'] });
let o = '', er = ''; c.stdout.on('data', (d) => { o += d; }); c.stderr.on('data', (d) => { er += d; });
const t = setTimeout(() => c.kill(), 240000);
c.on('error', rej); c.on('close', (cod) => { clearTimeout(t); res({ cod, stdout: o, stderr: er }); });
});
const iesiriBrut = fs.readFileSync(OUTF, 'utf8');
const outDir = path.join(T, 'aere-proof-of-software');
const r = { eticheta, secrete, ...rez, iesiriBrut, iesiri: parseaza(iesiriBrut), sumar: fs.readFileSync(SUMF, 'utf8'), outDir, cereri: [...cereri], mesaj: rez.stdout + '\n' + rez.stderr };
toateRularile.push(r);
return r;
}
const fisiereAtestare = (r) => (fs.existsSync(r.outDir) ? fs.readdirSync(r.outDir).filter((f) => f.endsWith('.json')).map((f) => [`fisier ${f}`, fs.readFileSync(path.join(r.outDir, f), 'utf8')]) : []);
// ---------------------------------------------------------------- secretele plantate, recognoscibile
const CHEI = pq.generateKeyPair({ alg: 'secp256k1+ml-dsa-44', seed: '0x' + 'deadbeef'.repeat(8), classicalSecretKey: '0x' + 'c0ffee00'.repeat(8) });
const CHEIE_TEXT = JSON.stringify(CHEI, null, 1);
// AERE-SINTETIC: cheie API plantata de proba (forma unei chei Cloud, valoare inventata), ca sa se vada ca nu apare in nicio iesire
const API_KEY = 'ak2800.0x' + '1234abcd'.repeat(5) + '.PLANTEDAPISECRET' + 'f00d'.repeat(8);
const pqBare = CHEI.pq.secretKey.slice(2);
// ATENTIE: primii 32 de octeti ai cheii secrete ML-DSA sunt rho, care e si inceputul cheii PUBLICE (publicata in atestare,
// in semnatura). Deci nu se cauta inceputul cheii secrete: se cauta K (octetii 32..63) si o bucata din s1 (128..159).
const SECRETE_CAUTATE = {
'aere-api-key intreaga': API_KEY,
'aere-api-key partea secreta': 'PLANTEDAPISECRET' + 'f00d'.repeat(8),
'marcajul cheii API': 'PLANTEDAPISECRET',
'cheia clasica': CHEI.classical.secretKey.slice(2),
'o bucata din cheia clasica': 'c0ffee00c0ffee00',
'samanta ML-DSA': 'deadbeefdeadbeef',
'K din cheia ML-DSA': pqBare.slice(64, 128),
's1 din cheia ML-DSA': pqBare.slice(256, 320),
'marcajul cheii stricate': 'PLANTEDBADKEY9c0de',
'marcajul cheii API cu rand nou': 'PLANTEDNL',
};
const cautaSecrete = (text) => { const t = String(text).toLowerCase(); return Object.entries(SECRETE_CAUTATE).filter(([, v]) => t.includes(v.toLowerCase())).map(([k]) => k); };
const faraMasti = (s) => s.split(/\r?\n/).filter((l) => !l.startsWith('::add-mask::')).join('\n');
// ================================================================ probele
console.log('actiunea: ' + ACTIUNE);
await test('action.yml si index.mjs declara ACELEASI intrari (cu implicitele si obligativitatea lor) si iesiri; runs node24 cu index.mjs; index.mjs importa static numai module node:', () => {
const y = fs.readFileSync(path.join(DIR_POS, 'action', 'action.yml'), 'utf8');
const L = y.split(/\r?\n/);
const bloc = (nume) => { const i = L.indexOf(nume + ':'); assert.ok(i >= 0, 'no ' + nume + ' block'); const out = []; for (let j = i + 1; j < L.length && (L[j].startsWith(' ') || !L[j].trim()); j++) out.push(L[j]); return out; };
const campuri = (linii) => { const o = {}; let cur = null; for (const l of linii) { const m = /^ {2}([a-z][a-z0-9-]*):/.exec(l); if (m) { cur = m[1]; o[cur] = {}; continue; } const d = /^ {4}(default|required):\s*'?([^']*?)'?\s*$/.exec(l); if (d && cur) o[cur][d[1]] = d[2]; } return o; };
const intrari = campuri(bloc('inputs'));
assert.deepStrictEqual(Object.keys(intrari).sort(), Object.keys(INTRARI).sort());
for (const [k, v] of Object.entries(INTRARI)) {
assert.strictEqual(intrari[k].default, v.default, `default of ${k}`);
assert.strictEqual(intrari[k].required === 'true', v.required === true, `required of ${k}`);
}
assert.deepStrictEqual(Object.keys(campuri(bloc('outputs'))).sort(), [...IESIRI].sort());
assert.match(y, /^ {2}using: node24$/m); assert.match(y, /^ {2}main: index\.mjs$/m);
const src = fs.readFileSync(ACTIUNE, 'utf8');
const statice = [...src.matchAll(/^import .* from '([^']+)';/gm)].map((m) => m[1]);
assert.ok(statice.length >= 5, 'the import scan found nothing (method control)');
assert.deepStrictEqual(statice.filter((s) => !s.startsWith('node:')), [], 'static imports outside node:');
assert.strictEqual([...src.matchAll(/await import\(/g)].length, 2, 'exactly two dynamic imports: pos.mjs and the Cloud client, by relative path');
for (const f of ['action/index.mjs', 'action/action.yml', 'action/README.md', 'action/examples/attest-npm-package.yml', 'test/action.test.mjs']) {
// caracterul se construieste din cod: scris literal (sau ca secventa de evadare, pe care unealta de scriere o
// transforma in caracter) proba s-ar gasi pe ea insasi
assert.ok(!fs.readFileSync(path.join(DIR_POS, f), 'utf8').includes(String.fromCharCode(0x2014)), 'em-dash in ' + f);
}
});
await test('iesirile: nume=valoare pe un rand; o valoare pe mai multe randuri primeste delimitator si se citeste inapoi identic, fara sa poata injecta alta iesire', () => {
assert.strictEqual(linieIesire('a', 'b c'), 'a=b c\n');
const v = 'rand 1\nrand 2\r\nrand 3'; const l = linieIesire('x', v);
assert.match(l, /^x<<ghadelimiter_[0-9a-f-]{36}\n/); assert.deepStrictEqual(parseaza(l), { x: v });
assert.deepStrictEqual(Object.keys(parseaza(linieIesire('x', 'a\ny=z'))), ['x']);
});
await test('curatarea: o valoare secreta dispare si scrisa cu alte litere; sub 8 caractere nu se inregistreaza; un sir hexa lung dintr-un mesaj de biblioteca e taiat, unul scurt nu', () => {
const s = new Secrete(); s.adauga('0x' + 'AbCd'.repeat(16));
assert.strictEqual(s.curata('x 0x' + 'abcd'.repeat(16) + ' y'), 'x *** y');
assert.strictEqual(s.curata('ABCD'.repeat(16)), '***');
assert.deepStrictEqual(s.adauga('scurt'), []);
assert.strictEqual(taieHex('key ' + 'f'.repeat(64) + ' end'), 'key [long hex removed] end');
assert.strictEqual(taieHex('abbrev ' + 'a'.repeat(31)), 'abbrev ' + 'a'.repeat(31));
});
const G = depozitDeProba();
const TGZ_REF = packNpmFromTree(G, 'HEAD:pkg', tmp('aere-pos-act-ref-'));
const SHA_REF = sha256(TGZ_REF);
const R1 = await ruleaza('pozitiv simplu', { workspace: G, inputs: { 'source-path': 'pkg' } });
await test('POZITIV: atestare + reconstructie dintr-o clona curata; iesirile scrise corect; nesemnat si NEnotarizat, iar rezumatul o spune', () => {
assert.strictEqual(R1.cod, 0, R1.mesaj);
const o = R1.iesiri;
assert.deepStrictEqual(Object.keys(o).sort(), ['artifact-path', 'artifact-sha256', 'attestation-path', 'statement-hash', 'tree']);
assert.ok(fs.existsSync(o['artifact-path']) && fs.existsSync(o['attestation-path']));
assert.strictEqual(o['artifact-sha256'], sha256(o['artifact-path']));
assert.strictEqual(o['artifact-sha256'], SHA_REF, 'the artifact is npm pack of the committed tree');
assert.strictEqual(o.tree, gitIn(G, ['rev-parse', 'HEAD:pkg']));
const att = JSON.parse(fs.readFileSync(o['attestation-path'], 'utf8'));
assert.strictEqual(att.statementHash, o['statement-hash']); assert.strictEqual(att.statement.artifacts[0].sha256, o['artifact-sha256']);
assert.strictEqual(att.statement.source.path, 'pkg'); assert.strictEqual(att.statement.source.pathDirty, false);
assert.strictEqual(att.statement.source.commit, gitIn(G, ['rev-parse', 'HEAD'])); assert.strictEqual(att.statement.build.kind, 'npm-pack');
assert.strictEqual(att.signature, null); assert.strictEqual(att.notarization, null);
assert.match(R1.stdout, /OK {3}rebuild: npm pack of the attested tree reproduces proba-actiune-1\.2\.3\.tgz byte for byte/);
assert.match(R1.sumar, /\*\*NOT notarized\*\*/); assert.match(R1.sumar, /\*\*unsigned\*\*/); assert.match(R1.sumar, /reproduced byte for byte from a clean clone/);
assert.strictEqual(R1.cereri.length, 0, 'no request is made without an API key');
});
await test('un STRAIN verifica atestarea iesita cu `pos.mjs verify --rebuild-from` pe clona LUI: VALID, reconstructia reproduce artefactul', () => {
const C = tmp('aere-pos-act-strain-'); gitIn(os.tmpdir(), ['clone', '-q', G, C]);
const out = execFileSync(process.execPath, [POS, 'verify', R1.iesiri['attestation-path'], '--rebuild-from', C, R1.iesiri['artifact-path']], { stdio: 'pipe' }).toString();
assert.match(out, /OK {3}rebuild: npm pack of the attested tree reproduces/); assert.match(out, /VALID: every present claim holds/);
});
modServer = '200';
const R2 = await ruleaza('semnat + notarizat', { workspace: G, inputs: { 'source-path': 'pkg', 'signing-key': CHEIE_TEXT, 'aere-api-key': API_KEY }, env: { AERE_POS_API_BASE: BAZA }, secrete: true });
await test('POZITIV: semnat hibrid + notarizat (serverul raspunde 200): notarized-tx si proof-url, chitanta in atestare, o singura cerere cu cheia si hash-ul corect; pos.mjs verify citeste chitanta', async () => {
assert.strictEqual(R2.cod, 0, R2.mesaj);
const o = R2.iesiri; const att = JSON.parse(fs.readFileSync(o['attestation-path'], 'utf8'));
assert.strictEqual(o['notarized-tx'], TX); assert.strictEqual(o['proof-url'], `${BAZA}/proof/${att.statementHash}`);
assert.strictEqual(R2.cereri.length, 1); const c = R2.cereri[0];
assert.strictEqual(c.m, 'POST'); assert.strictEqual(c.u, '/v1/notarize'); assert.strictEqual(c.k, API_KEY);
assert.deepStrictEqual(JSON.parse(c.b), { hash: att.statementHash });
assert.strictEqual(att.notarization.txHash, TX); assert.strictEqual(att.notarization.block, 4242); assert.strictEqual(att.notarization.proof, '/v1/proof/' + att.statementHash);
assert.strictEqual(att.signature.alg, 'secp256k1+ml-dsa-44'); assert.strictEqual(att.signature.classicalPublicKey, CHEI.classical.publicKey);
assert.strictEqual(o['artifact-sha256'], SHA_REF, 'signing does not change the artifact');
const lant = { proof: async () => ({ notarized: true, block: 4242, txHash: TX, finality: 'post-quantum', pqAnchor: null }) };
const r = await verify(att, [o['artifact-path']], { cloud: lant });
assert.strictEqual(r.valid, true, JSON.stringify(r.checks));
assert.ok(r.checks.find((x) => x.name.startsWith('hybrid signature') && x.pass === true));
assert.ok(r.checks.find((x) => x.name.startsWith('on chain: first appearance matches the receipt') && x.pass === true));
const r0 = await verify(att, [o['artifact-path']], { cloud: { proof: async () => ({ notarized: true, block: 4243, txHash: TX, finality: 'post-quantum' }) } });
assert.strictEqual(r0.valid, false, 'control: a chain answering another block must make it INVALID');
assert.match(R2.sumar, /tx `0xabab/); assert.doesNotMatch(R2.sumar, /NOT notarized/);
});
const SH = tmp('aere-pos-act-shallow-'); gitIn(os.tmpdir(), ['clone', '-q', '--depth', '1', pathToFileURL(G).href, SH]);
const R3 = await ruleaza('clona superficiala, implicitele actiunii', { workspace: SH, inputs: { 'source-path': 'pkg' }, faraImplicite: true });
await test('POZITIV: spatiu de lucru SUPERFICIAL (ca actions/checkout implicit, fetch-depth 1) si fara INPUT_BUILD/INPUT_VERIFY-REBUILD: implicitele din cod, acelasi artefact', () => {
assert.strictEqual(gitIn(SH, ['rev-parse', '--is-shallow-repository']), 'true', 'control: the workspace really is shallow');
assert.strictEqual(R3.cod, 0, R3.mesaj);
assert.match(R3.stdout, /OK {3}rebuild: npm pack of the attested tree reproduces/);
assert.strictEqual(R3.iesiri['artifact-sha256'], SHA_REF);
});
const DH = tmp('aere-pos-act-detasat-'); gitIn(os.tmpdir(), ['clone', '-q', G, DH]); gitIn(DH, ['config', 'core.autocrlf', 'false']);
gitIn(DH, ['checkout', '-q', '--detach']); fs.writeFileSync(path.join(DH, 'pkg', 'lib', 'index.js'), 'module.exports = 3;\n'); gitIn(DH, ['commit', '-q', '-am', 'commit detasat']);
const R4 = await ruleaza('HEAD detasat', { workspace: DH, inputs: { 'source-path': 'pkg' } });
await test('POZITIV: HEAD detasat pe un commit care nu e pe nicio ramura (ca un checkout de pull request): clona curata il contine si reconstructia trece', () => {
assert.strictEqual(gitIn(DH, ['for-each-ref', '--contains', 'HEAD', 'refs/heads']), '', 'control: no branch contains the commit');
assert.strictEqual(R4.cod, 0, R4.mesaj);
const att = JSON.parse(fs.readFileSync(R4.iesiri['attestation-path'], 'utf8'));
assert.strictEqual(att.statement.source.commit, gitIn(DH, ['rev-parse', 'HEAD']));
assert.notStrictEqual(R4.iesiri['artifact-sha256'], SHA_REF, 'other content, other artifact');
assert.strictEqual(R4.iesiri['artifact-sha256'], sha256(packNpmFromTree(DH, 'HEAD:pkg', tmp('aere-pos-act-ref2-'))));
});
const W = tmp('aere-pos-act-crlf-'); gitIn(os.tmpdir(), ['clone', '-q', '-c', 'core.autocrlf=true', G, W]);
const R5 = await ruleaza('copie de lucru CRLF', { workspace: W, inputs: { 'source-path': 'pkg' } });
await test('POZITIV: copie de lucru cu CRLF (core.autocrlf=true, ca un runner Windows): nu e "murdara", iar artefactul e acelasi, din octetii comisi', () => {
assert.ok(fs.readFileSync(path.join(W, 'pkg', 'LICENSE'), 'utf8').includes('\r\n'), 'control: the checkout really has CRLF');
assert.strictEqual(R5.cod, 0, R5.mesaj);
assert.strictEqual(R5.iesiri['artifact-sha256'], SHA_REF);
});
fs.appendFileSync(path.join(G, 'altceva', 'x.txt'), 'schimbat, in afara pachetului\n');
const R6 = await ruleaza('schimbare in afara source-path', { workspace: G, inputs: { 'source-path': 'pkg' } });
gitIn(G, ['checkout', '-q', '--', 'altceva/x.txt']);
await test('POZITIV (marginea gardei): o schimbare necomisa IN AFARA source-path nu opreste atestarea; declaratia spune dirty=true, pathDirty=false', () => {
assert.strictEqual(R6.cod, 0, R6.mesaj);
const att = JSON.parse(fs.readFileSync(R6.iesiri['attestation-path'], 'utf8'));
assert.strictEqual(att.statement.source.dirty, true); assert.strictEqual(att.statement.source.pathDirty, false);
assert.strictEqual(R6.iesiri['artifact-sha256'], SHA_REF);
});
const R7 = await ruleaza('cheie base64', { workspace: G, inputs: { 'source-path': 'pkg', 'signing-key': Buffer.from(CHEIE_TEXT).toString('base64'), 'verify-rebuild': 'false' }, secrete: true });
await test('POZITIV: cheia de semnare data ca base64 al fisierului keygen; cu verify-rebuild=false rezumatul spune ca reconstructia NU e verificata', () => {
assert.strictEqual(R7.cod, 0, R7.mesaj);
const att = JSON.parse(fs.readFileSync(R7.iesiri['attestation-path'], 'utf8'));
assert.strictEqual(att.signature.classicalPublicKey, CHEI.classical.publicKey);
assert.match(R7.sumar, /\*\*NOT verified\*\*: verify-rebuild is false/);
assert.doesNotMatch(R7.stdout, /rebuild: npm pack of the attested tree reproduces/);
});
// ---------------------------------------------------------------- controalele negative
fs.writeFileSync(path.join(G, 'pkg', 'nou.txt'), 'necomis\n');
const Ra = await ruleaza('(a) fisier neurmarit', { workspace: G, inputs: { 'source-path': 'pkg' } });
fs.rmSync(path.join(G, 'pkg', 'nou.txt'));
await test('CONTROL NEGATIV (a): un fisier neurmarit in source-path -> refuz cu motivul si fisierul numite; fara iesiri, fara artefact', () => {
assert.notStrictEqual(Ra.cod, 0);
assert.match(Ra.mesaj, /source-path "pkg" has uncommitted or untracked files/); assert.match(Ra.stderr, /\?\? pkg\/nou\.txt/);
assert.match(Ra.stdout, /^::error::source-path "pkg" has uncommitted or untracked files/m);
assert.strictEqual(Ra.iesiriBrut, ''); assert.ok(!fs.existsSync(Ra.outDir) || !fs.readdirSync(Ra.outDir).length);
});
fs.appendFileSync(path.join(G, 'pkg', 'lib', 'index.js'), '// schimbat si necomis\n');
const Ra2 = await ruleaza('(a) fisier modificat', { workspace: G, inputs: { 'source-path': 'pkg' } });
gitIn(G, ['checkout', '-q', '--', 'pkg/lib/index.js']);
await test('CONTROL NEGATIV (a, varianta): un fisier urmarit modificat si necomis in source-path -> refuz, cu fisierul numit', () => {
assert.notStrictEqual(Ra2.cod, 0);
assert.match(Ra2.mesaj, /has uncommitted or untracked files/); assert.match(Ra2.stderr, / M pkg\/lib\/index\.js/);
assert.strictEqual(Ra2.iesiriBrut, '');
});
modServer = '403';
const Rb = await ruleaza('(b) 403', { workspace: G, inputs: { 'source-path': 'pkg', 'signing-key': CHEIE_TEXT, 'aere-api-key': API_KEY }, env: { AERE_POS_API_BASE: BAZA }, secrete: true });
await test('CONTROL NEGATIV (b): serverul de notarizare raspunde 403 -> actiunea cade, spune HTTP 403 si NOT notarized, nu scrie proof-url; cererea a ajuns (refuzul e pe RASPUNS)', () => {
assert.notStrictEqual(Rb.cod, 0);
assert.match(Rb.mesaj, /notarization refused: HTTP 403 \(invalid api key \*\*\*\); the attestation is NOT notarized/);
assert.strictEqual(Rb.cereri.length, 1, 'the request reached the server');
assert.ok(!('proof-url' in Rb.iesiri) && !('notarized-tx' in Rb.iesiri)); assert.strictEqual(Rb.iesiriBrut, '');
assert.match(Rb.sumar, /The attestation is NOT notarized\./); assert.doesNotMatch(Rb.sumar, /proof:/);
const ramas = fisiereAtestare(Rb); assert.strictEqual(ramas.length, 1); assert.strictEqual(JSON.parse(ramas[0][1]).notarization, null);
});
modServer = '200-fara-tx';
const Rb2 = await ruleaza('(b) 200 fara txHash', { workspace: G, inputs: { 'source-path': 'pkg', 'aere-api-key': API_KEY, 'verify-rebuild': 'false' }, env: { AERE_POS_API_BASE: BAZA }, secrete: true });
await test('CONTROL NEGATIV (b, varianta): 200 fara txHash si bloc -> cade; un 200 gol nu e o notarizare', () => {
assert.notStrictEqual(Rb2.cod, 0);
assert.match(Rb2.mesaj, /notarization answered 200 without a transaction hash and a block; the attestation is NOT notarized/);
assert.strictEqual(Rb2.iesiriBrut, '');
});
modServer = '302';
const Rb3 = await ruleaza('(b) redirectare', { workspace: G, inputs: { 'source-path': 'pkg', 'aere-api-key': API_KEY, 'verify-rebuild': 'false' }, env: { AERE_POS_API_BASE: BAZA }, secrete: true });
await test('CONTROL NEGATIV (b, varianta): o redirectare 302 NU se urmeaza (ar duce x-api-key in alta parte) -> cade; tinta redirectarii nu primeste nimic', () => {
assert.notStrictEqual(Rb3.cod, 0);
assert.match(Rb3.mesaj, /notarization request failed: .*redirect/);
assert.strictEqual(Rb3.cereri.filter((c) => c.u === '/v1/leak').length, 0); assert.strictEqual(Rb3.cereri.length, 1);
assert.strictEqual(Rb3.iesiriBrut, '');
});
const Rb4 = await ruleaza('(b) baza API straina', { workspace: G, inputs: { 'source-path': 'pkg', 'aere-api-key': API_KEY }, env: { AERE_POS_API_BASE: 'https://example.invalid/v1' }, secrete: true });
await test('CONTROL NEGATIV (b, varianta): AERE_POS_API_BASE spre o gazda care nu e loopback -> refuz inainte de orice cerere', () => {
assert.notStrictEqual(Rb4.cod, 0);
assert.match(Rb4.mesaj, /AERE_POS_API_BASE may point only to a loopback address/);
assert.strictEqual(Rb4.iesiriBrut, '');
});
modServer = '200';
const Rc = await ruleaza('(c) artefact alterat dupa atestare', { workspace: G, inputs: { 'source-path': 'pkg', 'aere-api-key': API_KEY }, env: { AERE_POS_TEST_HOOK: 'alter-after-attest', AERE_POS_API_BASE: BAZA }, secrete: true });
await test('CONTROL NEGATIV (c): un octet schimbat in artefact intre atestare si verificare -> verificarea cade pe digestul artefactului; nimic nu se notarizeaza', () => {
assert.notStrictEqual(Rc.cod, 0);
assert.match(Rc.mesaj, /verification of the fresh attestation failed; nothing is notarized/);
assert.match(Rc.mesaj, /FAIL artifact proba-actiune-1\.2\.3\.tgz: sha256 and size match \(got 0x[0-9a-f]{64}\)/);
assert.match(Rc.stdout, /TEST HOOK ACTIVE: alter-after-attest/);
assert.match(Rc.stdout, /OK {3}rebuild: npm pack of the attested tree reproduces/, 'the rebuild itself held: the failure is the one named');
assert.strictEqual(Rc.cereri.length, 0, 'nothing was sent for notarization'); assert.strictEqual(Rc.iesiriBrut, '');
});
const Rc2 = await ruleaza('(c) artefact strain de arbore', { workspace: G, inputs: { 'source-path': 'pkg' }, env: { AERE_POS_TEST_HOOK: 'foreign-artifact' } });
await test('CONTROL NEGATIV (c, varianta): un artefact care NU vine din arborele comis (alterat inainte de atestare) -> digestul tine, reconstructia NU reproduce, refuz', () => {
assert.notStrictEqual(Rc2.cod, 0);
assert.match(Rc2.mesaj, /does NOT reproduce the artifact byte for byte; nothing is notarized/);
assert.match(Rc2.mesaj, /FAIL rebuild: npm pack of the attested tree reproduces proba-actiune-1\.2\.3\.tgz byte for byte \(got 0x[0-9a-f]{64}/);
assert.match(Rc2.stdout, /OK {3}artifact proba-actiune-1\.2\.3\.tgz: sha256 and size match/);
assert.strictEqual(Rc2.iesiriBrut, '');
});
const Rc3 = await ruleaza('(c) artefact strain, fara reconstructie', { workspace: G, inputs: { 'source-path': 'pkg', 'verify-rebuild': 'false' }, env: { AERE_POS_TEST_HOOK: 'foreign-artifact' } });
await test('perechea lui (c, varianta): ACELASI artefact strain cu verify-rebuild=false TRECE (numai digesturile), si rezumatul spune NOT verified: reconstructia e singura care il prinde', () => {
assert.strictEqual(Rc3.cod, 0, Rc3.mesaj);
assert.match(Rc3.sumar, /\*\*NOT verified\*\*/); assert.notStrictEqual(Rc3.iesiri['artifact-sha256'], SHA_REF);
});
const cheieRea = '{"alg":"secp256k1+ml-dsa-44","classical":{"secretKey":"0xPLANTEDBADKEY9c0de' + 'ab'.repeat(20);
const Rd1 = await ruleaza('(d) cheie JSON stricat', { workspace: G, inputs: { 'source-path': 'pkg', 'signing-key': cheieRea }, secrete: true });
// AERE-SINTETIC: o cheie de semnare de alta forma, inventata pentru proba de refuz
const Rd2 = await ruleaza('(d) cheie de alta forma', { workspace: G, inputs: { 'source-path': 'pkg', 'signing-key': JSON.stringify({ alg: 'x', secret: 'PLANTEDBADKEY9c0de-forma' }) }, secrete: true });
const alta = pq.generateKeyPair({ alg: 'secp256k1+ml-dsa-44' });
const Rd3 = await ruleaza('(d) cheie publica nepotrivita', { workspace: G, inputs: { 'source-path': 'pkg', 'signing-key': JSON.stringify({ ...CHEI, classical: { ...CHEI.classical, publicKey: alta.classical.publicKey } }), 'verify-rebuild': 'false' }, secrete: true });
const Rd4 = await ruleaza('(d) cheie API cu rand nou', { workspace: G, inputs: { 'source-path': 'pkg', 'aere-api-key': 'ak2800.0x' + '9876fedc'.repeat(5) + '.PLANTEDNL' + 'beef'.repeat(4) + '\nsecond-line-value' }, env: { AERE_POS_API_BASE: BAZA }, secrete: true });
await test('intrarile secrete stricate sunt refuzate cu motivul numit (JSON stricat, alta forma, cheie publica nepotrivita, rand nou in cheia API), fara sa trimita nimic', () => {
assert.notStrictEqual(Rd1.cod, 0); assert.match(Rd1.mesaj, /signing-key is not valid JSON \(nor base64 of JSON\); its content is not shown/);
assert.notStrictEqual(Rd2.cod, 0); assert.match(Rd2.mesaj, /signing-key is JSON but not a key file written by `pos\.mjs keygen`/);
assert.notStrictEqual(Rd3.cod, 0); assert.match(Rd3.mesaj, /attestation or signing failed: classical public key does not match the secret key/);
assert.notStrictEqual(Rd4.cod, 0); assert.match(Rd4.mesaj, /aere-api-key contains a line break or a control character; nothing was sent/); assert.strictEqual(Rd4.cereri.length, 0);
for (const r of [Rd1, Rd2, Rd3, Rd4]) assert.strictEqual(r.iesiriBrut, '', r.eticheta);
});
await test('CONTROL NEGATIV (d): cheia de semnare si cheia API plantate NU apar in stdout (in afara comenzilor ::add-mask::), stderr, GITHUB_OUTPUT, GITHUB_STEP_SUMMARY, nici in fisierele de atestare, in NICIO rulare cu secrete (reusite sau cazute)', () => {
// controlul pozitiv al metodei: cautarea gaseste ce e sigur acolo
assert.ok(cautaSecrete('x ' + API_KEY + ' y').includes('aere-api-key intreaga'));
assert.ok(cautaSecrete(CHEIE_TEXT.toUpperCase()).includes('K din cheia ML-DSA'), 'the search is case-insensitive');
assert.strictEqual(pqBare.slice(0, 64), CHEI.pq.publicKey.slice(2, 66), 'rho: the start of the ML-DSA secret key IS public (why it is not searched)');
const cuSecrete = toateRularile.filter((r) => r.secrete);
assert.ok(cuSecrete.length >= 10, 'runs with planted secrets: ' + cuSecrete.length);
const gasite = [];
for (const r of cuSecrete) {
assert.ok(/^::add-mask::/m.test(r.stdout), r.eticheta + ': no ::add-mask::');
const locuri = [['stdout', faraMasti(r.stdout)], ['stderr', r.stderr], ['GITHUB_OUTPUT', r.iesiriBrut], ['GITHUB_STEP_SUMMARY', r.sumar], ...fisiereAtestare(r)];
for (const [loc, text] of locuri) { const g = cautaSecrete(text); if (g.length) gasite.push(`${r.eticheta} / ${loc}: ${g.join(', ')}`); }
}
assert.deepStrictEqual(gasite, []);
// mastile chiar poarta secretele: altfel runner-ul nu ar avea ce ascunde, si cautarea de mai sus ar fi fost pe un stdout gol
const masti = (r) => r.stdout.split(/\r?\n/).filter((l) => l.startsWith('::add-mask::')).join('\n');
for (const k of ['aere-api-key intreaga', 'aere-api-key partea secreta', 'cheia clasica', 'K din cheia ML-DSA', 'samanta ML-DSA']) assert.ok(cautaSecrete(masti(R2)).includes(k), 'mask missing: ' + k);
assert.ok(cautaSecrete(masti(Rd1)).includes('marcajul cheii stricate'), 'a key that does not parse is still masked');
assert.ok(cautaSecrete(Rb.cereri.length ? JSON.stringify(Rb.cereri[0]) : '').includes('aere-api-key intreaga'), 'control: the 403 server did receive (and echo) the planted key');
});
const Re = await ruleaza('(e) build necunoscut', { workspace: G, inputs: { 'source-path': 'pkg', build: 'docker' } });
const Rf = await ruleaza('(f) boolean gresit', { workspace: G, inputs: { 'source-path': 'pkg', 'verify-rebuild': 'yes' } });
const Rg = await ruleaza('(g) carlig pe runner', { workspace: G, inputs: { 'source-path': 'pkg' }, env: { GITHUB_ACTIONS: 'true', AERE_POS_TEST_HOOK: 'alter-after-attest' } });
const Rh = await ruleaza('(h) radacina depozitului', { workspace: G, inputs: { 'source-path': '.' } });
const Ri = await ruleaza('(i) out-dir in source-path', { workspace: G, inputs: { 'source-path': 'pkg', 'out-dir': 'pkg/out' } });
const Rj = await ruleaza('(j) source-path lipsa', { workspace: G, inputs: {} });
await test('refuzurile intrarilor, fiecare cu motivul lui: build necunoscut, boolean gresit, carlig de proba pe un runner, radacina depozitului, out-dir in source-path, source-path lipsa', () => {
const cere = (r, re) => { assert.notStrictEqual(r.cod, 0, r.eticheta); assert.match(r.mesaj, re, r.eticheta); assert.strictEqual(r.iesiriBrut, '', r.eticheta); };
cere(Re, /build "docker" is not supported/);
cere(Rf, /input verify-rebuild must be true or false/);
cere(Rg, /AERE_POS_TEST_HOOK is a switch for the local tests and is refused on a GitHub runner/);
cere(Rh, /source-path is the repository root: this version attests a package in a SUBDIRECTORY/);
cere(Ri, /out-dir is inside source-path/);
cere(Rj, /source-path is required/);
assert.ok(!fs.existsSync(path.join(G, 'pkg', 'out')), 'the refused run left nothing inside source-path');
});
// ---------------------------------------------------------------- setul minim de fisiere (ce descarca un runner) si dependintele
function aspectMinim() {
const L = tmp('aere-pos-act-aspect-');
for (const f of ['tools/proof-of-software/pos.mjs', 'tools/proof-of-software/action/index.mjs', 'tools/proof-of-software/action/action.yml', 'sdk/index.mjs', 'sdk/package.json', 'sdk-pq-sign/index.mjs', 'sdk-pq-sign/package.json', 'sdk-pq-sign/package-lock.json']) {
fs.mkdirSync(path.dirname(path.join(L, f)), { recursive: true }); fs.copyFileSync(path.join(RADACINA, f), path.join(L, f));
}
return L;
}
const L1 = aspectMinim();
const Rk = await ruleaza('dependinte lipsa, npm ci offline', { workspace: G, inputs: { 'source-path': 'pkg' }, actiune: path.join(L1, 'tools', 'proof-of-software', 'action', 'index.mjs'), env: { AERE_POS_TEST_HOOK: 'npm-offline' } });
await test('dependinte LIPSA (checkout-ul actiunii fara node_modules, ca pe un runner): actiunea incearca npm ci din package-lock.json; niciodata un ERR_MODULE_NOT_FOUND brut', () => {
assert.doesNotMatch(Rk.mesaj, /ERR_MODULE_NOT_FOUND|Cannot find package/);
assert.match(Rk.stdout, /installing the pinned dependencies of sdk-pq-sign from its package-lock\.json/);
if (Rk.cod === 0) { assert.match(Rk.stdout, /dependencies installed/); assert.strictEqual(Rk.iesiri['artifact-sha256'], SHA_REF); return; }
assert.match(Rk.mesaj, /could not install the pinned dependencies of sdk-pq-sign \(npm ci from its package-lock\.json\)/);
assert.strictEqual(Rk.iesiriBrut, '');
if (!/ENOTCACHED|only-if-cached/.test(Rk.mesaj)) throw new Error('npm ci failed for another reason than an empty offline cache: ' + Rk.stderr.slice(0, 300));
nemasurate.push('instalarea reala a dependintelor pe un runner (npm ci din registru): in proba --offline, cache-ul local nu are pachetele, refuzul iese curat');
});
const L2 = aspectMinim();
fs.cpSync(path.join(RADACINA, 'sdk-pq-sign', 'node_modules'), path.join(L2, 'sdk-pq-sign', 'node_modules'), { recursive: true });
const Rl = await ruleaza('set minim + dependinte', { workspace: G, inputs: { 'source-path': 'pkg', 'signing-key': CHEIE_TEXT }, actiune: path.join(L2, 'tools', 'proof-of-software', 'action', 'index.mjs'), secrete: true });
await test('POZITIV: numai cele 8 fisiere (pos.mjs, action/, sdk, sdk-pq-sign) + dependintele fixate ajung: atestare semnata + reconstructie, acelasi artefact', () => {
assert.strictEqual(Rl.cod, 0, Rl.mesaj);
assert.match(Rl.stdout, /dependencies present/);
assert.strictEqual(Rl.iesiri['artifact-sha256'], SHA_REF);
assert.match(Rl.stdout, /OK {3}rebuild: npm pack of the attested tree reproduces/);
});
server.close();
if (process.env.AERE_POS_PASTREAZA !== '1') {
let ramase = 0;
for (const d of facute) { try { fs.rmSync(d, { recursive: true, force: true, maxRetries: 3 }); } catch { ramase++; } }
console.log(`curatenie: ${facute.length - ramase} dosare temporare sterse, ${ramase} ramase`);
}
console.log(`\n${treceri} treceri, ${esecuri.length} esecuri`);
if (nemasurate.length) console.log(`NEMASURAT (${nemasurate.length}): ${nemasurate.join(' | ')}`);
if (esecuri.length) process.exitCode = 1;