@cyclonedx/cdxgen 12.1.5 → 12.2.1

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 (193) hide show
  1. package/README.md +51 -40
  2. package/bin/cdxgen.js +194 -97
  3. package/bin/evinse.js +4 -4
  4. package/bin/repl.js +1 -1
  5. package/bin/sign.js +102 -0
  6. package/bin/validate.js +233 -0
  7. package/bin/verify.js +69 -28
  8. package/data/queries.json +1 -1
  9. package/data/rules/ci-permissions.yaml +186 -0
  10. package/data/rules/dependency-sources.yaml +123 -0
  11. package/data/rules/package-integrity.yaml +135 -0
  12. package/data/rules/vscode-extensions.yaml +228 -0
  13. package/lib/cli/index.js +449 -429
  14. package/lib/cli/index.poku.js +117 -0
  15. package/lib/evinser/db.js +137 -0
  16. package/lib/{helpers → evinser}/db.poku.js +2 -6
  17. package/lib/evinser/evinser.js +2 -14
  18. package/lib/helpers/analyzer.js +606 -3
  19. package/lib/helpers/analyzer.poku.js +230 -0
  20. package/lib/helpers/bomSigner.js +312 -0
  21. package/lib/helpers/bomSigner.poku.js +156 -0
  22. package/lib/helpers/ciParsers/azurePipelines.js +295 -0
  23. package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
  24. package/lib/helpers/ciParsers/circleCi.js +286 -0
  25. package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
  26. package/lib/helpers/ciParsers/common.js +24 -0
  27. package/lib/helpers/ciParsers/githubActions.js +636 -0
  28. package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
  29. package/lib/helpers/ciParsers/gitlabCi.js +213 -0
  30. package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
  31. package/lib/helpers/ciParsers/jenkins.js +181 -0
  32. package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
  33. package/lib/helpers/depsUtils.js +219 -0
  34. package/lib/helpers/depsUtils.poku.js +207 -0
  35. package/lib/helpers/display.js +426 -5
  36. package/lib/helpers/envcontext.js +18 -3
  37. package/lib/helpers/formulationParsers.js +351 -0
  38. package/lib/helpers/logger.js +14 -0
  39. package/lib/helpers/protobom.js +9 -9
  40. package/lib/helpers/pythonutils.js +9 -0
  41. package/lib/helpers/remote/dependency-track.js +84 -0
  42. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  43. package/lib/helpers/table.js +384 -0
  44. package/lib/helpers/table.poku.js +186 -0
  45. package/lib/helpers/utils.js +865 -416
  46. package/lib/helpers/utils.poku.js +172 -265
  47. package/lib/helpers/versutils.js +202 -0
  48. package/lib/helpers/versutils.poku.js +315 -0
  49. package/lib/helpers/vsixutils.js +1061 -0
  50. package/lib/helpers/vsixutils.poku.js +2247 -0
  51. package/lib/managers/binary.js +19 -19
  52. package/lib/managers/docker.js +108 -1
  53. package/lib/managers/oci.js +10 -0
  54. package/lib/managers/piptree.js +3 -9
  55. package/lib/parsers/npmrc.js +17 -13
  56. package/lib/parsers/npmrc.poku.js +41 -5
  57. package/lib/server/openapi.yaml +34 -1
  58. package/lib/server/server.js +50 -13
  59. package/lib/server/server.poku.js +332 -144
  60. package/lib/stages/postgen/annotator.js +1 -1
  61. package/lib/stages/postgen/auditBom.js +196 -0
  62. package/lib/stages/postgen/auditBom.poku.js +378 -0
  63. package/lib/stages/postgen/postgen.js +54 -1
  64. package/lib/stages/postgen/postgen.poku.js +90 -1
  65. package/lib/stages/postgen/ruleEngine.js +369 -0
  66. package/lib/stages/pregen/envAudit.js +299 -0
  67. package/lib/stages/pregen/envAudit.poku.js +572 -0
  68. package/lib/stages/pregen/pregen.js +12 -8
  69. package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
  70. package/lib/validator/complianceEngine.js +241 -0
  71. package/lib/validator/complianceEngine.poku.js +168 -0
  72. package/lib/validator/complianceRules.js +1610 -0
  73. package/lib/validator/complianceRules.poku.js +328 -0
  74. package/lib/validator/index.js +222 -0
  75. package/lib/validator/index.poku.js +144 -0
  76. package/lib/validator/reporters/annotations.js +121 -0
  77. package/lib/validator/reporters/console.js +149 -0
  78. package/lib/validator/reporters/index.js +41 -0
  79. package/lib/validator/reporters/json.js +37 -0
  80. package/lib/validator/reporters/sarif.js +184 -0
  81. package/lib/validator/reporters.poku.js +150 -0
  82. package/package.json +8 -9
  83. package/types/bin/sign.d.ts +3 -0
  84. package/types/bin/sign.d.ts.map +1 -0
  85. package/types/bin/validate.d.ts +3 -0
  86. package/types/bin/validate.d.ts.map +1 -0
  87. package/types/helpers/utils.d.ts +0 -1
  88. package/types/lib/cli/index.d.ts +49 -52
  89. package/types/lib/cli/index.d.ts.map +1 -1
  90. package/types/lib/evinser/db.d.ts +34 -0
  91. package/types/lib/evinser/db.d.ts.map +1 -0
  92. package/types/lib/evinser/evinser.d.ts +63 -16
  93. package/types/lib/evinser/evinser.d.ts.map +1 -1
  94. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  95. package/types/lib/helpers/bomSigner.d.ts +27 -0
  96. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  97. package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
  98. package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
  99. package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
  100. package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
  101. package/types/lib/helpers/ciParsers/common.d.ts +11 -0
  102. package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
  103. package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
  104. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
  105. package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
  106. package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
  107. package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
  108. package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
  109. package/types/lib/helpers/depsUtils.d.ts +21 -0
  110. package/types/lib/helpers/depsUtils.d.ts.map +1 -0
  111. package/types/lib/helpers/display.d.ts +111 -11
  112. package/types/lib/helpers/display.d.ts.map +1 -1
  113. package/types/lib/helpers/envcontext.d.ts +19 -7
  114. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  115. package/types/lib/helpers/formulationParsers.d.ts +50 -0
  116. package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
  117. package/types/lib/helpers/logger.d.ts +15 -1
  118. package/types/lib/helpers/logger.d.ts.map +1 -1
  119. package/types/lib/helpers/protobom.d.ts +2 -2
  120. package/types/lib/helpers/pythonutils.d.ts +10 -1
  121. package/types/lib/helpers/pythonutils.d.ts.map +1 -1
  122. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  123. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  124. package/types/lib/helpers/table.d.ts +6 -0
  125. package/types/lib/helpers/table.d.ts.map +1 -0
  126. package/types/lib/helpers/utils.d.ts +533 -128
  127. package/types/lib/helpers/utils.d.ts.map +1 -1
  128. package/types/lib/helpers/versutils.d.ts +8 -0
  129. package/types/lib/helpers/versutils.d.ts.map +1 -0
  130. package/types/lib/helpers/vsixutils.d.ts +130 -0
  131. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  132. package/types/lib/managers/docker.d.ts +12 -31
  133. package/types/lib/managers/docker.d.ts.map +1 -1
  134. package/types/lib/managers/oci.d.ts +11 -1
  135. package/types/lib/managers/oci.d.ts.map +1 -1
  136. package/types/lib/managers/piptree.d.ts.map +1 -1
  137. package/types/lib/parsers/npmrc.d.ts +4 -1
  138. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  139. package/types/lib/server/server.d.ts +22 -2
  140. package/types/lib/server/server.d.ts.map +1 -1
  141. package/types/lib/stages/postgen/auditBom.d.ts +20 -0
  142. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  143. package/types/lib/stages/postgen/postgen.d.ts +8 -1
  144. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  145. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  146. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  147. package/types/lib/stages/pregen/envAudit.d.ts +8 -0
  148. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  149. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  150. package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
  151. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  152. package/types/lib/validator/complianceEngine.d.ts +66 -0
  153. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  154. package/types/lib/validator/complianceRules.d.ts +70 -0
  155. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  156. package/types/lib/validator/index.d.ts +70 -0
  157. package/types/lib/validator/index.d.ts.map +1 -0
  158. package/types/lib/validator/reporters/annotations.d.ts +31 -0
  159. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  160. package/types/lib/validator/reporters/console.d.ts +30 -0
  161. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  162. package/types/lib/validator/reporters/index.d.ts +21 -0
  163. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  164. package/types/lib/validator/reporters/json.d.ts +11 -0
  165. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  166. package/types/lib/validator/reporters/sarif.d.ts +16 -0
  167. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  168. package/lib/helpers/db.js +0 -162
  169. package/lib/stages/pregen/env-audit.js +0 -34
  170. package/lib/stages/pregen/env-audit.poku.js +0 -290
  171. package/types/helpers/db.d.ts +0 -35
  172. package/types/helpers/db.d.ts.map +0 -1
  173. package/types/lib/helpers/db.d.ts +0 -35
  174. package/types/lib/helpers/db.d.ts.map +0 -1
  175. package/types/lib/helpers/validator.d.ts.map +0 -1
  176. package/types/lib/stages/pregen/env-audit.d.ts +0 -2
  177. package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
  178. package/types/managers/binary.d.ts +0 -37
  179. package/types/managers/binary.d.ts.map +0 -1
  180. package/types/managers/docker.d.ts +0 -56
  181. package/types/managers/docker.d.ts.map +0 -1
  182. package/types/managers/oci.d.ts +0 -2
  183. package/types/managers/oci.d.ts.map +0 -1
  184. package/types/managers/piptree.d.ts +0 -2
  185. package/types/managers/piptree.d.ts.map +0 -1
  186. package/types/server/server.d.ts +0 -34
  187. package/types/server/server.d.ts.map +0 -1
  188. package/types/stages/postgen/annotator.d.ts +0 -27
  189. package/types/stages/postgen/annotator.d.ts.map +0 -1
  190. package/types/stages/postgen/postgen.d.ts +0 -51
  191. package/types/stages/postgen/postgen.d.ts.map +0 -1
  192. package/types/stages/pregen/pregen.d.ts +0 -59
  193. package/types/stages/pregen/pregen.d.ts.map +0 -1
@@ -1,7 +1,9 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
2
3
  import process from "node:process";
3
4
 
4
- import { createStream, table } from "table";
5
+ import { createStream, table } from "./table.js";
6
+ import { isSecureMode, safeExistsSync, toCamel } from "./utils.js";
5
7
 
6
8
  // https://github.com/yangshun/tree-node-cli/blob/master/src/index.js
7
9
  const SYMBOLS_ANSI = {
@@ -19,6 +21,16 @@ const highlightStr = (s, highlight) => {
19
21
  }
20
22
  return s;
21
23
  };
24
+ /**
25
+ * Prints the BOM components as a streaming table to the console.
26
+ * Delegates to {@link printOSTable} automatically when the BOM metadata indicates
27
+ * an operating-system or platform component type.
28
+ *
29
+ * @param {Object} bomJson CycloneDX BOM JSON object
30
+ * @param {string[]} [filterTypes] Optional list of component types to include; all types shown when omitted
31
+ * @param {string} [highlight] Optional string to highlight in the output
32
+ * @returns {void}
33
+ */
22
34
  export function printTable(
23
35
  bomJson,
24
36
  filterTypes = undefined,
@@ -78,6 +90,7 @@ export function printTable(
78
90
  ]);
79
91
  }
80
92
  }
93
+ stream.end();
81
94
  console.log();
82
95
  if (!filterTypes) {
83
96
  console.log(
@@ -98,6 +111,12 @@ const formatProps = (props) => {
98
111
  }
99
112
  return retList.join("\n");
100
113
  };
114
+ /**
115
+ * Prints OS package components from the BOM as a formatted streaming table.
116
+ *
117
+ * @param {Object} bomJson CycloneDX BOM JSON object
118
+ * @returns {void}
119
+ */
101
120
  export function printOSTable(bomJson) {
102
121
  const config = {
103
122
  columnDefault: {
@@ -116,8 +135,16 @@ export function printOSTable(bomJson) {
116
135
  (comp.tags || []).join(", "),
117
136
  ]);
118
137
  }
138
+ stream.end();
119
139
  console.log();
120
140
  }
141
+ /**
142
+ * Prints the services listed in the BOM as a formatted table.
143
+ * Includes endpoint URLs, authentication flag, and cross-trust-boundary flag.
144
+ *
145
+ * @param {Object} bomJson CycloneDX BOM JSON object
146
+ * @returns {void}
147
+ */
121
148
  export function printServices(bomJson) {
122
149
  const data = [["Name", "Endpoints", "Authenticated", "X Trust Boundary"]];
123
150
  if (!bomJson?.services) {
@@ -142,8 +169,14 @@ export function printServices(bomJson) {
142
169
  }
143
170
  }
144
171
 
172
+ /**
173
+ * Prints the formulation components from the BOM as a formatted table.
174
+ *
175
+ * @param {Object} bomJson CycloneDX BOM JSON object
176
+ * @returns {void}
177
+ */
145
178
  export function printFormulation(bomJson) {
146
- const data = [["Tyoe", "Name", "Version"]];
179
+ const data = [["Type", "Name", "Version"]];
147
180
  if (!bomJson?.formulation) {
148
181
  return;
149
182
  }
@@ -178,6 +211,13 @@ const locationComparator = (a, b) => {
178
211
  return a.localeCompare(b);
179
212
  };
180
213
 
214
+ /**
215
+ * Prints component evidence occurrences (file locations) as a streaming table.
216
+ * Only components that have `evidence.occurrences` are included.
217
+ *
218
+ * @param {Object} bomJson CycloneDX BOM JSON object
219
+ * @returns {void}
220
+ */
181
221
  export function printOccurrences(bomJson) {
182
222
  if (!bomJson?.components) {
183
223
  return;
@@ -214,9 +254,17 @@ export function printOccurrences(bomJson) {
214
254
  stream.write(row);
215
255
  }
216
256
  }
257
+ stream.end();
217
258
  console.log();
218
259
  }
219
260
 
261
+ /**
262
+ * Prints the call stack evidence for each component in the BOM as a formatted table.
263
+ * Only components that have `evidence.callstack.frames` are included.
264
+ *
265
+ * @param {Object} bomJson CycloneDX BOM JSON object
266
+ * @returns {void}
267
+ */
220
268
  export function printCallStack(bomJson) {
221
269
  const data = [["Group", "Name", "Version", "Call Stack"]];
222
270
  if (!bomJson?.components) {
@@ -260,6 +308,15 @@ export function printCallStack(bomJson) {
260
308
  console.log(table(data, config));
261
309
  }
262
310
  }
311
+ /**
312
+ * Prints the dependency tree from the BOM as an ASCII tree diagram.
313
+ * Uses the `table` library for small trees and plain console output for larger ones.
314
+ *
315
+ * @param {Object} bomJson CycloneDX BOM JSON object containing a `dependencies` array
316
+ * @param {string} [mode="dependsOn"] Dependency relation to traverse (`"dependsOn"` or `"provides"`)
317
+ * @param {string} [highlight] Optional string to highlight in the tree output
318
+ * @returns {void}
319
+ */
263
320
  export function printDependencyTree(
264
321
  bomJson,
265
322
  mode = "dependsOn",
@@ -362,9 +419,16 @@ const recursePrint = (depMap, subtree, level, shownList, treeGraphics) => {
362
419
  }
363
420
  };
364
421
 
422
+ /**
423
+ * Prints a table of reachable components derived from a reachability slices file.
424
+ * Aggregates per-purl reachable-flow counts and sorts them descending.
425
+ *
426
+ * @param {Object} sliceArtefacts Slice artefact paths, must include `reachablesSlicesFile`
427
+ * @returns {void}
428
+ */
365
429
  export function printReachables(sliceArtefacts) {
366
430
  const reachablesSlicesFile = sliceArtefacts.reachablesSlicesFile;
367
- if (!existsSync(reachablesSlicesFile)) {
431
+ if (!safeExistsSync(reachablesSlicesFile)) {
368
432
  return;
369
433
  }
370
434
  const purlCounts = {};
@@ -398,6 +462,12 @@ export function printReachables(sliceArtefacts) {
398
462
  }
399
463
  }
400
464
 
465
+ /**
466
+ * Prints a formatted table of CycloneDX vulnerability objects.
467
+ *
468
+ * @param {Object[]} vulnerabilities Array of CycloneDX vulnerability objects
469
+ * @returns {void}
470
+ */
401
471
  export function printVulnerabilities(vulnerabilities) {
402
472
  if (!vulnerabilities) {
403
473
  return;
@@ -426,6 +496,14 @@ export function printVulnerabilities(vulnerabilities) {
426
496
  console.log(`${vulnerabilities.length} vulnerabilities found.`);
427
497
  }
428
498
 
499
+ /**
500
+ * Prints an OWASP donation banner when running in a CI environment.
501
+ * The banner is suppressed when `options.noBanner` is set or the repository
502
+ * belongs to the cdxgen project itself.
503
+ *
504
+ * @param {Object} options CLI options
505
+ * @returns {void}
506
+ */
429
507
  export function printSponsorBanner(options) {
430
508
  if (
431
509
  process?.env?.CI &&
@@ -439,7 +517,7 @@ export function printSponsorBanner(options) {
439
517
  },
440
518
  };
441
519
  let message =
442
- "OWASP foundation relies on donations to fund our projects.\nDonation link: https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX";
520
+ "OWASP foundation relies on donations to fund our projects.\nDonation link: https://owasp.org/donate/?reponame=www-project-cdxgen&title=OWASP+cdxgen";
443
521
  if (options.serverUrl && options.apiKey) {
444
522
  message = `${message}\nDependency Track: https://owasp.org/donate/?reponame=www-project-dependency-track&title=OWASP+Dependency-Track`;
445
523
  }
@@ -448,6 +526,13 @@ export function printSponsorBanner(options) {
448
526
  }
449
527
  }
450
528
 
529
+ /**
530
+ * Prints a BOM summary table including generator tool names, component package types,
531
+ * and component namespaces extracted from BOM metadata properties.
532
+ *
533
+ * @param {Object} bomJson CycloneDX BOM JSON object
534
+ * @returns {void}
535
+ */
451
536
  export function printSummary(bomJson) {
452
537
  const config = {
453
538
  header: {
@@ -497,3 +582,339 @@ export function printSummary(bomJson) {
497
582
  const data = [[message]];
498
583
  console.log(table(data, config));
499
584
  }
585
+
586
+ /**
587
+ * @typedef {{type: string, variable: string, severity: string, message: string, mitigation: string}} EnvAuditFinding
588
+ */
589
+
590
+ /**
591
+ * Runs the pre-generation environment audit and renders the results as formatted
592
+ * tables to the console. Called when the --env-audit CLI flag is set.
593
+ *
594
+ * @param {string} filePath Project path being scanned
595
+ * @param {Object} config Loaded .cdxgenrc / config-file values
596
+ * @param {Object} options Effective CLI options
597
+ * @param {EnvAuditFinding[]} envAuditFindings Audit findings to display
598
+ */
599
+ export function displaySelfThreatModel(
600
+ filePath,
601
+ config,
602
+ options,
603
+ envAuditFindings,
604
+ ) {
605
+ const TLP = options.tlpClassification || "CLEAR";
606
+ const risks = [];
607
+ let riskScore = 0;
608
+
609
+ const addRisk = (level, reason, category = "configuration") => {
610
+ const scores = { low: 1, medium: 3, high: 5, critical: 8 };
611
+ riskScore = Math.min(10, riskScore + scores[level]);
612
+ risks.push({ level, reason, category });
613
+ };
614
+
615
+ // Config file risks
616
+ if (Object.keys(config).length > 0) {
617
+ addRisk(
618
+ "medium",
619
+ "A .cdxgenrc config file was loaded from the working directory. It may override security-relevant settings without being visible on the command line.",
620
+ "configuration",
621
+ );
622
+ const sensitive = ["server-url", "api-key", "include-formulation"];
623
+ for (const key of sensitive) {
624
+ if (config[key] || config[toCamel(key)]) {
625
+ addRisk(
626
+ key === "api-key" ? "high" : "medium",
627
+ `Config file sets '${key}', which affects SBOM content or remote submission behavior.`,
628
+ "configuration",
629
+ );
630
+ }
631
+ }
632
+ }
633
+
634
+ // Remote submission risks
635
+ if (options.serverUrl) {
636
+ const isHttps = options.serverUrl.startsWith("https://");
637
+ addRisk(
638
+ isHttps ? "medium" : "critical",
639
+ `SBOM will be submitted to ${options.serverUrl}${!isHttps ? " over plain HTTP — contents may be intercepted or tampered in transit." : "."}`,
640
+ "network",
641
+ );
642
+ if (options.skipDtTlsCheck) {
643
+ addRisk(
644
+ "high",
645
+ "TLS certificate validation is disabled for Dependency-Track uploads. SBOM contents may be intercepted or tampered in transit.",
646
+ "network",
647
+ );
648
+ }
649
+ }
650
+
651
+ // Data exposure risks
652
+ if (options.includeFormulation) {
653
+ addRisk(
654
+ "medium",
655
+ "Formulation mode is active. The SBOM will include build metadata such as git history, committer identities, and CI environment variables.",
656
+ "data-exposure",
657
+ );
658
+ }
659
+ if (options.evidence || options.deep) {
660
+ addRisk(
661
+ "medium",
662
+ "Evidence / deep mode will invoke build tools and parse source files to collect call graph and reachability evidence. Malicious build scripts may execute.",
663
+ "data-exposure",
664
+ );
665
+ }
666
+ if (options.installDeps) {
667
+ addRisk(
668
+ "high",
669
+ "Dependency auto-install is enabled. Lifecycle hooks (install scripts) from third-party packages will execute in the current environment.",
670
+ "data-exposure",
671
+ );
672
+ }
673
+
674
+ // Output path outside the project directory
675
+ if (options.output) {
676
+ const resolvedOutput = path.resolve(options.output);
677
+ const resolvedProject = path.resolve(filePath);
678
+ if (
679
+ !resolvedOutput.startsWith(resolvedProject + path.sep) &&
680
+ resolvedOutput !== resolvedProject
681
+ ) {
682
+ addRisk(
683
+ "medium",
684
+ `Output path '${options.output}' resolves to '${resolvedOutput}', which is outside the project directory '${resolvedProject}'. Ensure this is intentional.`,
685
+ "configuration",
686
+ );
687
+ }
688
+ }
689
+
690
+ // Environment variable risks (config-layer only; env-audit covers the rest)
691
+ if (process.env.CDXGEN_SERVER_URL) {
692
+ addRisk(
693
+ "low",
694
+ "CDXGEN_SERVER_URL is set in the environment and will override any --server-url value.",
695
+ "environment",
696
+ );
697
+ }
698
+
699
+ // Integrate environment audit findings
700
+ if (envAuditFindings?.length) {
701
+ for (const f of envAuditFindings) {
702
+ const categoryMap = {
703
+ "code-execution": "runtime",
704
+ "debug-exposure": "runtime",
705
+ "environment-variable": "environment",
706
+ "network-interception": "network",
707
+ "credential-exposure": "environment",
708
+ "permission-misuse": "runtime",
709
+ privilege: "runtime",
710
+ };
711
+ addRisk(
712
+ f.severity,
713
+ `${f.variable}: ${f.message}`,
714
+ categoryMap[f.type] || "configuration",
715
+ );
716
+ }
717
+ }
718
+
719
+ const nodeOptions = process.env.NODE_OPTIONS || "";
720
+ const riskLevel =
721
+ riskScore >= 8
722
+ ? "CRITICAL"
723
+ : riskScore >= 5
724
+ ? "HIGH"
725
+ : riskScore >= 3
726
+ ? "MEDIUM"
727
+ : "LOW";
728
+
729
+ const riskColor = {
730
+ CRITICAL: "\x1b[1;31m",
731
+ HIGH: "\x1b[1;33m",
732
+ MEDIUM: "\x1b[1;36m",
733
+ LOW: "\x1b[1;32m",
734
+ };
735
+ const reset = "\x1b[0m";
736
+ const tlpGuidance = {
737
+ CLEAR: "May be shared publicly. No restrictions.",
738
+ GREEN: "Limited to community/peers. Not for public posting.",
739
+ AMBER:
740
+ "Limited to organisation and trusted partners. Handle-in-confidence.",
741
+ AMBER_AND_STRICT: "Organisation only. No external sharing.",
742
+ RED: "Named recipients only. Do not forward or store beyond session.",
743
+ };
744
+ const headerData = [
745
+ ["TLP Classification", `${TLP} — ${tlpGuidance[TLP]}`],
746
+ ["Risk Score", `${riskScore}/10`],
747
+ ["Risk Level", `${riskColor[riskLevel]}${riskLevel}${reset}`],
748
+ ];
749
+ const headerConfig = {
750
+ header: {
751
+ alignment: "center",
752
+ content:
753
+ "SBOM Generation Environment Assessment\nPre-generation security audit by cdxgen",
754
+ },
755
+ columns: [{ width: 30, alignment: "right" }, { width: 70 }],
756
+ columnDefault: { wrapWord: true },
757
+ };
758
+
759
+ console.log(table(headerData, headerConfig));
760
+ if (risks.length > 0) {
761
+ const findingsData = [["#", "Severity", "Category", "Finding"]];
762
+ risks.forEach(({ level, reason, category }, i) => {
763
+ const severityColor =
764
+ level === "critical"
765
+ ? "\x1b[1;31m"
766
+ : level === "high"
767
+ ? "\x1b[1;33m"
768
+ : level === "medium"
769
+ ? "\x1b[1;36m"
770
+ : "\x1b[1;32m";
771
+ findingsData.push([
772
+ `${i + 1}`,
773
+ `${severityColor}${level.toUpperCase()}${reset}`,
774
+ category,
775
+ reason,
776
+ ]);
777
+ });
778
+ const findingsConfig = {
779
+ header: {
780
+ alignment: "center",
781
+ content: `Findings (${risks.length})`,
782
+ },
783
+ columns: [
784
+ { width: 5, alignment: "right" },
785
+ { width: 12 },
786
+ { width: 17 },
787
+ { width: 66 },
788
+ ],
789
+ columnDefault: { wrapWord: true },
790
+ };
791
+ console.log(table(findingsData, findingsConfig));
792
+ } else {
793
+ const noFindingsData = [
794
+ [
795
+ `${riskColor[riskLevel]}✅ No risks detected in the current configuration.${reset}`,
796
+ ],
797
+ ];
798
+ const noFindingsConfig = {
799
+ header: { alignment: "center", content: "📋 Findings" },
800
+ columns: [{ width: 100, alignment: "center" }],
801
+ };
802
+ console.log(table(noFindingsData, noFindingsConfig));
803
+ }
804
+
805
+ const configData = [
806
+ ["Setting", "Value"],
807
+ ["Project", options.projectName || filePath],
808
+ ["Type(s)", options.projectType?.join(", ") || "auto-detect"],
809
+ ["Profile", options.profile || "generic"],
810
+ ["Path", filePath],
811
+ ["Output", options.output || "(stdout)"],
812
+ ["Recursive", options.recursive ? "yes" : "no"],
813
+ ["Remote Submission", options.serverUrl || "none"],
814
+ ["Formulation", options.includeFormulation ? "yes" : "no"],
815
+ ["Evidence / Deep Mode", options.evidence || options.deep ? "yes" : "no"],
816
+ ["Auto-install Dependencies", options.installDeps ? "yes" : "no"],
817
+ ["NODE_OPTIONS", nodeOptions || "(not set)"],
818
+ ];
819
+ const effConfigTableConfig = {
820
+ header: { alignment: "center", content: "Effective Configuration" },
821
+ columns: [{ width: 28 }, { width: 72 }],
822
+ columnDefault: { wrapWord: true },
823
+ };
824
+ console.log(table(configData, effConfigTableConfig));
825
+
826
+ const recommendations = [];
827
+ if (["AMBER", "AMBER_AND_STRICT", "RED"].includes(TLP)) {
828
+ recommendations.push([
829
+ "High",
830
+ "Omit --include-formulation to avoid embedding committer identities and CI secrets in the SBOM.",
831
+ ]);
832
+ if (TLP === "RED") {
833
+ recommendations.push([
834
+ "Critical",
835
+ "Run cdxgen inside an isolated container or VM with no access to production credentials.",
836
+ ]);
837
+ recommendations.push([
838
+ "Critical",
839
+ "Do not set --server-url; review and handle the output SBOM manually before sharing.",
840
+ ]);
841
+ }
842
+ }
843
+ if (riskScore >= 5) {
844
+ recommendations.push([
845
+ "High",
846
+ "Address the findings above before scanning untrusted repositories.",
847
+ ]);
848
+ recommendations.push([
849
+ "Medium",
850
+ "Pass --no-install-deps to prevent package manager hooks from executing.",
851
+ ]);
852
+ }
853
+ if (envAuditFindings.some((f) => f.type === "code-execution")) {
854
+ recommendations.push([
855
+ "High",
856
+ "Remove code-execution flags (--require, --eval, --loader, --import) from NODE_OPTIONS and JAVA_TOOL_OPTIONS.",
857
+ ]);
858
+ }
859
+ if (envAuditFindings.some((f) => f.variable === "NODE_PATH")) {
860
+ recommendations.push([
861
+ "High",
862
+ "Unset NODE_PATH to prevent module-resolution poisoning by malicious packages.",
863
+ ]);
864
+ }
865
+ if (envAuditFindings.some((f) => f.type === "privilege")) {
866
+ recommendations.push([
867
+ "High",
868
+ "Do not run cdxgen as root. Create a dedicated low-privilege user or use a rootless container.",
869
+ ]);
870
+ }
871
+ if (/--permission\b/i.test(nodeOptions)) {
872
+ recommendations.push([
873
+ "Medium",
874
+ "Audit every --allow-* scope; use absolute paths rather than wildcards to minimise the permission surface.",
875
+ ]);
876
+ }
877
+ recommendations.push([
878
+ "Info",
879
+ "Minimal safe invocation: cdxgen --no-install-deps --output ./sbom.cdx.json <path>",
880
+ ]);
881
+ const recommendationsData = [["Priority", "Action"]];
882
+ recommendations.forEach(([priority, action]) => {
883
+ const priorityColor =
884
+ priority === "Critical"
885
+ ? "\x1b[1;31m"
886
+ : priority === "High"
887
+ ? "\x1b[1;33m"
888
+ : priority === "Medium"
889
+ ? "\x1b[1;36m"
890
+ : "\x1b[1;32m";
891
+ recommendationsData.push([`${priorityColor}${priority}${reset}`, action]);
892
+ });
893
+ const recommendationsConfig = {
894
+ header: {
895
+ alignment: "center",
896
+ content: `Recommendations for TLP:${TLP}`,
897
+ },
898
+ columns: [{ width: 12 }, { width: 88 }],
899
+ columnDefault: { wrapWord: true },
900
+ };
901
+
902
+ console.log(table(recommendationsData, recommendationsConfig));
903
+ // Only abort in secure mode when at least one finding is high or critical severity.
904
+ // Accumulated low/medium findings may push riskScore above 5 but should not abort.
905
+ if (
906
+ isSecureMode &&
907
+ envAuditFindings?.some((f) => ["high", "critical"].includes(f.severity))
908
+ ) {
909
+ const abortData = [
910
+ [
911
+ `${riskColor[riskLevel]}🚫 SECURE MODE: High-risk configuration detected. Aborting SBOM generation.${reset}`,
912
+ ],
913
+ ];
914
+ const abortConfig = {
915
+ columns: [{ width: 100, alignment: "center" }],
916
+ };
917
+ console.log(table(abortData, abortConfig));
918
+ process.exit(1);
919
+ }
920
+ }
@@ -37,6 +37,7 @@ export const SDKMAN_JAVA_TOOL_ALIASES = {
37
37
  java23: process.env.JAVA23_TOOL || "23.0.2-tem",
38
38
  java24: process.env.JAVA24_TOOL || "24.0.2-tem",
39
39
  java25: process.env.JAVA25_TOOL || "25.0.2-tem",
40
+ java26: process.env.JAVA26_TOOL || "26-tem",
40
41
  };
41
42
 
42
43
  /**
@@ -257,7 +258,7 @@ export function collectGccInfo(dir) {
257
258
  type: "platform",
258
259
  name: "gcc",
259
260
  version: versionDesc.split("\n")[0],
260
- description: moduleDesc.replaceAll("\n", "\\n"),
261
+ description: (moduleDesc || "").replaceAll("\n", "\\n"),
261
262
  };
262
263
  }
263
264
  return undefined;
@@ -277,7 +278,7 @@ export function collectRustInfo(dir) {
277
278
  type: "platform",
278
279
  name: "rustc",
279
280
  version: versionDesc.trim(),
280
- description: moduleDesc.trim(),
281
+ description: (moduleDesc || "").trim(),
281
282
  };
282
283
  }
283
284
  return undefined;
@@ -405,6 +406,12 @@ const getCommandOutput = (cmd, dir, args) => {
405
406
  }
406
407
  if (DEBUG_MODE) {
407
408
  if (dir) {
409
+ if (safeExistsSync(join(dir, commandToUse))) {
410
+ console.warn(
411
+ `SECURE MODE: Found ${commandToUse} inside ${dir}. This command will not be executed.`,
412
+ );
413
+ return undefined;
414
+ }
408
415
  console.log(`Executing ${commandToUse} in ${dir}`);
409
416
  } else {
410
417
  console.log(`Executing ${commandToUse}`);
@@ -417,7 +424,7 @@ const getCommandOutput = (cmd, dir, args) => {
417
424
  });
418
425
  const stdout = result.stdout ? result.stdout.toString() : "";
419
426
  const stderr = result.stderr ? result.stderr.toString() : "";
420
- return `${stdout} + "\n" + ${stderr}`;
427
+ return `${stdout}\n${stderr}`.trim() || undefined;
421
428
  };
422
429
 
423
430
  /**
@@ -690,6 +697,14 @@ export function isRbenvAvailable() {
690
697
  }
691
698
  }
692
699
 
700
+ /**
701
+ * Returns the rbenv binary directory for the given Ruby version.
702
+ * Respects the `RBENV_ROOT` environment variable when set; otherwise falls back
703
+ * to `~/.rbenv/versions/<rubyVersion>/bin`.
704
+ *
705
+ * @param {string} rubyVersion Ruby version string (e.g. `"3.2.2"`)
706
+ * @returns {string} Absolute path to the rbenv bin directory for that version
707
+ */
693
708
  export function rubyVersionDir(rubyVersion) {
694
709
  return process.env.RBENV_ROOT
695
710
  ? join(process.env.RBENV_ROOT, "versions", rubyVersion, "bin")