UNPKG

@cyclonedx/cdxgen

Version:

Creates CycloneDX Software Bill of Materials (SBOM) from source or container image

1,607 lines (1,576 loc) 58.1 kB
#!/usr/bin/env node import { readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; import process from "node:process"; import repl from "node:repl"; import jsonata from "jsonata"; import { createBom } from "../lib/cli/index.js"; import { isSpdxJsonLd } from "../lib/helpers/bomUtils.js"; import { printCallStack, printDependencyTree, printFormulation, printOccurrences, printOSTable, printServices, printSummary, printTable, printVulnerabilities, } from "../lib/helpers/display.js"; import { formatHbomHardwareClassSummary, getHbomSummary, isHbomLikeBom, } from "../lib/helpers/hbomAnalysis.js"; import { getPropertyValue, getSourceDerivedCryptoComponents, getUnpackagedExecutableComponents, getUnpackagedSharedLibraryComponents, } from "../lib/helpers/inventoryStats.js"; import { importProtobomModule, isProtoBomPath, } from "../lib/helpers/protobomLoader.js"; import { getProvenanceComponents, getTrustedComponents, } from "../lib/helpers/provenanceUtils.js"; import { toCycloneDxLikeBom } from "../lib/helpers/spdxUtils.js"; import { table } from "../lib/helpers/table.js"; import { getTmpDir, isDryRun, safeExistsSync, safeMkdirSync, safeMkdtempSync, safeWriteSync, } from "../lib/helpers/utils.js"; import { getBomWithOras } from "../lib/managers/oci.js"; import { validateBom } from "../lib/validator/bomValidator.js"; const options = { useColors: true, breakEvalOnSigint: true, preview: true, prompt: "cdx ↝ ", ignoreUndefined: true, useGlobal: true, }; // Use canonical terminal settings to support custom readlines process.env.NODE_NO_READLINE = 1; const cdxArt = ` ██████╗██████╗ ██╗ ██╗ ██╔════╝██╔══██╗╚██╗██╔╝ ██║ ██║ ██║ ╚███╔╝ ██║ ██║ ██║ ██╔██╗ ╚██████╗██████╔╝██╔╝ ██╗ ╚═════╝╚═════╝ ╚═╝ ╚═╝ `; console.log(cdxArt); if (process.env.CDXGEN_NODE_OPTIONS) { process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ""} ${process.env.CDXGEN_NODE_OPTIONS}`; } // The current sbom is stored here let sbom; const getInteractiveBom = () => toCycloneDxLikeBom(sbom); function getContainerRegistryHost(reference) { const trimmedReference = `${reference || ""}`.trim().toLowerCase(); if (!trimmedReference) { return undefined; } const slashIndex = trimmedReference.indexOf("/"); if (slashIndex <= 0) { return undefined; } return trimmedReference.slice(0, slashIndex).replace(/:\d+$/, ""); } function isSupportedSbomRegistryReference(reference) { const registryHost = getContainerRegistryHost(reference); return registryHost === "ghcr.io" || registryHost === "docker.io"; } function unescapeAnnotationText(value) { return String(value || "") .replace(/<br>/g, "\n") .replace(/&lt;/g, "<") .replace(/&gt;/g, ">") .replace(/&amp;/g, "&") .replace(/\\([\\`*_{}\[\]()#+!|])/g, "$1") .trim(); } function parseAnnotationProperties(text) { const properties = {}; const lines = String(text || "").split(/\r?\n/); let foundHeader = false; for (const line of lines) { if (!line.startsWith("|")) { continue; } const cells = line .split("|") .slice(1, -1) .map((cell) => cell.trim()); if (cells.length < 2) { continue; } if (cells[0] === "Property" && cells[1] === "Value") { foundHeader = true; continue; } if ( foundHeader && /^-+$/.test(cells[0].replace(/\s/g, "")) && /^-+$/.test(cells[1].replace(/\s/g, "")) ) { continue; } if (!foundHeader) { continue; } properties[unescapeAnnotationText(cells[0])] = unescapeAnnotationText( cells[1], ); } return properties; } function getAuditAnnotations() { return (sbom?.annotations || []) .map((annotation) => { const properties = parseAnnotationProperties(annotation?.text); return { firstLine: unescapeAnnotationText( String(annotation?.text || "").split(/\r?\n/, 1)[0], ), properties, raw: annotation, }; }) .filter( (annotation) => Object.keys(annotation.properties).some((key) => key.startsWith("cdx:audit:"), ) || annotation.firstLine.includes("cdx:audit:"), ); } function printAuditTable(title, rows) { if (rows.length <= 1) { return; } console.log( table(rows, { header: { alignment: "center", content: title, }, }), ); } function printKeyValueTable(title, entries) { const rows = [["Field", "Value"]]; entries.forEach(([field, value]) => { if (value === undefined || value === null || value === "") { return; } rows.push([field, `${value}`]); }); printAuditTable(title, rows); } function isLikelyObom(bom) { return Boolean( bom?.components?.some((comp) => comp?.properties?.some((prop) => prop?.name === "cdx:osquery:category"), ), ); } function isLikelyHbom(bom) { return isHbomLikeBom(bom); } function isLikelyCargoBom(bom) { const formulation = Array.isArray(bom?.formulation) ? bom.formulation : bom?.formulation ? [bom.formulation] : []; return Boolean( bom?.components?.some((component) => component?.purl?.startsWith("pkg:cargo/"), ) || formulation.some((entry) => entry?.components?.some( (component) => getPropertyValue(component, "cdx:rust:buildTool") === "cargo", ), ), ); } function getCargoHotspotComponents(bom) { return (bom?.components || []).filter( (component) => component?.purl?.startsWith("pkg:cargo/") && (getPropertyValue(component, "cdx:cargo:yanked") === "true" || Boolean(getPropertyValue(component, "cdx:cargo:git")) || Boolean(getPropertyValue(component, "cdx:cargo:path")) || getPropertyValue(component, "cdx:cargo:dependencyKind") === "build" || getPropertyValue(component, "cdx:cargo:workspaceDependencyResolved") === "true" || Boolean(getPropertyValue(component, "cdx:cargo:target"))), ); } function getCargoWorkflowComponents(bom) { return (bom?.components || []).filter( (component) => getPropertyValue(component, "cdx:github:action:ecosystem") === "cargo" || getPropertyValue(component, "cdx:github:step:usesCargo") === "true", ); } function getCargoFormulationEntries(bom) { const formulation = Array.isArray(bom?.formulation) ? bom.formulation : bom?.formulation ? [bom.formulation] : []; const matchingEntries = []; for (const formulationEntry of formulation) { const cargoComponents = (formulationEntry?.components || []).filter( (component) => getPropertyValue(component, "cdx:rust:buildTool") === "cargo", ); if (cargoComponents.length) { matchingEntries.push({ ...formulationEntry, components: cargoComponents, }); } } return matchingEntries; } const HBOM_FIRMWARE_PROPERTIES = Object.freeze([ "cdx:hbom:protocol", "cdx:hbom:flags", "cdx:hbom:guids", "cdx:hbom:instanceIds", "cdx:hbom:createdEpoch", "cdx:hbom:firmwareDate", ]); const HBOM_BUS_SECURITY_PROPERTIES = Object.freeze([ "cdx:hbom:securityLevel", "cdx:hbom:iommuProtection", "cdx:hbom:policy", "cdx:hbom:authorized", "cdx:hbom:usbVersion", "cdx:hbom:usbClassName", "cdx:hbom:usbInterfaceClasses", "cdx:hbom:pciClass", "cdx:hbom:pciClassCode", "cdx:hbom:displayConnectorType", "cdx:hbom:contentProtection", "cdx:hbom:drmNode", ]); const HBOM_POWER_PROPERTIES = Object.freeze([ "cdx:hbom:designCapacityPercent", "cdx:hbom:energyNow", "cdx:hbom:energyFull", "cdx:hbom:energyFullDesign", "cdx:hbom:chargeNow", "cdx:hbom:chargeFull", "cdx:hbom:chargeFullDesign", "cdx:hbom:powerNow", "cdx:hbom:voltageNow", "cdx:hbom:currentNow", "cdx:hbom:warningLevel", ]); function getInteractiveHbomOrWarn(replContext) { const interactiveBom = getInteractiveBom(); if (!interactiveBom) { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); replContext.displayPrompt(); return undefined; } if (!isLikelyHbom(interactiveBom)) { console.log( "This BOM does not look like an HBOM. Import an HBOM generated with 'cdxgen -t hbom' to use this view.", ); replContext.displayPrompt(); return undefined; } return interactiveBom; } function filterHbomComponentsByProperties( bom, propertyNames, hardwareClasses = [], ) { return (bom?.components || []).filter((component) => { const hardwareClass = getPropertyValue(component, "cdx:hbom:hardwareClass"); if (hardwareClasses.includes(hardwareClass)) { return true; } return propertyNames.some((propertyName) => Boolean(getPropertyValue(component, propertyName)), ); }); } function getDiagnosticDisplayDetail(diagnostic) { return ( diagnostic.installHint || diagnostic.privilegeHint || diagnostic.message || diagnostic.code || "-" ); } let historyFile; const historyConfigDir = join(homedir(), ".config", ".cdxgen"); if (!process.env.CDXGEN_REPL_HISTORY && !safeExistsSync(historyConfigDir)) { try { safeMkdirSync(historyConfigDir, { recursive: true }); historyFile = join(historyConfigDir, ".repl_history"); } catch (_e) { // ignore } } else { historyFile = join(historyConfigDir, ".repl_history"); } export const importSbom = async (sbomOrPath) => { const importTarget = String(sbomOrPath || "").trim(); if (!importTarget) { console.log("⚠ An SBOM path or image reference is required."); return; } if (importTarget.endsWith(".json") && safeExistsSync(importTarget)) { try { sbom = JSON.parse(readFileSync(importTarget, "utf-8")); let bomType = "SBOM"; if (isSpdxJsonLd(sbom)) { bomType = "SPDX"; } if (sbom?.vulnerabilities && Array.isArray(sbom.vulnerabilities)) { bomType = "VDR"; } console.log(`✅ ${bomType} imported successfully from ${importTarget}`); printSummary(sbom); if (isLikelyHbom(sbom)) { console.log( "💭 HBOM detected. Try .hbomsummary, .hbomevidence, .hbomdiagnostics, or .hbomtips", ); } if (isLikelyObom(sbom)) { console.log( "💭 OBOM detected. Try .osinfocategories, .obomtips, .processes, or .services_snapshot", ); } if (isLikelyCargoBom(sbom)) { console.log( "💭 Cargo signals detected. Try .cargohotspots or .cargoworkflows.", ); } if (getAuditAnnotations().length) { console.log( "💭 Audit annotations detected. Try .auditfindings, .auditactions, or .dispatchedges.", ); } } catch (e) { console.log( `⚠ Unable to import the BOM from ${importTarget} due to ${e}`, ); } } else if (isProtoBomPath(importTarget) && safeExistsSync(importTarget)) { const { readBinary } = await importProtobomModule( "cdxi", "protobuf BOM input", ); sbom = readBinary(importTarget, true); printSummary(sbom); } else if (isSupportedSbomRegistryReference(importTarget)) { try { sbom = getBomWithOras(importTarget); if (sbom) { printSummary(sbom); } else { console.log( `cyclonedx sbom attachment was not found within ${importTarget}`, ); } } catch (e) { console.log( `⚠ Unable to import the BOM from ${importTarget} due to ${e}`, ); } } else { console.log(`⚠ ${importTarget} is invalid.`); } }; // Load any sbom passed from the command line if (process.argv.length > 2) { await importSbom(process.argv[process.argv.length - 1]); console.log("💭 Type .print to view the BOM as a table"); console.log("💭 Type .trusted to list components with trusted publishing."); console.log( "💭 Type .provenance to list components with registry provenance evidence.", ); if (isLikelyHbom(sbom)) { console.log( "💭 Type .hbomsummary to review the host profile, evidence coverage, hardware-class mix, and collector diagnostics.", ); } if (getAuditAnnotations().length) { console.log( "💭 Type .auditfindings to review cdx-audit and bom-audit annotations.", ); } } else if (safeExistsSync("bom.json")) { // If the current directory has a bom.json load it await importSbom("bom.json"); } else { console.log("💭 Use .create <path> to create an SBOM for the given path."); console.log("💭 Use .import <json> to import an existing BOM."); console.log( "💭 For OBOM investigations, try .obomtips after importing an OBOM.", ); console.log( "💭 For HBOM investigations, try .hbomtips after importing an HBOM.", ); console.log("💭 Type .exit or press ctrl+d to close."); } const cdxgenRepl = repl.start(options); if (historyFile) { cdxgenRepl.setupHistory( process.env.CDXGEN_REPL_HISTORY || historyFile, (err) => { if (err) { console.log( "⚠ REPL history would not be persisted for this session. Set the environment variable CDXGEN_REPL_HISTORY to specify a custom history file", ); } }, ); } cdxgenRepl.defineCommand("create", { help: "create an SBOM for the given path", async action(sbomOrPath) { this.clearBufferedCommand(); const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxgen-repl-")); const bomFile = join(tempDir, "bom.json"); const bomNSData = await createBom(sbomOrPath, { multiProject: true, installDeps: true, output: bomFile, }); if (bomNSData) { sbom = bomNSData.bomJson; console.log("✅ BOM imported successfully."); console.log("💭 Type .print to view the BOM as a table"); console.log( "💭 Type .trusted to list components with trusted publishing.", ); console.log( "💭 Type .provenance to list components with registry provenance evidence.", ); if (isLikelyHbom(sbom)) { console.log( "💭 Type .hbomsummary or .hbomdiagnostics for focused hardware inventory and collector-diagnostic summaries.", ); } if (getAuditAnnotations().length) { console.log( "💭 Type .auditfindings to review cdx-audit and bom-audit annotations.", ); } if (isLikelyCargoBom(sbom)) { console.log( "💭 Type .cargohotspots or .cargoworkflows for Cargo-specific pivots.", ); } } else { console.log("BOM was not generated successfully"); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("import", { help: "import an existing BOM", async action(sbomOrPath) { this.clearBufferedCommand(); await importSbom(sbomOrPath); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("summary", { help: "summarize an existing BOM", action() { if (sbom) { printSummary(sbom); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbomsummary", { help: "summarize HBOM host metadata, evidence coverage, and hardware-class mix", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom) { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); this.displayPrompt(); return; } if (!isLikelyHbom(interactiveBom)) { console.log( "This BOM does not look like an HBOM. Import an HBOM generated with 'cdxgen -t hbom' to use this view.", ); this.displayPrompt(); return; } const hbomSummary = getHbomSummary(interactiveBom); printKeyValueTable("HBOM summary", [ ["Host", hbomSummary.metadataName], ["Component type", hbomSummary.metadataType], ["Manufacturer", hbomSummary.manufacturer], ["Platform", hbomSummary.platform], ["Architecture", hbomSummary.architecture], ["Collector profile", hbomSummary.collectorProfile], ["Identifier policy", hbomSummary.identifierPolicy], ["Component count", hbomSummary.componentCount], ["Hardware class count", hbomSummary.hardwareClassCount], [ "Top hardware classes", formatHbomHardwareClassSummary(hbomSummary.hardwareClassCounts), ], ["Command evidence count", hbomSummary.evidenceCommandCount], ["Observed file count", hbomSummary.evidenceFileCount], ]); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbomclasses", { help: "show HBOM component counts by hardware class", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom) { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); this.displayPrompt(); return; } if (!isLikelyHbom(interactiveBom)) { console.log( "This BOM does not look like an HBOM. Import an HBOM generated with 'cdxgen -t hbom' to use this view.", ); this.displayPrompt(); return; } const hbomSummary = getHbomSummary(interactiveBom); if (!hbomSummary.hardwareClassCounts.length) { console.log( "No HBOM hardware classes were found on the loaded BOM. Check whether the document includes cdx:hbom:hardwareClass properties.", ); this.displayPrompt(); return; } printAuditTable("HBOM hardware classes", [ ["Hardware class", "Count"], ...hbomSummary.hardwareClassCounts.map(({ hardwareClass, count }) => [ hardwareClass, `${count}`, ]), ]); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbomevidence", { help: "show HBOM collector profile plus command and observed-file evidence", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom) { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); this.displayPrompt(); return; } if (!isLikelyHbom(interactiveBom)) { console.log( "This BOM does not look like an HBOM. Import an HBOM generated with 'cdxgen -t hbom' to use this view.", ); this.displayPrompt(); return; } const hbomSummary = getHbomSummary(interactiveBom); printKeyValueTable("HBOM evidence overview", [ ["Collector profile", hbomSummary.collectorProfile], ["Command evidence count", hbomSummary.evidenceCommandCount], ["Observed file count", hbomSummary.evidenceFileCount], ]); if (hbomSummary.evidenceCommands.length) { printAuditTable("HBOM command evidence", [ ["Command #", "Evidence"], ...hbomSummary.evidenceCommands.map((command, index) => [ `${index + 1}`, command, ]), ]); } if (hbomSummary.evidenceFiles.length) { printAuditTable("HBOM observed files", [ ["File #", "Path"], ...hbomSummary.evidenceFiles.map((filePath, index) => [ `${index + 1}`, filePath, ]), ]); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbomdiagnostics", { help: "show parsed HBOM command diagnostics, issue counts, and install or privilege guidance", action() { const interactiveBom = getInteractiveHbomOrWarn(this); if (!interactiveBom) { return; } const hbomSummary = getHbomSummary(interactiveBom); if (!hbomSummary.commandDiagnosticCount) { console.log( "No HBOM command diagnostics were found. This usually means the collector completed without recording missing-command or permission-denied enrichments.", ); this.displayPrompt(); return; } printKeyValueTable("HBOM diagnostic overview", [ ["Diagnostic count", hbomSummary.commandDiagnosticCount], ["Actionable diagnostics", hbomSummary.actionableDiagnosticCount], ["Missing commands", hbomSummary.missingCommandCount], ["Permission denied", hbomSummary.permissionDeniedCount], ["Partial support", hbomSummary.partialSupportCount], ["Timeouts", hbomSummary.timeoutCount], ["Other command errors", hbomSummary.commandErrorCount], ["Diagnostic issues", hbomSummary.diagnosticIssues.join(", ")], ["Missing command IDs", hbomSummary.missingCommandIds.join(", ")], ["Permission-denied IDs", hbomSummary.permissionDeniedIds.join(", ")], ["Install hint count", hbomSummary.installHintCount], ["Privilege hint count", hbomSummary.privilegeHintCount], [ "Requires privileged rerun", hbomSummary.requiresPrivilegedEnrichment ? "yes" : "no", ], ]); printAuditTable("HBOM command diagnostics", [ ["Issue", "Diagnostic ID", "Command", "Hint / Message"], ...hbomSummary.commandDiagnostics.map((diagnostic) => [ diagnostic.issue || "unknown", diagnostic.id || "-", diagnostic.command || "-", getDiagnosticDisplayDetail(diagnostic), ]), ]); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbomfirmware", { help: "show firmware, board, TPM, and update-managed HBOM components plus host firmware provenance", action() { const interactiveBom = getInteractiveHbomOrWarn(this); if (!interactiveBom) { return; } const metadataComponent = interactiveBom.metadata?.component; const firmwareComponents = filterHbomComponentsByProperties( interactiveBom, HBOM_FIRMWARE_PROPERTIES, ["firmware", "board", "tpm"], ); const metadataEntries = [ [ "Board vendor", getPropertyValue(metadataComponent, "cdx:hbom:boardVendor"), ], ["Board name", getPropertyValue(metadataComponent, "cdx:hbom:boardName")], [ "BIOS vendor", getPropertyValue(metadataComponent, "cdx:hbom:biosVendor"), ], [ "BIOS version", getPropertyValue(metadataComponent, "cdx:hbom:biosVersion"), ], [ "Firmware date", getPropertyValue(metadataComponent, "cdx:hbom:firmwareDate"), ], [ "Device-tree revision", getPropertyValue(metadataComponent, "cdx:hbom:deviceTreeRevision"), ], ]; const hasMetadataProvenance = metadataEntries.some(([, value]) => Boolean(value), ); if (!firmwareComponents.length && !hasMetadataProvenance) { console.log( "No focused firmware or board provenance pivots were found. Import an HBOM from a host that exposes board, TPM, or firmware-management metadata to use this view.", ); this.displayPrompt(); return; } if (hasMetadataProvenance) { printKeyValueTable("HBOM host firmware provenance", metadataEntries); } if (firmwareComponents.length) { printTable( { components: firmwareComponents, dependencies: [] }, undefined, undefined, `Found ${firmwareComponents.length} firmware, board, TPM, or update-managed component(s).`, ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbombuses", { help: "show bus, connector, USB, PCI, and external-expansion HBOM components with security or topology metadata", action() { const interactiveBom = getInteractiveHbomOrWarn(this); if (!interactiveBom) { return; } const busComponents = filterHbomComponentsByProperties( interactiveBom, HBOM_BUS_SECURITY_PROPERTIES, [ "bus", "usb-device", "pci-device", "display-adapter", "display-connector", ], ); if (!busComponents.length) { console.log( "No bus or connector components with focused security or topology metadata were found.", ); this.displayPrompt(); return; } printTable( { components: busComponents, dependencies: [] }, undefined, undefined, `Found ${busComponents.length} bus, USB, PCI, or display-link component(s) with bus-security or topology pivots.`, ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbompower", { help: "show HBOM power and battery components with detailed design-capacity and runtime telemetry", action() { const interactiveBom = getInteractiveHbomOrWarn(this); if (!interactiveBom) { return; } const powerComponents = filterHbomComponentsByProperties( interactiveBom, HBOM_POWER_PROPERTIES, ["power"], ); if (!powerComponents.length) { console.log( "No focused power or battery telemetry components were found on the loaded HBOM.", ); this.displayPrompt(); return; } printTable( { components: powerComponents, dependencies: [] }, undefined, undefined, `Found ${powerComponents.length} power or battery component(s) with detailed runtime telemetry.`, ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("exit", { help: "exit", action() { this.close(); }, }); cdxgenRepl.defineCommand("sbom", { help: "show the current sbom", action() { if (sbom) { console.log(sbom); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("search", { help: "search the current bom. performs case insensitive search on various attributes.", async action(searchStr) { if (sbom) { if (searchStr) { try { let fixedSearchStr = searchStr.replaceAll("/", "\\/"); let dependenciesSearchStr = fixedSearchStr; if (!fixedSearchStr.includes("~>")) { dependenciesSearchStr = `dependencies[ref ~> /${fixedSearchStr}/i or dependsOn ~> /${fixedSearchStr}/i or provides ~> /${fixedSearchStr}/i]`; fixedSearchStr = `components[group ~> /${fixedSearchStr}/i or name ~> /${fixedSearchStr}/i or description ~> /${fixedSearchStr}/i or publisher ~> /${fixedSearchStr}/i or purl ~> /${fixedSearchStr}/i or tags ~> /${fixedSearchStr}/i]`; } const expression = jsonata(fixedSearchStr); const bomForSearch = searchStr.includes("~>") ? sbom : getInteractiveBom(); let components = await expression.evaluate(bomForSearch); const dexpression = jsonata(dependenciesSearchStr); let dependencies = await dexpression.evaluate(bomForSearch); if (components && !Array.isArray(components)) { components = [components]; } if (dependencies && !Array.isArray(dependencies)) { dependencies = [dependencies]; } if (!components) { console.log("No results found!"); } else { printTable({ components, dependencies }, undefined, searchStr); if (dependencies?.length) { printDependencyTree( { components, dependencies }, "dependsOn", searchStr, ); } } } catch (e) { console.log(e); } } else { console.log('⚠ Specify the search string. Eg: .search "search string"'); } } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("sort", { help: "sort the current bom based on the attribute", async action(sortStr) { if (sbom) { if (sortStr) { try { if (!sortStr.includes("^")) { sortStr = `components^(${sortStr})`; } const expression = jsonata(sortStr); const components = await expression.evaluate(sbom); if (!components) { console.log("No results found!"); } else { printTable({ components, dependencies: [] }); // Store the sorted list in memory if (components.length === sbom.components.length) { sbom.components = components; } } } catch (e) { console.log(e); } } else { console.log("⚠ Specify the attribute to sort by. Eg: .sort name"); } } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("query", { help: "query the current bom using jsonata expression", async action(querySpec) { if (sbom) { if (querySpec) { try { const expression = jsonata(querySpec); console.log(await expression.evaluate(sbom)); } catch (e) { console.log(e); } } else { console.log( "⚠ Specify the search specification in jsonata format. Eg: .query metadata.component", ); } } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("print", { help: "print the current bom as a table", action() { const interactiveBom = getInteractiveBom(); if (interactiveBom) { printTable(interactiveBom); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("trusted", { help: "print components with trusted publishing", action() { const interactiveBom = getInteractiveBom(); if (interactiveBom?.components) { const trustedComponents = getTrustedComponents(interactiveBom.components); if (!trustedComponents.length) { console.log( "No trusted-publishing components found. Look for components enriched with cdx:npm:trustedPublishing or cdx:pypi:trustedPublishing.", ); } else { printTable( { components: trustedComponents, dependencies: [] }, undefined, undefined, `Found ${trustedComponents.length} trusted component(s) backed by trusted publishing metadata.`, ); } } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("provenance", { help: "print components with direct registry provenance evidence", action() { const interactiveBom = getInteractiveBom(); if (interactiveBom?.components) { const provenanceComponents = getProvenanceComponents( interactiveBom.components, ); if (!provenanceComponents.length) { console.log( "No provenance-backed components found. Look for registry URLs, digests, signatures, or key IDs captured as component properties.", ); } else { printTable( { components: provenanceComponents, dependencies: [] }, undefined, undefined, `Found ${provenanceComponents.length} component(s) with direct registry provenance evidence.`, ); } } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("cryptos", { help: "print the components of type cryptographic-asset as a table", action() { if (sbom) { printTable(sbom, ["cryptographic-asset"]); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("sourcecryptos", { help: "show source-derived cryptographic assets detected from JS AST analysis", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom?.components) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const sourceCryptoComponents = getSourceDerivedCryptoComponents( interactiveBom.components, ); if (!sourceCryptoComponents.length) { console.log( "No source-derived crypto assets found. Generate a CBOM or SBOM with source crypto analysis to use this view.", ); this.displayPrompt(); return; } printTable( { components: sourceCryptoComponents, dependencies: [] }, ["cryptographic-asset"], undefined, `Found ${sourceCryptoComponents.length} source-derived cryptographic asset component(s).`, ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("unpackagedbins", { help: "show executable file components that were not matched to OS package ownership", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom?.components) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const unpackagedExecutables = getUnpackagedExecutableComponents( interactiveBom.components, ); if (!unpackagedExecutables.length) { console.log( "No unpackaged executable file components found. Import a container or rootfs BOM with native file inventory to use this view.", ); this.displayPrompt(); return; } printTable( { components: unpackagedExecutables, dependencies: [] }, ["file"], undefined, `Found ${unpackagedExecutables.length} executable file component(s) that were not traced to OS package ownership.`, ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("unpackagedlibs", { help: "show shared library file components that were not matched to OS package ownership", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom?.components) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const unpackagedSharedLibraries = getUnpackagedSharedLibraryComponents( interactiveBom.components, ); if (!unpackagedSharedLibraries.length) { console.log( "No unpackaged shared library file components found. Import a container or rootfs BOM with native file inventory to use this view.", ); this.displayPrompt(); return; } printTable( { components: unpackagedSharedLibraries, dependencies: [] }, ["file"], undefined, `Found ${unpackagedSharedLibraries.length} shared library file component(s) that were not traced to OS package ownership.`, ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("frameworks", { help: "print the components of type framework as a table", action() { if (sbom) { printTable(sbom, ["framework"]); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("tree", { help: "display the dependency tree", action() { const interactiveBom = getInteractiveBom(); if (interactiveBom) { printDependencyTree(interactiveBom); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("provides", { help: "display the provides tree", action() { const interactiveBom = getInteractiveBom(); if (interactiveBom) { printDependencyTree(interactiveBom, "provides"); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("validate", { help: "validate the bom using jsonschema", action() { if (sbom) { const result = validateBom(sbom); if (result) { console.log("BOM is valid!"); } } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("save", { help: "save the bom to a new file", action(saveToFile) { if (sbom) { if (!saveToFile) { saveToFile = "bom.json"; } if (isDryRun) { console.log( `⚠ Dry run mode blocks saving the BOM to ${saveToFile}. Disable --dry-run or CDXGEN_DRY_RUN to persist it.`, ); this.displayPrompt(); return; } safeWriteSync(saveToFile, JSON.stringify(sbom, null, 2)); console.log(`BOM saved successfully to ${saveToFile}`); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("update", { help: "update the bom components based on the given query", async action(updateSpec) { if (sbom) { if (!updateSpec) { return; } if (!updateSpec.startsWith("|")) { updateSpec = `|${updateSpec}`; } if (!updateSpec.endsWith("|")) { updateSpec = `${updateSpec}|`; } updateSpec = `$ ~> ${updateSpec}`; const expression = jsonata(updateSpec); const newSbom = await expression.evaluate(sbom); if (newSbom && newSbom.components.length <= sbom.components.length) { sbom = newSbom; } console.log("BOM updated successfully."); } else { console.log( "⚠ No BOM is loaded. Use .import command to import an existing BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("occurrences", { help: "view components with evidence.occurrences", async action() { if (sbom) { try { const expression = jsonata( "components[$count(evidence.occurrences) > 0]", ); let components = await expression.evaluate(sbom); if (!components) { console.log( "No results found. Use evinse command to generate an BOM with evidence.", ); } else { if (!Array.isArray(components)) { components = [components]; } printOccurrences({ components }); } } catch (e) { console.log(e); } } else { console.log( "⚠ No BOM is loaded. Use .import command to import an evinse BOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("callstack", { help: "view components with evidence.callstack", async action() { if (sbom) { try { const expression = jsonata( "components[$count(evidence.callstack.frames) > 0]", ); let components = await expression.evaluate(sbom); if (!components) { console.log( "callstack evidence was not found. Use evinse command to generate an SBOM with evidence.", ); } else { if (!Array.isArray(components)) { components = [components]; } printCallStack({ components }); } } catch (e) { console.log(e); } } else { console.log( "⚠ No SBOM is loaded. Use .import command to import an evinse SBOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("services", { help: "view services", async action() { if (sbom) { try { const expression = jsonata("services"); let services = await expression.evaluate(sbom); if (!services) { console.log( "No services found. Use evinse command to generate a SaaSBOM with evidence.", ); } else { if (!Array.isArray(services)) { services = [services]; } printServices({ services }); } } catch (e) { console.log(e); } } else { console.log( "⚠ No SaaSBOM is loaded. Use .import command to import a SaaSBOM", ); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("vulnerabilities", { help: "view vulnerabilities", async action() { if (sbom) { try { const expression = jsonata("vulnerabilities"); let vulnerabilities = await expression.evaluate(sbom); if (!vulnerabilities) { console.log( "No vulnerabilities found. Use depscan to generate a VDR file with vulnerabilities.", ); } else { if (!Array.isArray(vulnerabilities)) { vulnerabilities = [vulnerabilities]; } printVulnerabilities(vulnerabilities); } } catch (e) { console.log(e); } } else { console.log("⚠ No BOM is loaded. Use .import command to import a VDR"); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("formulation", { help: "view formulation", async action() { if (sbom) { try { const expression = jsonata("formulation"); let formulation = await expression.evaluate(sbom); if (!formulation) { console.log( "No formulation found. Pass the argument --include-formulation to generate SBOM with formulation details.", ); } else { if (!Array.isArray(formulation)) { formulation = [formulation]; } printFormulation({ formulation }); } } catch (e) { console.log(e); } } else { console.log("⚠ No SBOM is loaded. Use .import command to import an SBOM"); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("cargohotspots", { help: "show Cargo package components with high-signal source, workspace, or build metadata", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom?.components) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const cargoComponents = getCargoHotspotComponents(interactiveBom); if (!cargoComponents.length) { console.log( "No Cargo hotspot components found. Look for Cargo BOMs enriched with manifest, registry, or workspace metadata.", ); this.displayPrompt(); return; } printTable( { components: cargoComponents, dependencies: [] }, undefined, undefined, `Found ${cargoComponents.length} Cargo component(s) with high-signal source, workspace, or build metadata.`, ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("cargoworkflows", { help: "show Cargo-native build formulation plus Cargo-related workflow actions and run steps", action() { const interactiveBom = getInteractiveBom(); if (!interactiveBom) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const cargoWorkflowComponents = getCargoWorkflowComponents(interactiveBom); const cargoFormulation = getCargoFormulationEntries(interactiveBom); if (!cargoWorkflowComponents.length && !cargoFormulation.length) { console.log( "No Cargo workflow or formulation pivots found. Import an SBOM generated with --include-formulation for Cargo projects.", ); this.displayPrompt(); return; } if (cargoWorkflowComponents.length) { printTable( { components: cargoWorkflowComponents, dependencies: [] }, undefined, undefined, `Found ${cargoWorkflowComponents.length} Cargo-related workflow component(s).`, ); } if (cargoFormulation.length) { printFormulation({ formulation: cargoFormulation }); } this.displayPrompt(); }, }); cdxgenRepl.defineCommand("auditfindings", { help: "summarize cdx-audit and bom-audit annotations from the loaded BOM", action() { if (!sbom) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const auditAnnotations = getAuditAnnotations(); if (!auditAnnotations.length) { console.log( "No audit annotations found. Generate an SBOM with --bom-audit or import a BOM enriched by cdx-audit.", ); this.displayPrompt(); return; } const rows = [ ["Engine", "Severity", "Rule", "Target / Edge", "Next action"], ]; auditAnnotations.forEach((annotation) => { const props = annotation.properties; rows.push([ props["cdx:audit:engine"] || "bom-audit", props["cdx:audit:severity"] || "unknown", props["cdx:audit:topFinding:ruleId"] || props["cdx:audit:ruleId"] || "-", props["cdx:audit:dispatch:edge"] || props["cdx:audit:target:purl"] || props["cdx:audit:location:file"] || annotation.firstLine, props["cdx:audit:nextAction"] || props["cdx:audit:upstreamGuidance"] || props["cdx:audit:mitigation"] || "-", ]); }); printAuditTable("Audit findings", rows); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("auditactions", { help: "list next actions from predictive audit annotations", action() { if (!sbom) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const auditAnnotations = getAuditAnnotations().filter( (annotation) => annotation.properties["cdx:audit:nextAction"], ); if (!auditAnnotations.length) { console.log( "No predictive next actions found. Import a BOM annotated by cdx-audit to review remediation guidance.", ); this.displayPrompt(); return; } const rows = [["Severity", "Target", "Next action", "Upstream guidance"]]; auditAnnotations.forEach((annotation) => { const props = annotation.properties; rows.push([ props["cdx:audit:severity"] || "unknown", props["cdx:audit:dispatch:edge"] || props["cdx:audit:target:purl"] || annotation.firstLine, props["cdx:audit:nextAction"] || "-", props["cdx:audit:upstreamGuidance"] || "-", ]); }); printAuditTable("Predictive audit actions", rows); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("dispatchedges", { help: "show local sender to receiver workflow edges captured by predictive audit", action() { if (!sbom) { console.log("⚠ No BOM is loaded. Use .import command to import an SBOM"); this.displayPrompt(); return; } const auditAnnotations = getAuditAnnotations().filter( (annotation) => annotation.properties["cdx:audit:dispatch:edge"], ); if (!auditAnnotations.length) { console.log( "No local dispatch edges found. Import a BOM annotated by cdx-audit with correlated workflow dispatch findings.", ); this.displayPrompt(); return; } const rows = [["Severity", "Rule", "Sender -> Receiver", "Receiver files"]]; auditAnnotations.forEach((annotation) => { const props = annotation.properties; rows.push([ props["cdx:audit:severity"] || "unknown", props["cdx:audit:topFinding:ruleId"] || "-", props["cdx:audit:dispatch:edge"] || annotation.firstLine, props["cdx:audit:dispatch:receiverFiles"] || "-", ]); }); printAuditTable("Predictive workflow dispatch edges", rows); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("osinfocategories", { help: "view the category names for the OS info from the obom", async action() { if (sbom) { try { const expression = jsonata( '$distinct(components.properties[name="cdx:osquery:category"].value)', ); const catgories = await expression.evaluate(sbom); if (!catgories) { console.log( "Unable to retrieve the os info categories. Only OBOMs generated by cdxgen are supported by this tool.", ); } else { console.log(catgories.join("\n")); } } catch (e) { console.log(e); } } else { console.log("⚠ No OBOM is loaded. Use .import command to import an OBOM"); } this.displayPrompt(); }, }); // OBOM-specific analyst helper tips for SOC/IR and compliance workflows. cdxgenRepl.defineCommand("obomtips", { help: "show analyst tips and useful commands for OBOM investigations", action() { console.log("OBOM analyst quick guide:"); console.log("1. .osinfocategories"); console.log( "2. Run an OS-query category command from .help (examples below)", ); console.log(" .processes"); console.log(" .services_snapshot"); console.log(" .scheduled_tasks"); console.log(" .startup_items"); console.log("3. .inspect <name>"); console.log("4. .services / .print / .summary for quick pivots"); console.log( "Tip: Generate with --bom-audit --bom-audit-categories obom-runtime for prioritized findings.", ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("hbomtips", { help: "show analyst tips and useful commands for HBOM investigations", action() { console.log("HBOM analyst quick guide:"); console.log("1. .hbomsummary"); console.log("2. .hbomclasses"); console.log("3. .hbomevidence"); console.log("4. .hbomdiagnostics"); console.log("5. .hbomfirmware / .hbombuses / .hbompower"); console.log( "6. .auditfindings to review hbom-security, hbom-performance, and hbom-compliance findings", ); console.log("7. .search <hardwareClass or device name>"); console.log( 'Tip: .query components[properties[name="cdx:hbom:hardwareClass" and value="storage"]] filters directly by hardware class.', ); this.displayPrompt(); }, }); cdxgenRepl.defineCommand("licenses", { help: "visualize license distribution", async action() { if (!sbom?.components) { console.log("⚠ No SBOM lo