// 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'); }