// 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< { 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;