@cyclonedx/cdxgen 12.2.1 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +239 -90
  2. package/bin/audit.js +191 -0
  3. package/bin/cdxgen.js +513 -167
  4. package/bin/convert.js +99 -0
  5. package/bin/evinse.js +23 -0
  6. package/bin/repl.js +339 -8
  7. package/bin/sign.js +8 -0
  8. package/bin/validate.js +8 -0
  9. package/bin/verify.js +8 -0
  10. package/data/container-knowledge-index.json +125 -0
  11. package/data/gtfobins-index.json +6296 -0
  12. package/data/lolbas-index.json +150 -0
  13. package/data/queries-darwin.json +63 -3
  14. package/data/queries-win.json +45 -3
  15. package/data/queries.json +74 -2
  16. package/data/rules/chrome-extensions.yaml +240 -0
  17. package/data/rules/ci-permissions.yaml +478 -18
  18. package/data/rules/container-risk.yaml +270 -0
  19. package/data/rules/obom-runtime.yaml +891 -0
  20. package/data/rules/package-integrity.yaml +49 -0
  21. package/data/spdx-export.schema.json +6794 -0
  22. package/data/spdx-model-v3.0.1.jsonld +15999 -0
  23. package/lib/audit/index.js +1924 -0
  24. package/lib/audit/index.poku.js +1488 -0
  25. package/lib/audit/progress.js +137 -0
  26. package/lib/audit/progress.poku.js +188 -0
  27. package/lib/audit/reporters.js +618 -0
  28. package/lib/audit/scoring.js +310 -0
  29. package/lib/audit/scoring.poku.js +341 -0
  30. package/lib/audit/targets.js +260 -0
  31. package/lib/audit/targets.poku.js +331 -0
  32. package/lib/cli/index.js +154 -11
  33. package/lib/cli/index.poku.js +251 -0
  34. package/lib/helpers/analyzer.js +446 -2
  35. package/lib/helpers/analyzer.poku.js +72 -1
  36. package/lib/helpers/annotationFormatter.js +49 -0
  37. package/lib/helpers/annotationFormatter.poku.js +44 -0
  38. package/lib/helpers/bomUtils.js +36 -0
  39. package/lib/helpers/bomUtils.poku.js +51 -0
  40. package/lib/helpers/caxa.js +2 -2
  41. package/lib/helpers/chromextutils.js +1153 -0
  42. package/lib/helpers/chromextutils.poku.js +493 -0
  43. package/lib/helpers/ciParsers/githubActions.js +1632 -45
  44. package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
  45. package/lib/helpers/containerRisk.js +186 -0
  46. package/lib/helpers/containerRisk.poku.js +52 -0
  47. package/lib/helpers/display.js +241 -59
  48. package/lib/helpers/display.poku.js +162 -2
  49. package/lib/helpers/exportUtils.js +123 -0
  50. package/lib/helpers/exportUtils.poku.js +60 -0
  51. package/lib/helpers/formulationParsers.js +69 -0
  52. package/lib/helpers/formulationParsers.poku.js +44 -0
  53. package/lib/helpers/gtfobins.js +189 -0
  54. package/lib/helpers/gtfobins.poku.js +49 -0
  55. package/lib/helpers/lolbas.js +267 -0
  56. package/lib/helpers/lolbas.poku.js +39 -0
  57. package/lib/helpers/osqueryTransform.js +84 -0
  58. package/lib/helpers/osqueryTransform.poku.js +49 -0
  59. package/lib/helpers/provenanceUtils.js +193 -0
  60. package/lib/helpers/provenanceUtils.poku.js +145 -0
  61. package/lib/helpers/pylockutils.js +281 -0
  62. package/lib/helpers/pylockutils.poku.js +48 -0
  63. package/lib/helpers/registryProvenance.js +793 -0
  64. package/lib/helpers/registryProvenance.poku.js +452 -0
  65. package/lib/helpers/source.js +1267 -0
  66. package/lib/helpers/source.poku.js +771 -0
  67. package/lib/helpers/spdxUtils.js +97 -0
  68. package/lib/helpers/spdxUtils.poku.js +70 -0
  69. package/lib/helpers/unicodeScan.js +147 -0
  70. package/lib/helpers/unicodeScan.poku.js +45 -0
  71. package/lib/helpers/utils.js +700 -128
  72. package/lib/helpers/utils.poku.js +877 -80
  73. package/lib/managers/binary.js +29 -5
  74. package/lib/managers/docker.js +179 -52
  75. package/lib/managers/docker.poku.js +327 -28
  76. package/lib/managers/oci.js +107 -23
  77. package/lib/managers/oci.poku.js +132 -0
  78. package/lib/server/openapi.yaml +17 -0
  79. package/lib/server/server.js +225 -336
  80. package/lib/server/server.poku.js +16 -10
  81. package/lib/stages/postgen/annotator.js +7 -0
  82. package/lib/stages/postgen/annotator.poku.js +40 -0
  83. package/lib/stages/postgen/auditBom.js +19 -3
  84. package/lib/stages/postgen/auditBom.poku.js +1729 -67
  85. package/lib/stages/postgen/postgen.js +40 -0
  86. package/lib/stages/postgen/postgen.poku.js +47 -0
  87. package/lib/stages/postgen/ruleEngine.js +80 -2
  88. package/lib/stages/postgen/spdxConverter.js +796 -0
  89. package/lib/stages/postgen/spdxConverter.poku.js +341 -0
  90. package/lib/validator/bomValidator.js +232 -0
  91. package/lib/validator/bomValidator.poku.js +70 -0
  92. package/lib/validator/complianceRules.js +70 -7
  93. package/lib/validator/complianceRules.poku.js +30 -0
  94. package/lib/validator/reporters/annotations.js +2 -2
  95. package/lib/validator/reporters/console.js +11 -0
  96. package/lib/validator/reporters.poku.js +13 -0
  97. package/package.json +10 -7
  98. package/types/bin/audit.d.ts +3 -0
  99. package/types/bin/audit.d.ts.map +1 -0
  100. package/types/bin/convert.d.ts +3 -0
  101. package/types/bin/convert.d.ts.map +1 -0
  102. package/types/bin/repl.d.ts.map +1 -1
  103. package/types/lib/audit/index.d.ts +115 -0
  104. package/types/lib/audit/index.d.ts.map +1 -0
  105. package/types/lib/audit/progress.d.ts +27 -0
  106. package/types/lib/audit/progress.d.ts.map +1 -0
  107. package/types/lib/audit/reporters.d.ts +35 -0
  108. package/types/lib/audit/reporters.d.ts.map +1 -0
  109. package/types/lib/audit/scoring.d.ts +35 -0
  110. package/types/lib/audit/scoring.d.ts.map +1 -0
  111. package/types/lib/audit/targets.d.ts +63 -0
  112. package/types/lib/audit/targets.d.ts.map +1 -0
  113. package/types/lib/cli/index.d.ts +8 -0
  114. package/types/lib/cli/index.d.ts.map +1 -1
  115. package/types/lib/helpers/analyzer.d.ts +13 -0
  116. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  117. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  118. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  119. package/types/lib/helpers/bomUtils.d.ts +5 -0
  120. package/types/lib/helpers/bomUtils.d.ts.map +1 -0
  121. package/types/lib/helpers/chromextutils.d.ts +97 -0
  122. package/types/lib/helpers/chromextutils.d.ts.map +1 -0
  123. package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
  124. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  125. package/types/lib/helpers/containerRisk.d.ts +17 -0
  126. package/types/lib/helpers/containerRisk.d.ts.map +1 -0
  127. package/types/lib/helpers/display.d.ts +4 -1
  128. package/types/lib/helpers/display.d.ts.map +1 -1
  129. package/types/lib/helpers/exportUtils.d.ts +40 -0
  130. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  131. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  132. package/types/lib/helpers/gtfobins.d.ts +17 -0
  133. package/types/lib/helpers/gtfobins.d.ts.map +1 -0
  134. package/types/lib/helpers/lolbas.d.ts +16 -0
  135. package/types/lib/helpers/lolbas.d.ts.map +1 -0
  136. package/types/lib/helpers/osqueryTransform.d.ts +7 -0
  137. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
  138. package/types/lib/helpers/provenanceUtils.d.ts +90 -0
  139. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
  140. package/types/lib/helpers/pylockutils.d.ts +51 -0
  141. package/types/lib/helpers/pylockutils.d.ts.map +1 -0
  142. package/types/lib/helpers/registryProvenance.d.ts +17 -0
  143. package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
  144. package/types/lib/helpers/source.d.ts +141 -0
  145. package/types/lib/helpers/source.d.ts.map +1 -0
  146. package/types/lib/helpers/spdxUtils.d.ts +2 -0
  147. package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
  148. package/types/lib/helpers/unicodeScan.d.ts +46 -0
  149. package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
  150. package/types/lib/helpers/utils.d.ts +29 -11
  151. package/types/lib/helpers/utils.d.ts.map +1 -1
  152. package/types/lib/managers/binary.d.ts.map +1 -1
  153. package/types/lib/managers/docker.d.ts.map +1 -1
  154. package/types/lib/managers/oci.d.ts.map +1 -1
  155. package/types/lib/server/server.d.ts +0 -36
  156. package/types/lib/server/server.d.ts.map +1 -1
  157. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  158. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  159. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  160. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  161. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  162. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  163. package/types/lib/validator/bomValidator.d.ts +1 -0
  164. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  165. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  166. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  167. package/types/bin/dependencies.d.ts +0 -3
  168. package/types/bin/dependencies.d.ts.map +0 -1
  169. package/types/bin/licenses.d.ts +0 -3
  170. package/types/bin/licenses.d.ts.map +0 -1
package/bin/convert.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import { dirname } from "node:path";
5
+ import process from "node:process";
6
+
7
+ import yargs from "yargs";
8
+ import { hideBin } from "yargs/helpers";
9
+
10
+ import {
11
+ getNonCycloneDxErrorMessage,
12
+ isCycloneDxBom,
13
+ } from "../lib/helpers/bomUtils.js";
14
+ import { deriveSpdxOutputPath } from "../lib/helpers/exportUtils.js";
15
+ import {
16
+ retrieveCdxgenVersion,
17
+ safeExistsSync,
18
+ safeMkdirSync,
19
+ } from "../lib/helpers/utils.js";
20
+ import { convertCycloneDxToSpdx } from "../lib/stages/postgen/spdxConverter.js";
21
+ import { validateSpdx } from "../lib/validator/bomValidator.js";
22
+
23
+ const _yargs = yargs(hideBin(process.argv));
24
+
25
+ const args = _yargs
26
+ .option("input", {
27
+ alias: "i",
28
+ default: "bom.json",
29
+ description: "Input CycloneDX BOM JSON file.",
30
+ })
31
+ .option("output", {
32
+ alias: "o",
33
+ description: "Output SPDX JSON file. Defaults to <input>.spdx.json.",
34
+ })
35
+ .option("validate", {
36
+ type: "boolean",
37
+ default: true,
38
+ description:
39
+ "Validate the generated SPDX export. Pass --no-validate to skip.",
40
+ })
41
+ .option("json-pretty", {
42
+ type: "boolean",
43
+ default: false,
44
+ description: "Pretty-print generated JSON output.",
45
+ })
46
+ .completion("completion", "Generate bash/zsh completion")
47
+ .epilogue("for documentation, visit https://cdxgen.github.io/cdxgen")
48
+ .scriptName("cdx-convert")
49
+ .version(retrieveCdxgenVersion())
50
+ .help()
51
+ .wrap(Math.min(120, yargs().terminalWidth())).argv;
52
+
53
+ if (!safeExistsSync(args.input)) {
54
+ console.error(`Input file '${args.input}' not found.`);
55
+ process.exit(1);
56
+ }
57
+
58
+ let bomJson;
59
+ try {
60
+ bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
61
+ } catch (error) {
62
+ console.error(`Failed to parse '${args.input}' as JSON: ${error.message}`);
63
+ process.exit(1);
64
+ }
65
+
66
+ if (!isCycloneDxBom(bomJson)) {
67
+ console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-convert"));
68
+ process.exit(1);
69
+ }
70
+ const cdxSpecVersion = Number.parseFloat(`${bomJson?.specVersion || ""}`);
71
+ if (![1.6, 1.7].includes(cdxSpecVersion)) {
72
+ console.error(
73
+ `Unsupported CycloneDX specVersion '${bomJson?.specVersion}'. cdx-convert currently supports CycloneDX 1.6 or 1.7 input and exports SPDX 3.0.1.`,
74
+ );
75
+ process.exit(1);
76
+ }
77
+
78
+ const spdxJson = convertCycloneDxToSpdx(bomJson, args);
79
+ if (!spdxJson) {
80
+ console.error("Conversion failed: unable to generate SPDX output.");
81
+ process.exit(1);
82
+ }
83
+
84
+ if (args.validate && !validateSpdx(spdxJson)) {
85
+ console.error("SPDX validation failed for the converted output.");
86
+ process.exit(1);
87
+ }
88
+
89
+ const outputPath = args.output || deriveSpdxOutputPath(args.input);
90
+ const outputParent = dirname(outputPath);
91
+ if (outputParent && outputParent !== "." && !safeExistsSync(outputParent)) {
92
+ safeMkdirSync(outputParent, { recursive: true });
93
+ }
94
+
95
+ fs.writeFileSync(
96
+ outputPath,
97
+ JSON.stringify(spdxJson, null, args.jsonPretty ? 2 : null),
98
+ );
99
+ console.log(`Successfully converted '${args.input}' to '${outputPath}'.`);
package/bin/evinse.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Evinse (Evinse Verification Is Nearly SBOM Evidence)
3
3
 
4
+ import fs from "node:fs";
4
5
  import process from "node:process";
5
6
 
6
7
  import yargs from "yargs";
@@ -11,12 +12,17 @@ import {
11
12
  createEvinseFile,
12
13
  prepareDB,
13
14
  } from "../lib/evinser/evinser.js";
15
+ import {
16
+ getNonCycloneDxErrorMessage,
17
+ isCycloneDxBom,
18
+ } from "../lib/helpers/bomUtils.js";
14
19
  import {
15
20
  printCallStack,
16
21
  printOccurrences,
17
22
  printReachables,
18
23
  printServices,
19
24
  } from "../lib/helpers/display.js";
25
+ import { safeExistsSync } from "../lib/helpers/utils.js";
20
26
  import { validateBom } from "../lib/validator/bomValidator.js";
21
27
 
22
28
  const args = yargs(hideBin(process.argv))
@@ -149,6 +155,23 @@ if (process.env?.CDXGEN_NODE_OPTIONS) {
149
155
  }
150
156
 
151
157
  console.log(evinseArt);
158
+ function ensureCycloneDxInput(inputFile) {
159
+ if (!safeExistsSync(inputFile)) {
160
+ return;
161
+ }
162
+ let bomJson;
163
+ try {
164
+ bomJson = JSON.parse(fs.readFileSync(inputFile, "utf8"));
165
+ } catch (error) {
166
+ console.error(`Unable to parse '${inputFile}' as JSON: ${error.message}`);
167
+ process.exit(1);
168
+ }
169
+ if (!isCycloneDxBom(bomJson)) {
170
+ console.error(getNonCycloneDxErrorMessage(bomJson, "evinse"));
171
+ process.exit(1);
172
+ }
173
+ }
174
+ ensureCycloneDxInput(args.input);
152
175
  (async () => {
153
176
  // First, prepare the database by cataloging jars and other libraries
154
177
  const dbObjMap = await prepareDB(args);
package/bin/repl.js CHANGED
@@ -9,6 +9,7 @@ import repl from "node:repl";
9
9
  import jsonata from "jsonata";
10
10
 
11
11
  import { createBom } from "../lib/cli/index.js";
12
+ import { isSpdxJsonLd } from "../lib/helpers/bomUtils.js";
12
13
  import {
13
14
  printCallStack,
14
15
  printDependencyTree,
@@ -21,6 +22,12 @@ import {
21
22
  printVulnerabilities,
22
23
  } from "../lib/helpers/display.js";
23
24
  import { readBinary } from "../lib/helpers/protobom.js";
25
+ import {
26
+ getProvenanceComponents,
27
+ getTrustedComponents,
28
+ } from "../lib/helpers/provenanceUtils.js";
29
+ import { toCycloneDxLikeBom } from "../lib/helpers/spdxUtils.js";
30
+ import { table } from "../lib/helpers/table.js";
24
31
  import { getTmpDir } from "../lib/helpers/utils.js";
25
32
  import { getBomWithOras } from "../lib/managers/oci.js";
26
33
  import { validateBom } from "../lib/validator/bomValidator.js";
@@ -54,6 +61,95 @@ if (process.env?.CDXGEN_NODE_OPTIONS) {
54
61
 
55
62
  // The current sbom is stored here
56
63
  let sbom;
64
+ const getInteractiveBom = () => toCycloneDxLikeBom(sbom);
65
+
66
+ function unescapeAnnotationText(value) {
67
+ return String(value || "")
68
+ .replace(/<br>/g, "\n")
69
+ .replace(/&lt;/g, "<")
70
+ .replace(/&gt;/g, ">")
71
+ .replace(/&amp;/g, "&")
72
+ .replace(/\\([\\`*_{}\[\]()#+!|])/g, "$1")
73
+ .trim();
74
+ }
75
+
76
+ function parseAnnotationProperties(text) {
77
+ const properties = {};
78
+ const lines = String(text || "").split(/\r?\n/);
79
+ let foundHeader = false;
80
+ for (const line of lines) {
81
+ if (!line.startsWith("|")) {
82
+ continue;
83
+ }
84
+ const cells = line
85
+ .split("|")
86
+ .slice(1, -1)
87
+ .map((cell) => cell.trim());
88
+ if (cells.length < 2) {
89
+ continue;
90
+ }
91
+ if (cells[0] === "Property" && cells[1] === "Value") {
92
+ foundHeader = true;
93
+ continue;
94
+ }
95
+ if (
96
+ foundHeader &&
97
+ /^-+$/.test(cells[0].replace(/\s/g, "")) &&
98
+ /^-+$/.test(cells[1].replace(/\s/g, ""))
99
+ ) {
100
+ continue;
101
+ }
102
+ if (!foundHeader) {
103
+ continue;
104
+ }
105
+ properties[unescapeAnnotationText(cells[0])] = unescapeAnnotationText(
106
+ cells[1],
107
+ );
108
+ }
109
+ return properties;
110
+ }
111
+
112
+ function getAuditAnnotations() {
113
+ return (sbom?.annotations || [])
114
+ .map((annotation) => {
115
+ const properties = parseAnnotationProperties(annotation?.text);
116
+ return {
117
+ firstLine: unescapeAnnotationText(
118
+ String(annotation?.text || "").split(/\r?\n/, 1)[0],
119
+ ),
120
+ properties,
121
+ raw: annotation,
122
+ };
123
+ })
124
+ .filter(
125
+ (annotation) =>
126
+ Object.keys(annotation.properties).some((key) =>
127
+ key.startsWith("cdx:audit:"),
128
+ ) || annotation.firstLine.includes("cdx:audit:"),
129
+ );
130
+ }
131
+
132
+ function printAuditTable(title, rows) {
133
+ if (rows.length <= 1) {
134
+ return;
135
+ }
136
+ console.log(
137
+ table(rows, {
138
+ header: {
139
+ alignment: "center",
140
+ content: title,
141
+ },
142
+ }),
143
+ );
144
+ }
145
+
146
+ function isLikelyObom(bom) {
147
+ return Boolean(
148
+ bom?.components?.some((comp) =>
149
+ comp?.properties?.some((prop) => prop?.name === "cdx:osquery:category"),
150
+ ),
151
+ );
152
+ }
57
153
 
58
154
  let historyFile;
59
155
  const historyConfigDir = join(homedir(), ".config", ".cdxgen");
@@ -73,11 +169,24 @@ export const importSbom = (sbomOrPath) => {
73
169
  try {
74
170
  sbom = JSON.parse(fs.readFileSync(sbomOrPath, "utf-8"));
75
171
  let bomType = "SBOM";
172
+ if (isSpdxJsonLd(sbom)) {
173
+ bomType = "SPDX";
174
+ }
76
175
  if (sbom?.vulnerabilities && Array.isArray(sbom.vulnerabilities)) {
77
176
  bomType = "VDR";
78
177
  }
79
178
  console.log(`✅ ${bomType} imported successfully from ${sbomOrPath}`);
80
179
  printSummary(sbom);
180
+ if (isLikelyObom(sbom)) {
181
+ console.log(
182
+ "💭 OBOM detected. Try .osinfocategories, .obomtips, .processes, or .services_snapshot",
183
+ );
184
+ }
185
+ if (getAuditAnnotations().length) {
186
+ console.log(
187
+ "💭 Audit annotations detected. Try .auditfindings, .auditactions, or .dispatchedges.",
188
+ );
189
+ }
81
190
  } catch (e) {
82
191
  console.log(`⚠ Unable to import the BOM from ${sbomOrPath} due to ${e}`);
83
192
  }
@@ -111,12 +220,24 @@ export const importSbom = (sbomOrPath) => {
111
220
  if (process.argv.length > 2) {
112
221
  importSbom(process.argv[process.argv.length - 1]);
113
222
  console.log("💭 Type .print to view the BOM as a table");
223
+ console.log("💭 Type .trusted to list components with trusted publishing.");
224
+ console.log(
225
+ "💭 Type .provenance to list components with registry provenance evidence.",
226
+ );
227
+ if (getAuditAnnotations().length) {
228
+ console.log(
229
+ "💭 Type .auditfindings to review cdx-audit and bom-audit annotations.",
230
+ );
231
+ }
114
232
  } else if (fs.existsSync("bom.json")) {
115
233
  // If the current directory has a bom.json load it
116
234
  importSbom("bom.json");
117
235
  } else {
118
236
  console.log("💭 Use .create <path> to create an SBOM for the given path.");
119
237
  console.log("💭 Use .import <json> to import an existing BOM.");
238
+ console.log(
239
+ "💭 For OBOM investigations, try .obomtips after importing an OBOM.",
240
+ );
120
241
  console.log("💭 Type .exit or press ctrl+d to close.");
121
242
  }
122
243
 
@@ -148,6 +269,17 @@ cdxgenRepl.defineCommand("create", {
148
269
  sbom = bomNSData.bomJson;
149
270
  console.log("✅ BOM imported successfully.");
150
271
  console.log("💭 Type .print to view the BOM as a table");
272
+ console.log(
273
+ "💭 Type .trusted to list components with trusted publishing.",
274
+ );
275
+ console.log(
276
+ "💭 Type .provenance to list components with registry provenance evidence.",
277
+ );
278
+ if (getAuditAnnotations().length) {
279
+ console.log(
280
+ "💭 Type .auditfindings to review cdx-audit and bom-audit annotations.",
281
+ );
282
+ }
151
283
  } else {
152
284
  console.log("BOM was not generated successfully");
153
285
  }
@@ -207,9 +339,12 @@ cdxgenRepl.defineCommand("search", {
207
339
  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]`;
208
340
  }
209
341
  const expression = jsonata(fixedSearchStr);
210
- let components = await expression.evaluate(sbom);
342
+ const bomForSearch = searchStr.includes("~>")
343
+ ? sbom
344
+ : getInteractiveBom();
345
+ let components = await expression.evaluate(bomForSearch);
211
346
  const dexpression = jsonata(dependenciesSearchStr);
212
- let dependencies = await dexpression.evaluate(sbom);
347
+ let dependencies = await dexpression.evaluate(bomForSearch);
213
348
  if (components && !Array.isArray(components)) {
214
349
  components = [components];
215
350
  }
@@ -303,8 +438,63 @@ cdxgenRepl.defineCommand("query", {
303
438
  cdxgenRepl.defineCommand("print", {
304
439
  help: "print the current bom as a table",
305
440
  action() {
306
- if (sbom) {
307
- printTable(sbom);
441
+ const interactiveBom = getInteractiveBom();
442
+ if (interactiveBom) {
443
+ printTable(interactiveBom);
444
+ } else {
445
+ console.log(
446
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM",
447
+ );
448
+ }
449
+ this.displayPrompt();
450
+ },
451
+ });
452
+ cdxgenRepl.defineCommand("trusted", {
453
+ help: "print components with trusted publishing",
454
+ action() {
455
+ const interactiveBom = getInteractiveBom();
456
+ if (interactiveBom?.components) {
457
+ const trustedComponents = getTrustedComponents(interactiveBom.components);
458
+ if (!trustedComponents.length) {
459
+ console.log(
460
+ "No trusted-publishing components found. Look for components enriched with cdx:npm:trustedPublishing or cdx:pypi:trustedPublishing.",
461
+ );
462
+ } else {
463
+ printTable(
464
+ { components: trustedComponents, dependencies: [] },
465
+ undefined,
466
+ undefined,
467
+ `Found ${trustedComponents.length} trusted component(s) backed by trusted publishing metadata.`,
468
+ );
469
+ }
470
+ } else {
471
+ console.log(
472
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM",
473
+ );
474
+ }
475
+ this.displayPrompt();
476
+ },
477
+ });
478
+ cdxgenRepl.defineCommand("provenance", {
479
+ help: "print components with direct registry provenance evidence",
480
+ action() {
481
+ const interactiveBom = getInteractiveBom();
482
+ if (interactiveBom?.components) {
483
+ const provenanceComponents = getProvenanceComponents(
484
+ interactiveBom.components,
485
+ );
486
+ if (!provenanceComponents.length) {
487
+ console.log(
488
+ "No provenance-backed components found. Look for registry URLs, digests, signatures, or key IDs captured as component properties.",
489
+ );
490
+ } else {
491
+ printTable(
492
+ { components: provenanceComponents, dependencies: [] },
493
+ undefined,
494
+ undefined,
495
+ `Found ${provenanceComponents.length} component(s) with direct registry provenance evidence.`,
496
+ );
497
+ }
308
498
  } else {
309
499
  console.log(
310
500
  "⚠ No BOM is loaded. Use .import command to import an existing BOM",
@@ -342,8 +532,9 @@ cdxgenRepl.defineCommand("frameworks", {
342
532
  cdxgenRepl.defineCommand("tree", {
343
533
  help: "display the dependency tree",
344
534
  action() {
345
- if (sbom) {
346
- printDependencyTree(sbom);
535
+ const interactiveBom = getInteractiveBom();
536
+ if (interactiveBom) {
537
+ printDependencyTree(interactiveBom);
347
538
  } else {
348
539
  console.log(
349
540
  "⚠ No BOM is loaded. Use .import command to import an existing BOM",
@@ -355,8 +546,9 @@ cdxgenRepl.defineCommand("tree", {
355
546
  cdxgenRepl.defineCommand("provides", {
356
547
  help: "display the provides tree",
357
548
  action() {
358
- if (sbom) {
359
- printDependencyTree(sbom, "provides");
549
+ const interactiveBom = getInteractiveBom();
550
+ if (interactiveBom) {
551
+ printDependencyTree(interactiveBom, "provides");
360
552
  } else {
361
553
  console.log(
362
554
  "⚠ No BOM is loaded. Use .import command to import an existing BOM",
@@ -566,6 +758,113 @@ cdxgenRepl.defineCommand("formulation", {
566
758
  this.displayPrompt();
567
759
  },
568
760
  });
761
+ cdxgenRepl.defineCommand("auditfindings", {
762
+ help: "summarize cdx-audit and bom-audit annotations from the loaded BOM",
763
+ action() {
764
+ if (!sbom) {
765
+ console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
766
+ this.displayPrompt();
767
+ return;
768
+ }
769
+ const auditAnnotations = getAuditAnnotations();
770
+ if (!auditAnnotations.length) {
771
+ console.log(
772
+ "No audit annotations found. Generate an SBOM with --bom-audit or import a BOM enriched by cdx-audit.",
773
+ );
774
+ this.displayPrompt();
775
+ return;
776
+ }
777
+ const rows = [
778
+ ["Engine", "Severity", "Rule", "Target / Edge", "Next action"],
779
+ ];
780
+ auditAnnotations.forEach((annotation) => {
781
+ const props = annotation.properties;
782
+ rows.push([
783
+ props["cdx:audit:engine"] || "bom-audit",
784
+ props["cdx:audit:severity"] || "unknown",
785
+ props["cdx:audit:topFinding:ruleId"] ||
786
+ props["cdx:audit:ruleId"] ||
787
+ "-",
788
+ props["cdx:audit:dispatch:edge"] ||
789
+ props["cdx:audit:target:purl"] ||
790
+ props["cdx:audit:location:file"] ||
791
+ annotation.firstLine,
792
+ props["cdx:audit:nextAction"] ||
793
+ props["cdx:audit:upstreamGuidance"] ||
794
+ props["cdx:audit:mitigation"] ||
795
+ "-",
796
+ ]);
797
+ });
798
+ printAuditTable("Audit findings", rows);
799
+ this.displayPrompt();
800
+ },
801
+ });
802
+ cdxgenRepl.defineCommand("auditactions", {
803
+ help: "list next actions from predictive audit annotations",
804
+ action() {
805
+ if (!sbom) {
806
+ console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
807
+ this.displayPrompt();
808
+ return;
809
+ }
810
+ const auditAnnotations = getAuditAnnotations().filter(
811
+ (annotation) => annotation.properties["cdx:audit:nextAction"],
812
+ );
813
+ if (!auditAnnotations.length) {
814
+ console.log(
815
+ "No predictive next actions found. Import a BOM annotated by cdx-audit to review remediation guidance.",
816
+ );
817
+ this.displayPrompt();
818
+ return;
819
+ }
820
+ const rows = [["Severity", "Target", "Next action", "Upstream guidance"]];
821
+ auditAnnotations.forEach((annotation) => {
822
+ const props = annotation.properties;
823
+ rows.push([
824
+ props["cdx:audit:severity"] || "unknown",
825
+ props["cdx:audit:dispatch:edge"] ||
826
+ props["cdx:audit:target:purl"] ||
827
+ annotation.firstLine,
828
+ props["cdx:audit:nextAction"] || "-",
829
+ props["cdx:audit:upstreamGuidance"] || "-",
830
+ ]);
831
+ });
832
+ printAuditTable("Predictive audit actions", rows);
833
+ this.displayPrompt();
834
+ },
835
+ });
836
+ cdxgenRepl.defineCommand("dispatchedges", {
837
+ help: "show local sender to receiver workflow edges captured by predictive audit",
838
+ action() {
839
+ if (!sbom) {
840
+ console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
841
+ this.displayPrompt();
842
+ return;
843
+ }
844
+ const auditAnnotations = getAuditAnnotations().filter(
845
+ (annotation) => annotation.properties["cdx:audit:dispatch:edge"],
846
+ );
847
+ if (!auditAnnotations.length) {
848
+ console.log(
849
+ "No local dispatch edges found. Import a BOM annotated by cdx-audit with correlated workflow dispatch findings.",
850
+ );
851
+ this.displayPrompt();
852
+ return;
853
+ }
854
+ const rows = [["Severity", "Rule", "Sender -> Receiver", "Receiver files"]];
855
+ auditAnnotations.forEach((annotation) => {
856
+ const props = annotation.properties;
857
+ rows.push([
858
+ props["cdx:audit:severity"] || "unknown",
859
+ props["cdx:audit:topFinding:ruleId"] || "-",
860
+ props["cdx:audit:dispatch:edge"] || annotation.firstLine,
861
+ props["cdx:audit:dispatch:receiverFiles"] || "-",
862
+ ]);
863
+ });
864
+ printAuditTable("Predictive workflow dispatch edges", rows);
865
+ this.displayPrompt();
866
+ },
867
+ });
569
868
  cdxgenRepl.defineCommand("osinfocategories", {
570
869
  help: "view the category names for the OS info from the obom",
571
870
  async action() {
@@ -591,6 +890,27 @@ cdxgenRepl.defineCommand("osinfocategories", {
591
890
  this.displayPrompt();
592
891
  },
593
892
  });
893
+ // OBOM-specific analyst helper tips for SOC/IR and compliance workflows.
894
+ cdxgenRepl.defineCommand("obomtips", {
895
+ help: "show analyst tips and useful commands for OBOM investigations",
896
+ action() {
897
+ console.log("OBOM analyst quick guide:");
898
+ console.log("1. .osinfocategories");
899
+ console.log(
900
+ "2. Run an OS-query category command from .help (examples below)",
901
+ );
902
+ console.log(" .processes");
903
+ console.log(" .services_snapshot");
904
+ console.log(" .scheduled_tasks");
905
+ console.log(" .startup_items");
906
+ console.log("3. .inspect <name>");
907
+ console.log("4. .services / .print / .summary for quick pivots");
908
+ console.log(
909
+ "Tip: Generate with --bom-audit --bom-audit-categories obom-runtime for prioritized findings.",
910
+ );
911
+ this.displayPrompt();
912
+ },
913
+ });
594
914
  cdxgenRepl.defineCommand("licenses", {
595
915
  help: "visualize license distribution",
596
916
  async action() {
@@ -631,6 +951,13 @@ cdxgenRepl.defineCommand("licenses", {
631
951
  cdxgenRepl.defineCommand("inspect", {
632
952
  help: "view full JSON details of a component: .inspect <name_search_string>",
633
953
  async action(nameStr) {
954
+ if (!nameStr) {
955
+ console.log(
956
+ "⚠ Specify a component name or purl fragment. Eg: .inspect lodash",
957
+ );
958
+ this.displayPrompt();
959
+ return;
960
+ }
634
961
  if (sbom?.components) {
635
962
  const found = sbom.components.find(
636
963
  (c) =>
@@ -792,6 +1119,7 @@ cdxgenRepl.defineCommand("tagcloud", {
792
1119
  "kernel_integrity",
793
1120
  "kernel_modules",
794
1121
  "ld_preload",
1122
+ "elevated_processes",
795
1123
  "listening_ports",
796
1124
  "os_version",
797
1125
  "pipes",
@@ -799,11 +1127,14 @@ cdxgenRepl.defineCommand("tagcloud", {
799
1127
  "portage_packages",
800
1128
  "process_events",
801
1129
  "processes",
1130
+ "privilege_transitions",
1131
+ "privileged_listening_ports",
802
1132
  "python_packages",
803
1133
  "rpm_packages",
804
1134
  "scheduled_tasks",
805
1135
  "services_snapshot",
806
1136
  "startup_items",
1137
+ "sudo_executions",
807
1138
  "system_info_snapshot",
808
1139
  "windows_drivers",
809
1140
  "windows_patches",
package/bin/sign.js CHANGED
@@ -7,6 +7,10 @@ import yargs from "yargs";
7
7
  import { hideBin } from "yargs/helpers";
8
8
 
9
9
  import { signBom } from "../lib/helpers/bomSigner.js";
10
+ import {
11
+ getNonCycloneDxErrorMessage,
12
+ isCycloneDxBom,
13
+ } from "../lib/helpers/bomUtils.js";
10
14
  import { retrieveCdxgenVersion, safeExistsSync } from "../lib/helpers/utils.js";
11
15
 
12
16
  const _yargs = yargs(hideBin(process.argv));
@@ -78,6 +82,10 @@ if (!safeExistsSync(args.privateKey)) {
78
82
  try {
79
83
  const bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
80
84
  const privateKey = fs.readFileSync(args.privateKey, "utf8");
85
+ if (!isCycloneDxBom(bomJson)) {
86
+ console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-sign"));
87
+ process.exit(1);
88
+ }
81
89
 
82
90
  const signedBom = signBom(bomJson, {
83
91
  privateKey,
package/bin/validate.js CHANGED
@@ -19,6 +19,10 @@ import process from "node:process";
19
19
  import yargs from "yargs";
20
20
  import { hideBin } from "yargs/helpers";
21
21
 
22
+ import {
23
+ getNonCycloneDxErrorMessage,
24
+ isCycloneDxBom,
25
+ } from "../lib/helpers/bomUtils.js";
22
26
  import {
23
27
  dirNameStr,
24
28
  retrieveCdxgenVersion,
@@ -177,6 +181,10 @@ function writeOrPrint(content, outputPath) {
177
181
 
178
182
  const bomJson = loadBom(args.input, args.platform);
179
183
  const publicKeyStr = loadPublicKey(args.publicKey);
184
+ if (!isCycloneDxBom(bomJson)) {
185
+ console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-validate"));
186
+ process.exit(1);
187
+ }
180
188
 
181
189
  const report = validateBomAdvanced(bomJson, {
182
190
  schema: args.schema,
package/bin/verify.js CHANGED
@@ -8,6 +8,10 @@ import yargs from "yargs";
8
8
  import { hideBin } from "yargs/helpers";
9
9
 
10
10
  import { verifyNode } from "../lib/helpers/bomSigner.js";
11
+ import {
12
+ getNonCycloneDxErrorMessage,
13
+ isCycloneDxBom,
14
+ } from "../lib/helpers/bomUtils.js";
11
15
  import {
12
16
  dirNameStr,
13
17
  retrieveCdxgenVersion,
@@ -91,6 +95,10 @@ if (!bomJson) {
91
95
  console.log(`${args.input} is invalid!`);
92
96
  process.exit(1);
93
97
  }
98
+ if (!isCycloneDxBom(bomJson)) {
99
+ console.log(getNonCycloneDxErrorMessage(bomJson, "cdx-verify"));
100
+ process.exit(1);
101
+ }
94
102
 
95
103
  if (bomJson && !safeExistsSync(args.publicKey)) {
96
104
  console.log("Public key for signature verification is missing!");