99 lines
4.6 KiB
JavaScript
99 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// Inventarul criptografic al unui cod sursa (CBOM CycloneDX 1.6). Fara dependinte externe.
|
|
// Folosire: node inventar.mjs scan <dosar> [--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 <dir> [options]
|
|
|
|
Options:
|
|
--out <file> write the CycloneDX 1.6 CBOM to <file> (default: stdout, unless --summary is given)
|
|
--summary print a text summary to stdout
|
|
--fail-on <list> exit with code 1 if findings match: vulnerable, weak, unknown, any (comma-separated)
|
|
--max-file-bytes <n> skip (and count) files larger than n bytes (default 1048576)
|
|
--max-files <n> read at most n files; the rest are counted, not read (default 50000)
|
|
--exclude-dir <name> 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 <file> 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 <dir>');
|
|
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));
|
|
}
|