@cyclonedx/cdxgen 12.3.3 → 12.4.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 (175) hide show
  1. package/README.md +69 -25
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +270 -127
  4. package/bin/convert.js +34 -15
  5. package/bin/hbom.js +495 -0
  6. package/bin/repl.js +592 -37
  7. package/bin/validate.js +31 -4
  8. package/bin/verify.js +18 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
  13. package/data/predictive-audit-allowlist.json +11 -0
  14. package/data/queries-darwin.json +12 -1
  15. package/data/queries-win.json +7 -1
  16. package/data/queries.json +39 -2
  17. package/data/rules/ai-agent-governance.yaml +16 -0
  18. package/data/rules/asar-archives.yaml +150 -0
  19. package/data/rules/chrome-extensions.yaml +8 -0
  20. package/data/rules/ci-permissions.yaml +42 -18
  21. package/data/rules/container-risk.yaml +14 -7
  22. package/data/rules/dependency-sources.yaml +11 -0
  23. package/data/rules/hbom-compliance.yaml +325 -0
  24. package/data/rules/hbom-performance.yaml +307 -0
  25. package/data/rules/hbom-security.yaml +248 -0
  26. package/data/rules/host-topology.yaml +165 -0
  27. package/data/rules/mcp-servers.yaml +18 -3
  28. package/data/rules/obom-runtime.yaml +907 -22
  29. package/data/rules/package-integrity.yaml +14 -0
  30. package/data/rules/rootfs-hardening.yaml +179 -0
  31. package/data/rules/vscode-extensions.yaml +9 -0
  32. package/lib/audit/index.js +210 -8
  33. package/lib/audit/index.poku.js +332 -0
  34. package/lib/audit/reporters.js +222 -0
  35. package/lib/audit/targets.js +146 -1
  36. package/lib/audit/targets.poku.js +186 -0
  37. package/lib/cli/asar.poku.js +328 -0
  38. package/lib/cli/index.js +527 -99
  39. package/lib/cli/index.poku.js +1469 -212
  40. package/lib/evinser/evinser.js +14 -9
  41. package/lib/helpers/analyzer.js +1406 -29
  42. package/lib/helpers/analyzer.poku.js +342 -0
  43. package/lib/helpers/analyzerScope.js +712 -0
  44. package/lib/helpers/asarutils.js +1556 -0
  45. package/lib/helpers/asarutils.poku.js +443 -0
  46. package/lib/helpers/auditCategories.js +12 -0
  47. package/lib/helpers/auditCategories.poku.js +32 -0
  48. package/lib/helpers/bomUtils.js +155 -1
  49. package/lib/helpers/bomUtils.poku.js +79 -1
  50. package/lib/helpers/cbomutils.js +271 -1
  51. package/lib/helpers/cbomutils.poku.js +248 -5
  52. package/lib/helpers/display.js +291 -1
  53. package/lib/helpers/display.poku.js +149 -0
  54. package/lib/helpers/evidenceUtils.js +58 -0
  55. package/lib/helpers/evidenceUtils.poku.js +54 -0
  56. package/lib/helpers/exportUtils.js +9 -0
  57. package/lib/helpers/gtfobins.js +142 -8
  58. package/lib/helpers/gtfobins.poku.js +24 -1
  59. package/lib/helpers/hbom.js +710 -0
  60. package/lib/helpers/hbom.poku.js +496 -0
  61. package/lib/helpers/hbomAnalysis.js +268 -0
  62. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  63. package/lib/helpers/hbomLoader.js +35 -0
  64. package/lib/helpers/hostTopology.js +803 -0
  65. package/lib/helpers/hostTopology.poku.js +363 -0
  66. package/lib/helpers/inventoryStats.js +69 -0
  67. package/lib/helpers/inventoryStats.poku.js +86 -0
  68. package/lib/helpers/lolbas.js +19 -1
  69. package/lib/helpers/lolbas.poku.js +23 -0
  70. package/lib/helpers/osqueryTransform.js +47 -0
  71. package/lib/helpers/osqueryTransform.poku.js +47 -0
  72. package/lib/helpers/plugins.js +350 -0
  73. package/lib/helpers/plugins.poku.js +57 -0
  74. package/lib/helpers/protobom.js +209 -45
  75. package/lib/helpers/protobom.poku.js +183 -5
  76. package/lib/helpers/protobomLoader.js +43 -0
  77. package/lib/helpers/protobomLoader.poku.js +31 -0
  78. package/lib/helpers/remote/dependency-track.js +36 -3
  79. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  80. package/lib/helpers/source.js +24 -0
  81. package/lib/helpers/source.poku.js +32 -0
  82. package/lib/helpers/utils.js +1438 -93
  83. package/lib/helpers/utils.poku.js +846 -4
  84. package/lib/managers/binary.e2e.poku.js +367 -0
  85. package/lib/managers/binary.js +2293 -353
  86. package/lib/managers/binary.poku.js +1699 -1
  87. package/lib/managers/docker.js +201 -79
  88. package/lib/managers/docker.poku.js +337 -12
  89. package/lib/server/server.js +4 -28
  90. package/lib/stages/postgen/annotator.js +38 -0
  91. package/lib/stages/postgen/annotator.poku.js +107 -1
  92. package/lib/stages/postgen/auditBom.js +121 -18
  93. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  94. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  95. package/lib/stages/postgen/postgen.js +406 -8
  96. package/lib/stages/postgen/postgen.poku.js +484 -0
  97. package/lib/stages/postgen/ruleEngine.js +116 -0
  98. package/lib/stages/pregen/envAudit.js +14 -3
  99. package/lib/validator/bomValidator.js +90 -38
  100. package/lib/validator/bomValidator.poku.js +90 -0
  101. package/lib/validator/complianceRules.js +4 -2
  102. package/lib/validator/index.poku.js +14 -0
  103. package/package.json +23 -21
  104. package/types/bin/hbom.d.ts +3 -0
  105. package/types/bin/hbom.d.ts.map +1 -0
  106. package/types/bin/repl.d.ts +1 -1
  107. package/types/bin/repl.d.ts.map +1 -1
  108. package/types/lib/audit/index.d.ts +44 -0
  109. package/types/lib/audit/index.d.ts.map +1 -1
  110. package/types/lib/audit/reporters.d.ts +16 -0
  111. package/types/lib/audit/reporters.d.ts.map +1 -1
  112. package/types/lib/audit/targets.d.ts.map +1 -1
  113. package/types/lib/cli/index.d.ts +16 -0
  114. package/types/lib/cli/index.d.ts.map +1 -1
  115. package/types/lib/evinser/evinser.d.ts +4 -0
  116. package/types/lib/evinser/evinser.d.ts.map +1 -1
  117. package/types/lib/helpers/analyzer.d.ts +33 -0
  118. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  119. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  120. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  121. package/types/lib/helpers/asarutils.d.ts +34 -0
  122. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  123. package/types/lib/helpers/auditCategories.d.ts +5 -0
  124. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  125. package/types/lib/helpers/bomUtils.d.ts +10 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -1
  127. package/types/lib/helpers/cbomutils.d.ts +3 -2
  128. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  129. package/types/lib/helpers/display.d.ts.map +1 -1
  130. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  131. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  132. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  133. package/types/lib/helpers/gtfobins.d.ts +8 -0
  134. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  135. package/types/lib/helpers/hbom.d.ts +49 -0
  136. package/types/lib/helpers/hbom.d.ts.map +1 -0
  137. package/types/lib/helpers/hbomAnalysis.d.ts +76 -0
  138. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  139. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  140. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  141. package/types/lib/helpers/hostTopology.d.ts +12 -0
  142. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  143. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  144. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  145. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  146. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  147. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  148. package/types/lib/helpers/plugins.d.ts +58 -0
  149. package/types/lib/helpers/plugins.d.ts.map +1 -0
  150. package/types/lib/helpers/protobom.d.ts +5 -4
  151. package/types/lib/helpers/protobom.d.ts.map +1 -1
  152. package/types/lib/helpers/protobomLoader.d.ts +17 -0
  153. package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
  154. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  155. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  156. package/types/lib/helpers/source.d.ts.map +1 -1
  157. package/types/lib/helpers/utils.d.ts +45 -8
  158. package/types/lib/helpers/utils.d.ts.map +1 -1
  159. package/types/lib/managers/binary.d.ts +5 -0
  160. package/types/lib/managers/binary.d.ts.map +1 -1
  161. package/types/lib/managers/docker.d.ts.map +1 -1
  162. package/types/lib/server/server.d.ts +2 -1
  163. package/types/lib/server/server.d.ts.map +1 -1
  164. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  165. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  166. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  167. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  168. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  170. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  171. package/types/lib/third-party/arborist/lib/node.d.ts +23 -0
  172. package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
  173. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  174. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  175. package/data/spdx-model-v3.0.1.jsonld +0 -15999
package/bin/cdxgen.js CHANGED
@@ -20,6 +20,7 @@ import { hideBin } from "yargs/helpers";
20
20
 
21
21
  import { createBom, submitBom } from "../lib/cli/index.js";
22
22
  import { signBom, verifyBom } from "../lib/helpers/bomSigner.js";
23
+ import { isCycloneDxBom } from "../lib/helpers/bomUtils.js";
23
24
  import {
24
25
  displaySelfThreatModel,
25
26
  printActivitySummary,
@@ -38,7 +39,14 @@ import {
38
39
  createOutputPlan,
39
40
  getOutputDirectory,
40
41
  } from "../lib/helpers/exportUtils.js";
42
+ import {
43
+ ensureNoMixedHbomProjectTypes,
44
+ ensureSupportedHbomSpecVersion,
45
+ hasHbomProjectType,
46
+ isHbomOnlyProjectTypes,
47
+ } from "../lib/helpers/hbom.js";
41
48
  import { TRACE_MODE, thoughtEnd, thoughtLog } from "../lib/helpers/logger.js";
49
+ import { importProtobomModule } from "../lib/helpers/protobomLoader.js";
42
50
  import {
43
51
  cleanupSourceDir,
44
52
  findGitRefForPurlVersion,
@@ -57,7 +65,9 @@ import {
57
65
  import {
58
66
  commandsExecuted,
59
67
  DEBUG_MODE,
68
+ getDefaultBomAuditCategories,
60
69
  getTmpDir,
70
+ isAllowedHttpHost,
61
71
  isBun,
62
72
  isDeno,
63
73
  isDryRun,
@@ -65,7 +75,9 @@ import {
65
75
  isNode,
66
76
  isSecureMode,
67
77
  isWin,
78
+ readEnvironmentVariable,
68
79
  recordActivity,
80
+ recordSensitiveFileRead,
69
81
  remoteHostsAccessed,
70
82
  retrieveCdxgenVersion,
71
83
  safeExistsSync,
@@ -73,6 +85,7 @@ import {
73
85
  safeWriteSync,
74
86
  setActivityContext,
75
87
  setDryRunMode,
88
+ shouldRunPredictiveBomAudit,
76
89
  toCamel,
77
90
  } from "../lib/helpers/utils.js";
78
91
  import { postProcess } from "../lib/stages/postgen/postgen.js";
@@ -118,6 +131,10 @@ for (const configPattern of configPaths) {
118
131
  }
119
132
 
120
133
  const _yargs = yargs(hideBin(process.argv));
134
+ const invokedCommandName = basename(process.argv[1] || "cdxgen").replace(
135
+ /\.(?:[cm]?js|exe)$/u,
136
+ "",
137
+ );
121
138
 
122
139
  const args = _yargs
123
140
  .env("CDXGEN")
@@ -244,6 +261,12 @@ const args = _yargs
244
261
  description:
245
262
  "Read-only mode. cdxgen only performs file reads and reports blocked writes, command execution, temp creation, network access, and submissions.",
246
263
  })
264
+ .option("include-runtime", {
265
+ type: "boolean",
266
+ default: false,
267
+ description:
268
+ "For HBOM runs, also collect OBOM runtime inventory and emit a merged host view with strict hardware/runtime topology links.",
269
+ })
247
270
  .option("activity-report", {
248
271
  choices: ["json", "jsonl"],
249
272
  description: "Render the activity report as JSON or JSON Lines.",
@@ -321,7 +344,7 @@ const args = _yargs
321
344
  description: "CycloneDX Specification version to use. Defaults to 1.7",
322
345
  default: 1.7,
323
346
  type: "number",
324
- choices: [1.4, 1.5, 1.6, 1.7],
347
+ choices: [1.4, 1.5, 1.6, 1.7, 2.0],
325
348
  })
326
349
  .option("filter", {
327
350
  description:
@@ -556,6 +579,7 @@ const args = _yargs
556
579
  "$0 -t java -t js .",
557
580
  "Generate a SBOM for Java and JavaScript in the current directory",
558
581
  ],
582
+ ["$0 -t hbom .", "Generate an HBOM for the current host"],
559
583
  [
560
584
  "$0 -t java --profile ml .",
561
585
  "Generate a Java SBOM for machine learning purposes.",
@@ -568,7 +592,7 @@ const args = _yargs
568
592
  ])
569
593
  .epilogue("for documentation, visit https://cdxgen.github.io/cdxgen")
570
594
  .config(config)
571
- .scriptName("cdxgen")
595
+ .scriptName(invokedCommandName || "cdxgen")
572
596
  .version(retrieveCdxgenVersion())
573
597
  .alias("v", "version")
574
598
  .help(false)
@@ -648,13 +672,13 @@ if (
648
672
  }
649
673
  }
650
674
  // Support for obom/cbom aliases
651
- if (process.argv[1].includes("obom") && !args.type) {
652
- args.type = "os";
675
+ if (invokedCommandName.includes("obom") && !args.type) {
676
+ args.type = ["os"];
653
677
  thoughtLog(
654
678
  "Ok, the user wants to generate an Operations Bill-of-Materials (OBOM).",
655
679
  );
656
680
  }
657
- if (process.argv[1].includes("spdxgen") && !args.format) {
681
+ if (invokedCommandName.includes("spdxgen") && !args.format) {
658
682
  args.format = "spdx";
659
683
  thoughtLog("Ok, defaulting the export format to SPDX.");
660
684
  }
@@ -699,19 +723,28 @@ for (const outputFile of Object.values(outputPlan.outputs)) {
699
723
  if (options.projectType && Array.isArray(options.projectType)) {
700
724
  options.projectType = Array.from(new Set(options.projectType));
701
725
  }
726
+ try {
727
+ ensureNoMixedHbomProjectTypes(options.projectType);
728
+ if (hasHbomProjectType(options.projectType)) {
729
+ ensureSupportedHbomSpecVersion(options.specVersion);
730
+ }
731
+ } catch (error) {
732
+ console.error(error.message);
733
+ process.exit(1);
734
+ }
702
735
  if (!options.projectType) {
703
736
  thoughtLog(
704
737
  "Ok, the user wants me to identify all the project types and generate a consolidated BOM document.",
705
738
  );
706
739
  }
707
740
  // Handle dedicated cbom and saasbom commands
708
- if (["cbom", "saasbom"].includes(process.argv[1])) {
709
- if (process.argv[1].includes("cbom")) {
741
+ if (["cbom", "saasbom"].includes(invokedCommandName)) {
742
+ if (invokedCommandName.includes("cbom")) {
710
743
  thoughtLog(
711
744
  "Ok, the user wants to generate Cryptographic Bill-of-Materials (CBOM).",
712
745
  );
713
746
  options.includeCrypto = true;
714
- } else if (process.argv[1].includes("saasbom")) {
747
+ } else if (invokedCommandName.includes("saasbom")) {
715
748
  thoughtLog(
716
749
  "Ok, the user wants to generate a Software as a Service Bill-of-Materials (SaaSBOM). I should carefully collect the services, endpoints, and data flows.",
717
750
  );
@@ -725,7 +758,7 @@ if (["cbom", "saasbom"].includes(process.argv[1])) {
725
758
  options.specVersion = 1.7;
726
759
  options.deep = true;
727
760
  }
728
- if (process.argv[1].includes("cdxgen-secure")) {
761
+ if (invokedCommandName.includes("cdxgen-secure")) {
729
762
  thoughtLog(
730
763
  "Ok, the user wants cdxgen to run in secure mode by default. Let's try and use the permissions api.",
731
764
  );
@@ -744,7 +777,16 @@ if (isDryRun) {
744
777
  if (options.standard) {
745
778
  options.specVersion = 1.7;
746
779
  }
747
- if (options.includeFormulation) {
780
+ const isHbomOnlyInvocation = isHbomOnlyProjectTypes(options.projectType);
781
+ if (options.includeFormulation && isHbomOnlyInvocation) {
782
+ thoughtLog(
783
+ "HBOM-only invocations do not benefit from formulation data. Let's ignore this option to keep the resulting document focused on hardware inventory.",
784
+ );
785
+ console.log(
786
+ "NOTE: Ignoring formulation collection for HBOM-only invocations because the resulting hardware BOM does not need workflow or dependency-tree enrichment.",
787
+ );
788
+ options.includeFormulation = false;
789
+ } else if (options.includeFormulation) {
748
790
  if (options.serverUrl) {
749
791
  thoughtLog(
750
792
  "Wait, the user specified a server URL and wants to include formulation data. Let's warn about accidentally disclosing sensitive data to a remote server.",
@@ -898,16 +940,32 @@ const applyAdvancedOptions = (options) => {
898
940
  );
899
941
  }
900
942
  if (options.bomAudit) {
901
- if (!options.includeFormulation) {
943
+ if (isHbomOnlyInvocation) {
944
+ thoughtLog(
945
+ "HBOM-only bom-audit runs should stay focused on hardware inventory. Skipping automatic formulation collection.",
946
+ );
947
+ } else if (!options.includeFormulation) {
902
948
  console.log(
903
949
  "NOTE: Automatically collecting formulation information. The section may include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution or LLM training.\n",
904
950
  );
951
+ options.includeFormulation = true;
905
952
  }
906
- options.includeFormulation = true;
907
953
  }
908
954
  return options;
909
955
  };
910
956
  applyAdvancedOptions(options);
957
+ if (options.bomAudit && !options.bomAuditCategories) {
958
+ const defaultBomAuditCategories = getDefaultBomAuditCategories(
959
+ options,
960
+ process.argv[1],
961
+ );
962
+ if (defaultBomAuditCategories) {
963
+ options.bomAuditCategories = defaultBomAuditCategories;
964
+ thoughtLog(
965
+ `Defaulting BOM audit categories to '${defaultBomAuditCategories}' for this OBOM or explicit os-only invocation.`,
966
+ );
967
+ }
968
+ }
911
969
 
912
970
  const envAuditFindings = auditEnvironment();
913
971
  if (options.envAudit) {
@@ -1063,11 +1121,27 @@ const checkPermissions = (filePath, options) => {
1063
1121
 
1064
1122
  const needsBomSigning = ({ generateKeyAndSign }) =>
1065
1123
  generateKeyAndSign ||
1066
- (process.env.SBOM_SIGN_ALGORITHM &&
1067
- process.env.SBOM_SIGN_ALGORITHM !== "none" &&
1068
- ((process.env.SBOM_SIGN_PRIVATE_KEY &&
1069
- safeExistsSync(process.env.SBOM_SIGN_PRIVATE_KEY)) ||
1070
- process.env.SBOM_SIGN_PRIVATE_KEY_BASE64));
1124
+ (() => {
1125
+ const sbomSignAlgorithm = readEnvironmentVariable("SBOM_SIGN_ALGORITHM");
1126
+ const sbomSignPrivateKey = readEnvironmentVariable(
1127
+ "SBOM_SIGN_PRIVATE_KEY",
1128
+ {
1129
+ sensitive: true,
1130
+ },
1131
+ );
1132
+ const sbomSignPrivateKeyBase64 = readEnvironmentVariable(
1133
+ "SBOM_SIGN_PRIVATE_KEY_BASE64",
1134
+ {
1135
+ sensitive: true,
1136
+ },
1137
+ );
1138
+ return (
1139
+ sbomSignAlgorithm &&
1140
+ sbomSignAlgorithm !== "none" &&
1141
+ ((sbomSignPrivateKey && safeExistsSync(sbomSignPrivateKey)) ||
1142
+ sbomSignPrivateKeyBase64)
1143
+ );
1144
+ })();
1071
1145
 
1072
1146
  const stringifyJson = (jsonPayload, jsonPretty) =>
1073
1147
  typeof jsonPayload === "string" || jsonPayload instanceof String
@@ -1096,7 +1170,21 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1096
1170
  });
1097
1171
  return jsonPayload;
1098
1172
  }
1099
- let alg = process.env.SBOM_SIGN_ALGORITHM || "RS512";
1173
+ const sbomSignAlgorithm = readEnvironmentVariable("SBOM_SIGN_ALGORITHM");
1174
+ const sbomSignPrivateKey = readEnvironmentVariable("SBOM_SIGN_PRIVATE_KEY", {
1175
+ sensitive: true,
1176
+ });
1177
+ const sbomSignPrivateKeyBase64 = readEnvironmentVariable(
1178
+ "SBOM_SIGN_PRIVATE_KEY_BASE64",
1179
+ {
1180
+ sensitive: true,
1181
+ },
1182
+ );
1183
+ const sbomSignPublicKey = readEnvironmentVariable("SBOM_SIGN_PUBLIC_KEY");
1184
+ const sbomSignPublicKeyBase64 = readEnvironmentVariable(
1185
+ "SBOM_SIGN_PUBLIC_KEY_BASE64",
1186
+ );
1187
+ let alg = sbomSignAlgorithm || "RS512";
1100
1188
  if (alg.includes("none")) {
1101
1189
  alg = "RS512";
1102
1190
  }
@@ -1134,31 +1222,25 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1134
1222
  privateKeyToUse = privateKey;
1135
1223
  jwkPublicKey = crypto.createPublicKey(publicKey).export({ format: "jwk" });
1136
1224
  } else {
1137
- if (process.env?.SBOM_SIGN_PRIVATE_KEY) {
1138
- privateKeyToUse = fs.readFileSync(
1139
- process.env.SBOM_SIGN_PRIVATE_KEY,
1140
- "utf8",
1141
- );
1142
- } else if (process.env?.SBOM_SIGN_PRIVATE_KEY_BASE64) {
1225
+ if (sbomSignPrivateKey) {
1226
+ recordSensitiveFileRead(sbomSignPrivateKey, {
1227
+ label: "SBOM signing private key",
1228
+ });
1229
+ privateKeyToUse = fs.readFileSync(sbomSignPrivateKey, "utf8");
1230
+ } else if (sbomSignPrivateKeyBase64) {
1143
1231
  privateKeyToUse = Buffer.from(
1144
- process.env.SBOM_SIGN_PRIVATE_KEY_BASE64,
1232
+ sbomSignPrivateKeyBase64,
1145
1233
  "base64",
1146
1234
  ).toString("utf8");
1147
1235
  }
1148
- if (
1149
- process.env.SBOM_SIGN_PUBLIC_KEY &&
1150
- safeExistsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
1151
- ) {
1236
+ if (sbomSignPublicKey && safeExistsSync(sbomSignPublicKey)) {
1152
1237
  jwkPublicKey = crypto
1153
- .createPublicKey(
1154
- fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8"),
1155
- )
1238
+ .createPublicKey(fs.readFileSync(sbomSignPublicKey, "utf8"))
1156
1239
  .export({ format: "jwk" });
1157
- } else if (process.env?.SBOM_SIGN_PUBLIC_KEY_BASE64) {
1158
- jwkPublicKey = Buffer.from(
1159
- process.env.SBOM_SIGN_PUBLIC_KEY_BASE64,
1160
- "base64",
1161
- ).toString("utf8");
1240
+ } else if (sbomSignPublicKeyBase64) {
1241
+ jwkPublicKey = Buffer.from(sbomSignPublicKeyBase64, "base64").toString(
1242
+ "utf8",
1243
+ );
1162
1244
  }
1163
1245
  }
1164
1246
  try {
@@ -1167,7 +1249,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1167
1249
  privateKey: privateKeyToUse,
1168
1250
  algorithm: alg,
1169
1251
  publicKeyJwk: jwkPublicKey,
1170
- mode: process.env.SBOM_SIGN_MODE || "replace",
1252
+ mode: readEnvironmentVariable("SBOM_SIGN_MODE") || "replace",
1171
1253
  signComponents: true,
1172
1254
  signServices: true,
1173
1255
  signAnnotations: true,
@@ -1345,10 +1427,16 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1345
1427
  cleanup = true;
1346
1428
  }
1347
1429
  setActivityContext({ sourcePath: srcDir });
1348
- prepareEnv(srcDir, options);
1430
+ if (!hasHbomProjectType(options.projectType)) {
1431
+ prepareEnv(srcDir, options);
1432
+ }
1349
1433
  thoughtLog("Getting ready to generate the BOM ⚡️.");
1350
1434
  const originalFetchPackageMetadata = process.env.CDXGEN_FETCH_PKG_METADATA;
1351
- if (options.bomAudit) {
1435
+ const shouldRunPredictiveAudit = shouldRunPredictiveBomAudit(
1436
+ options,
1437
+ process.argv[1],
1438
+ );
1439
+ if (options.bomAudit && shouldRunPredictiveAudit) {
1352
1440
  process.env.CDXGEN_FETCH_PKG_METADATA = "true";
1353
1441
  }
1354
1442
  let bomNSData;
@@ -1385,8 +1473,10 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1385
1473
  );
1386
1474
  const {
1387
1475
  auditBom,
1476
+ formatDryRunSupportSummary,
1388
1477
  formatAnnotations,
1389
1478
  formatConsoleOutput,
1479
+ getBomAuditDryRunSupportSummary,
1390
1480
  hasCriticalFindings,
1391
1481
  } = await import("../lib/stages/postgen/auditBom.js");
1392
1482
  thoughtLog("Let's run security audit...");
@@ -1396,6 +1486,15 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1396
1486
  } else if (DEBUG_MODE) {
1397
1487
  console.log("BOM audit: No findings");
1398
1488
  }
1489
+ if (isDryRun) {
1490
+ const dryRunSupportSummary =
1491
+ await getBomAuditDryRunSupportSummary(options);
1492
+ const dryRunSupportMessage =
1493
+ formatDryRunSupportSummary(dryRunSupportSummary);
1494
+ if (dryRunSupportMessage) {
1495
+ console.log(dryRunSupportMessage);
1496
+ }
1497
+ }
1399
1498
  if (postAuditFindings.length && options.specVersion >= 1.4) {
1400
1499
  bomNSData.bomJson.annotations = [
1401
1500
  ...(bomNSData.bomJson.annotations || []),
@@ -1416,37 +1515,21 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1416
1515
  process.exit(1);
1417
1516
  }
1418
1517
 
1419
- thoughtLog("Let's run predictive dependency audit...");
1420
- const progressTracker = createProgressTracker();
1421
- const predictiveAuditScope =
1422
- options.bomAuditScope === "required" ? "required" : undefined;
1423
- const predictiveAuditTrusted = options.bomAuditOnlyTrusted
1424
- ? "only"
1425
- : options.bomAuditIncludeTrusted
1426
- ? "include"
1427
- : undefined;
1428
- const requiredAuditTargetCount = collectAuditTargets(
1429
- [
1430
- {
1431
- bomJson: bomNSData.bomJson,
1432
- source: filePath,
1433
- },
1434
- ],
1435
- {
1436
- scope: "required",
1437
- trusted: predictiveAuditTrusted,
1438
- },
1439
- ).targets.length;
1440
- const predictiveAuditMaxTargets =
1441
- typeof options.bomAuditMaxTargets === "number" &&
1442
- options.bomAuditMaxTargets > 0
1443
- ? options.bomAuditMaxTargets
1444
- : predictiveAuditScope === "required"
1445
- ? undefined
1446
- : Math.max(50, requiredAuditTargetCount);
1447
- let predictiveReport;
1448
- try {
1449
- predictiveReport = await runAuditFromBoms(
1518
+ if (!shouldRunPredictiveAudit) {
1519
+ thoughtLog(
1520
+ "Skipping predictive dependency audit for this OBOM or explicit os-only invocation.",
1521
+ );
1522
+ } else {
1523
+ thoughtLog("Let's run predictive dependency audit...");
1524
+ const progressTracker = createProgressTracker();
1525
+ const predictiveAuditScope =
1526
+ options.bomAuditScope === "required" ? "required" : undefined;
1527
+ const predictiveAuditTrusted = options.bomAuditOnlyTrusted
1528
+ ? "only"
1529
+ : options.bomAuditIncludeTrusted
1530
+ ? "include"
1531
+ : undefined;
1532
+ const requiredAuditTargetCount = collectAuditTargets(
1450
1533
  [
1451
1534
  {
1452
1535
  bomJson: bomNSData.bomJson,
@@ -1454,66 +1537,90 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1454
1537
  },
1455
1538
  ],
1456
1539
  {
1457
- categories: options.bomAuditCategories
1458
- ? options.bomAuditCategories
1459
- .split(",")
1460
- .map((category) => category.trim())
1461
- .filter(Boolean)
1462
- : undefined,
1463
- failSeverity: options.bomAuditFailSeverity,
1464
- maxTargets: predictiveAuditMaxTargets,
1465
- minSeverity: options.bomAuditMinSeverity,
1466
- onProgress: progressTracker.onProgress,
1467
- scope: predictiveAuditScope,
1540
+ scope: "required",
1468
1541
  trusted: predictiveAuditTrusted,
1469
- trustedSelectionHelp:
1470
- "Use --bom-audit-include-trusted to include them or --bom-audit-only-trusted to audit just those packages.",
1471
1542
  },
1472
- );
1473
- } finally {
1474
- progressTracker.stop();
1475
- }
1476
- if (predictiveReport.summary.totalTargets > 0) {
1477
- process.stderr.write(
1478
- renderConsoleReport(predictiveReport, {
1543
+ ).targets.length;
1544
+ const predictiveAuditMaxTargets =
1545
+ typeof options.bomAuditMaxTargets === "number" &&
1546
+ options.bomAuditMaxTargets > 0
1547
+ ? options.bomAuditMaxTargets
1548
+ : predictiveAuditScope === "required"
1549
+ ? undefined
1550
+ : Math.max(50, requiredAuditTargetCount);
1551
+ let predictiveReport;
1552
+ try {
1553
+ predictiveReport = await runAuditFromBoms(
1554
+ [
1555
+ {
1556
+ bomJson: bomNSData.bomJson,
1557
+ source: filePath,
1558
+ },
1559
+ ],
1560
+ {
1561
+ categories: options.bomAuditCategories
1562
+ ? options.bomAuditCategories
1563
+ .split(",")
1564
+ .map((category) => category.trim())
1565
+ .filter(Boolean)
1566
+ : undefined,
1567
+ failSeverity: options.bomAuditFailSeverity,
1568
+ maxTargets: predictiveAuditMaxTargets,
1569
+ minSeverity: options.bomAuditMinSeverity,
1570
+ onProgress: progressTracker.onProgress,
1571
+ scope: predictiveAuditScope,
1572
+ trusted: predictiveAuditTrusted,
1573
+ trustedSelectionHelp:
1574
+ "Use --bom-audit-include-trusted to include them or --bom-audit-only-trusted to audit just those packages.",
1575
+ },
1576
+ );
1577
+ } finally {
1578
+ progressTracker.stop();
1579
+ }
1580
+ if (predictiveReport.summary.totalTargets > 0) {
1581
+ process.stderr.write(
1582
+ renderConsoleReport(predictiveReport, {
1583
+ minSeverity: options.bomAuditMinSeverity,
1584
+ }),
1585
+ );
1586
+ } else if (DEBUG_MODE) {
1587
+ console.log(
1588
+ "Predictive BOM audit: No supported npm/PyPI targets found",
1589
+ );
1590
+ }
1591
+ const predictiveAnnotations = formatPredictiveAnnotations(
1592
+ predictiveReport,
1593
+ bomNSData.bomJson,
1594
+ {
1479
1595
  minSeverity: options.bomAuditMinSeverity,
1480
- }),
1596
+ },
1481
1597
  );
1482
- } else if (DEBUG_MODE) {
1483
- console.log("Predictive BOM audit: No supported npm/PyPI targets found");
1484
- }
1485
- const predictiveAnnotations = formatPredictiveAnnotations(
1486
- predictiveReport,
1487
- bomNSData.bomJson,
1488
- {
1598
+ if (predictiveAnnotations.length && options.specVersion >= 1.4) {
1599
+ bomNSData.bomJson.annotations = [
1600
+ ...(bomNSData.bomJson.annotations || []),
1601
+ ...predictiveAnnotations,
1602
+ ];
1603
+ thoughtLog(
1604
+ `Embedded ${predictiveAnnotations.length} predictive audit annotations`,
1605
+ );
1606
+ }
1607
+ const predictiveResult = finalizeAuditReport(predictiveReport, {
1608
+ failSeverity: options.bomAuditFailSeverity,
1489
1609
  minSeverity: options.bomAuditMinSeverity,
1490
- },
1491
- );
1492
- if (predictiveAnnotations.length && options.specVersion >= 1.4) {
1493
- bomNSData.bomJson.annotations = [
1494
- ...(bomNSData.bomJson.annotations || []),
1495
- ...predictiveAnnotations,
1496
- ];
1497
- thoughtLog(
1498
- `Embedded ${predictiveAnnotations.length} predictive audit annotations`,
1499
- );
1500
- }
1501
- const predictiveResult = finalizeAuditReport(predictiveReport, {
1502
- failSeverity: options.bomAuditFailSeverity,
1503
- minSeverity: options.bomAuditMinSeverity,
1504
- report: "console",
1505
- });
1506
- if (isSecureMode && predictiveResult.exitCode === 3) {
1507
- console.error(
1508
- "\nSecure mode: Predictive audit findings exceeded the configured threshold.",
1509
- );
1510
- console.error(
1511
- "Review findings above or adjust --bom-audit-fail-severity to proceed.",
1512
- );
1513
- if (cleanup) {
1514
- cleanupSourceDir(srcDir);
1610
+ report: "console",
1611
+ });
1612
+ if (isSecureMode && predictiveResult.exitCode === 3) {
1613
+ console.error(
1614
+ "\nSecure mode: Predictive audit findings exceeded the configured threshold.",
1615
+ );
1616
+ console.error(
1617
+ "Review findings above or adjust --bom-audit-fail-severity to proceed.",
1618
+ );
1619
+ if (cleanup) {
1620
+ cleanupSourceDir(srcDir);
1621
+ }
1622
+ process.exit(1);
1515
1623
  }
1516
- process.exit(1);
1517
1624
  }
1518
1625
  }
1519
1626
  let internalCycloneDxInputPath = outputPlan.outputs.cyclonedx;
@@ -1609,7 +1716,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1609
1716
  if (
1610
1717
  outputPlan.formats.has("spdx") &&
1611
1718
  bomNSData?.bomJson &&
1612
- bomNSData?.bomJson?.bomFormat === "CycloneDX"
1719
+ isCycloneDxBom(bomNSData.bomJson)
1613
1720
  ) {
1614
1721
  thoughtLog(
1615
1722
  "Preparing the SPDX 3.0.1 export from the validated CycloneDX BOM.",
@@ -1684,6 +1791,27 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1684
1791
  // Automatically submit the bom data
1685
1792
  // biome-ignore lint/suspicious/noDoubleEquals: yargs passes true for empty values
1686
1793
  if (options.serverUrl && options.serverUrl != true && options.apiKey) {
1794
+ if (isSecureMode) {
1795
+ let serverHostname;
1796
+ try {
1797
+ serverHostname = new URL(options.serverUrl).hostname;
1798
+ } catch (err) {
1799
+ console.log("Invalid Dependency-Track server URL", err);
1800
+ process.exit(1);
1801
+ }
1802
+ if (!isAllowedHttpHost(serverHostname)) {
1803
+ recordActivity({
1804
+ kind: "submit",
1805
+ reason: "The URL host is not allowed as per the allowlist.",
1806
+ status: "blocked",
1807
+ target: options.serverUrl,
1808
+ });
1809
+ console.log(
1810
+ `Dependency-Track server host '${serverHostname}' is not allowed by CDXGEN_ALLOWED_HOSTS.`,
1811
+ );
1812
+ process.exit(1);
1813
+ }
1814
+ }
1687
1815
  if (isDryRun) {
1688
1816
  recordActivity({
1689
1817
  kind: "submit",
@@ -1718,7 +1846,22 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1718
1846
  target: options.protoBinFile,
1719
1847
  });
1720
1848
  } else {
1721
- const protobomModule = await import("../lib/helpers/protobom.js");
1849
+ const protobomModule = await importProtobomModule(
1850
+ invokedCommandName || "cdxgen",
1851
+ "protobuf export",
1852
+ );
1853
+ try {
1854
+ protobomModule.assertProtoSupportedSpecVersion(
1855
+ bomNSData?.bomJson?.specVersion || options.specVersion,
1856
+ "protobuf export",
1857
+ );
1858
+ } catch (error) {
1859
+ console.error(error.message);
1860
+ if (cleanup) {
1861
+ cleanupSourceDir(srcDir);
1862
+ }
1863
+ process.exit(1);
1864
+ }
1722
1865
  protobomModule.writeBinary(bomNSData.bomJson, options.protoBinFile);
1723
1866
  thoughtLog("BOM file is also available in .proto format!");
1724
1867
  }