aere-quantum/crypto-inventory/lib/rezumat.mjs

77 lines
5.1 KiB
JavaScript

// Rezumatul text (engleza, pentru utilizator) si regula --fail-on.
import { CLS } from './catalog.mjs';
const ORDINE = [CLS.V, CLS.W, CLS.S, CLS.U];
export function numarPeClase(gasiri) {
const r = Object.fromEntries(ORDINE.map((c) => [c, 0]));
for (const g of gasiri) r[g.classification] = (r[g.classification] || 0) + 1;
return r;
}
// --fail-on: lista separata prin virgula din vulnerable, weak, unknown, any
// "vulnerable" = orice gasire pe care un calculator cuantic o sparge (inclusiv cele weak-now care sunt si Shor)
export function verificaFailOn(gasiri, spec) {
if (!spec) return { esec: false, motive: [] };
const cer = new Set(String(spec).split(',').map((x) => x.trim()).filter(Boolean));
const necunoscute = [...cer].filter((x) => !['vulnerable', 'weak', 'unknown', 'any'].includes(x));
if (necunoscute.length) throw new Error(`--fail-on: unknown value(s): ${necunoscute.join(', ')} (use vulnerable, weak, unknown or any)`);
const motive = [];
const vuln = gasiri.filter((g) => g.classification === CLS.V || (g.classification === CLS.W && g.quantumVulnerable));
const slabe = gasiri.filter((g) => g.classification === CLS.W);
const nec = gasiri.filter((g) => g.classification === CLS.U);
if ((cer.has('vulnerable') || cer.has('any')) && vuln.length) motive.push(`${vuln.length} quantum-vulnerable finding(s)`);
if ((cer.has('weak') || cer.has('any')) && slabe.length) motive.push(`${slabe.length} weak-now finding(s)`);
if (cer.has('unknown') && nec.length) motive.push(`${nec.length} unknown finding(s)`);
return { esec: motive.length > 0, motive };
}
export function rezumatText(rez) {
const s = rez.stats;
const g = rez.findings;
const L = [];
L.push(`Aere crypto inventory ${rez.tool.version}: ${rez.rootName}`);
const limbaje = Object.entries(s.codeFiles).sort().map(([k, v]) => `${k} ${v}`).join(', ') || 'none';
L.push(`Files: ${s.filesSeen} seen | ${s.codeFilesTotal} analyzed as code (${limbaje}) | ${s.textFilesPemOnly} other text files checked for PEM key/certificate blocks only`);
const nr = s.notRead;
L.push(`Not read: ${nr.binary} binary, ${nr.tooLarge} too large (> ${rez.options.maxFileBytes} bytes), ${nr.fileLimit} over the file limit (${rez.options.maxFiles}), ${nr.unreadable} unreadable; ${s.symlinksNotFollowed} symlinks not followed`);
for (const k of ['binary', 'tooLarge', 'fileLimit', 'unreadable']) {
if (s.notReadExamples[k].length) L.push(` e.g. ${k}: ${s.notReadExamples[k].join(', ')}${nr[k] > s.notReadExamples[k].length ? ', ...' : ''}`);
}
if (s.resolutionLimitFiles) L.push(`Variable resolution limit (${rez.options.maxResolvedVariablesPerFile} distinct variables per file) reached in ${s.resolutionLimitFiles} file(s); further variables there are reported as unknown: ${s.resolutionLimitExamples.join(', ')}${s.resolutionLimitFiles > s.resolutionLimitExamples.length ? ', ...' : ''}`);
const ex = Object.entries(s.dirsExcluded).sort().map(([n, c]) => `${n} (${c})`).join(', ');
L.push(`Directories not descended: ${ex || 'none'}${s.dirsUnreadable ? `; ${s.dirsUnreadable} unreadable` : ''}`);
const pk = {};
for (const x of g) pk[x.evidenceKind] = (pk[x.evidenceKind] || 0) + 1;
L.push(`Findings: ${g.length} (${Object.entries(pk).sort().map(([k, v]) => `${k} ${v}`).join(', ') || 'none'})`);
const cl = numarPeClase(g);
const wq = g.filter((x) => x.classification === CLS.W && x.quantumVulnerable).length;
for (const c of ORDINE) L.push(` ${c.padEnd(19)} ${String(cl[c]).padStart(5)}${c === CLS.W && wq ? ` (${wq} also quantum-vulnerable)` : ''}`);
const pl = {};
for (const x of g) pl[x.language] = (pl[x.language] || 0) + 1;
L.push(`By language: ${Object.entries(pl).sort().map(([k, v]) => `${k} ${v}`).join(', ') || 'none'}`);
const frecv = {};
for (const x of g) { const k = `${x.name} [${x.classification}]`; frecv[k] = (frecv[k] || 0) + 1; }
const top = Object.entries(frecv).sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).slice(0, 10);
if (top.length) {
L.push('Most frequent:');
top.forEach(([k, v], i) => L.push(` ${String(i + 1).padStart(2)}. ${k} x${v}`));
}
const vf = new Map();
for (const x of g) {
if (!(x.classification === CLS.V || (x.classification === CLS.W && x.quantumVulnerable))) continue;
if (!vf.has(x.file)) vf.set(x.file, []);
vf.get(x.file).push(x);
}
L.push(`Files with quantum-vulnerable findings: ${vf.size}`);
for (const [f, lista] of [...vf].sort()) {
L.push(` ${f} (${lista.length}: ${[...new Set(lista.map((x) => x.name))].join(', ')})`);
}
L.push(`Declared crypto libraries (manifests; declaration is not use): ${rez.libraries.length}`);
for (const b of rez.libraries) L.push(` ${b.file}:${b.line} ${b.name}${b.version ? ' ' + b.version : ''} (${b.provides})`);
if (s.manifestErrors.length) L.push(`Manifest errors: ${s.manifestErrors.join('; ')}`);
L.push('Scope: static pattern analysis of source text. Not covered: compiled binaries, transitive dependencies, cryptography chosen at run time.');
// numele de fisiere si textele vin din arborele scanat: caracterele de control (secvente ANSI) nu ajung in terminal
return L.map((l) => l.replace(/[\u0000-\u001f\u007f-\u009f]/g, '?')).join('\n');
}