#!/usr/bin/env node // Inventarul criptografic al unui cod sursa (CBOM CycloneDX 1.6). Fara dependinte externe. // Folosire: node inventar.mjs scan [--out cbom.json] [--summary] [--fail-on vulnerable] // Codul de iesire: 0 = bine, 1 = conditia --fail-on indeplinita, 2 = eroare de folosire sau de scanare. import { writeFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { resolve } from 'node:path'; import { scaneaza, VERSIUNE, DOSARE_EXCLUSE_IMPLICIT } from './lib/scan.mjs'; import { construiesteCbom } from './lib/cbom.mjs'; import { rezumatText, verificaFailOn, numarPeClase } from './lib/rezumat.mjs'; import { evalueaza, CLS } from './lib/catalog.mjs'; export { scaneaza as scan, construiesteCbom as buildCbom, rezumatText as renderSummary, verificaFailOn as checkFailOn, numarPeClase as countByClass, evalueaza as classify, CLS as CLASSES, VERSIUNE as VERSION, DOSARE_EXCLUSE_IMPLICIT as DEFAULT_EXCLUDED_DIRS }; const AJUTOR = `aere-crypto-inventory ${VERSIUNE} Usage: node inventar.mjs scan [options] Options: --out write the CycloneDX 1.6 CBOM to (default: stdout, unless --summary is given) --summary print a text summary to stdout --fail-on exit with code 1 if findings match: vulnerable, weak, unknown, any (comma-separated) --max-file-bytes skip (and count) files larger than n bytes (default 1048576) --max-files read at most n files; the rest are counted, not read (default 50000) --exclude-dir do not descend into directories with this name (repeatable) --no-default-excludes also descend into ${DOSARE_EXCLUSE_IMPLICIT.join(', ')} --deterministic serial number derived from content, no timestamp (reproducible output) --findings-json write the raw findings (one object per occurrence) as JSON Exit codes: 0 ok, 1 --fail-on condition met, 2 usage or scan error.`; export function main(argv) { const a = argv.slice(); const cmd = a.shift(); if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') { process.stdout.write(AJUTOR + '\n'); return cmd ? 0 : 2; } if (cmd === '--version') { process.stdout.write(VERSIUNE + '\n'); return 0; } if (cmd !== 'scan') { process.stderr.write(`unknown command: ${cmd}\n${AJUTOR}\n`); return 2; } let dir = null; const o = { excludeDirs: [] }; let out = null; let summary = false; let failOn = null; let determinist = false; let findingsJson = null; const numar = (x, nume) => { if (!/^\d+$/.test(String(x))) throw new Error(`${nume} needs a non-negative integer`); return Number(x); }; try { while (a.length) { const x = a.shift(); if (x === '--out') out = a.shift(); else if (x === '--summary') summary = true; else if (x === '--fail-on') failOn = a.shift(); else if (x === '--max-file-bytes') o.maxFileBytes = numar(a.shift(), x); else if (x === '--max-files') o.maxFiles = numar(a.shift(), x); else if (x === '--exclude-dir') o.excludeDirs.push(a.shift()); else if (x === '--no-default-excludes') o.noDefaultExcludes = true; else if (x === '--deterministic') determinist = true; else if (x === '--findings-json') findingsJson = a.shift(); else if (x.startsWith('--')) throw new Error(`unknown option: ${x}`); else if (!dir) dir = x; else throw new Error(`unexpected argument: ${x}`); } if (!dir) throw new Error('missing '); if (out === undefined || findingsJson === undefined || failOn === undefined) throw new Error('option is missing its value'); verificaFailOn([], failOn); } catch (err) { process.stderr.write(`error: ${err.message}\n${AJUTOR}\n`); return 2; } let rez; try { rez = scaneaza(dir, o); } catch (err) { process.stderr.write(`error: ${err.message}\n`); return 2; } const bom = construiesteCbom(rez, { deterministic: determinist }); const json = JSON.stringify(bom, null, 2) + '\n'; if (out) { writeFileSync(out, json); process.stderr.write(`CBOM written: ${resolve(out)} (${bom.components.length} components)\n`); } if (findingsJson) writeFileSync(findingsJson, JSON.stringify(rez.findings, null, 2) + '\n'); if (summary) process.stdout.write(rezumatText(rez) + '\n'); if (!out && !summary) process.stdout.write(json); const f = verificaFailOn(rez.findings, failOn); if (f.esec) { process.stderr.write(`fail-on ${failOn}: ${f.motive.join('; ')}\n`); return 1; } return 0; } if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { process.exitCode = main(process.argv.slice(2)); }