@cyclonedx/cdxgen 12.3.3 → 12.4.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 (157) hide show
  1. package/README.md +64 -22
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +238 -116
  4. package/bin/convert.js +28 -13
  5. package/bin/hbom.js +490 -0
  6. package/bin/repl.js +580 -29
  7. package/bin/validate.js +34 -4
  8. package/bin/verify.js +40 -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/predictive-audit-allowlist.json +11 -0
  13. package/data/queries-darwin.json +12 -1
  14. package/data/queries-win.json +7 -1
  15. package/data/queries.json +39 -2
  16. package/data/rules/ai-agent-governance.yaml +16 -0
  17. package/data/rules/asar-archives.yaml +150 -0
  18. package/data/rules/chrome-extensions.yaml +8 -0
  19. package/data/rules/ci-permissions.yaml +42 -18
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +11 -0
  22. package/data/rules/hbom-compliance.yaml +325 -0
  23. package/data/rules/hbom-performance.yaml +307 -0
  24. package/data/rules/hbom-security.yaml +248 -0
  25. package/data/rules/host-topology.yaml +165 -0
  26. package/data/rules/mcp-servers.yaml +18 -3
  27. package/data/rules/obom-runtime.yaml +907 -22
  28. package/data/rules/package-integrity.yaml +14 -0
  29. package/data/rules/rootfs-hardening.yaml +179 -0
  30. package/data/rules/vscode-extensions.yaml +9 -0
  31. package/lib/audit/index.js +209 -8
  32. package/lib/audit/index.poku.js +332 -0
  33. package/lib/audit/reporters.js +222 -0
  34. package/lib/audit/targets.js +146 -1
  35. package/lib/audit/targets.poku.js +186 -0
  36. package/lib/cli/asar.poku.js +328 -0
  37. package/lib/cli/index.js +506 -88
  38. package/lib/cli/index.poku.js +1352 -212
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/analyzer.js +1406 -29
  41. package/lib/helpers/analyzer.poku.js +342 -0
  42. package/lib/helpers/analyzerScope.js +712 -0
  43. package/lib/helpers/asarutils.js +1556 -0
  44. package/lib/helpers/asarutils.poku.js +443 -0
  45. package/lib/helpers/auditCategories.js +12 -0
  46. package/lib/helpers/auditCategories.poku.js +32 -0
  47. package/lib/helpers/cbomutils.js +271 -1
  48. package/lib/helpers/cbomutils.poku.js +248 -5
  49. package/lib/helpers/display.js +291 -1
  50. package/lib/helpers/display.poku.js +149 -0
  51. package/lib/helpers/evidenceUtils.js +58 -0
  52. package/lib/helpers/evidenceUtils.poku.js +54 -0
  53. package/lib/helpers/exportUtils.js +9 -0
  54. package/lib/helpers/gtfobins.js +142 -8
  55. package/lib/helpers/gtfobins.poku.js +24 -1
  56. package/lib/helpers/hbom.js +710 -0
  57. package/lib/helpers/hbom.poku.js +496 -0
  58. package/lib/helpers/hbomAnalysis.js +268 -0
  59. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  60. package/lib/helpers/hbomLoader.js +35 -0
  61. package/lib/helpers/hostTopology.js +803 -0
  62. package/lib/helpers/hostTopology.poku.js +363 -0
  63. package/lib/helpers/inventoryStats.js +69 -0
  64. package/lib/helpers/inventoryStats.poku.js +86 -0
  65. package/lib/helpers/lolbas.js +19 -1
  66. package/lib/helpers/lolbas.poku.js +23 -0
  67. package/lib/helpers/osqueryTransform.js +47 -0
  68. package/lib/helpers/osqueryTransform.poku.js +47 -0
  69. package/lib/helpers/plugins.js +349 -0
  70. package/lib/helpers/plugins.poku.js +57 -0
  71. package/lib/helpers/protobom.js +156 -45
  72. package/lib/helpers/protobom.poku.js +140 -5
  73. package/lib/helpers/remote/dependency-track.js +36 -3
  74. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  75. package/lib/helpers/source.js +24 -0
  76. package/lib/helpers/source.poku.js +32 -0
  77. package/lib/helpers/utils.js +1438 -93
  78. package/lib/helpers/utils.poku.js +846 -4
  79. package/lib/managers/binary.e2e.poku.js +367 -0
  80. package/lib/managers/binary.js +2293 -353
  81. package/lib/managers/binary.poku.js +1699 -1
  82. package/lib/managers/docker.js +201 -79
  83. package/lib/managers/docker.poku.js +337 -12
  84. package/lib/server/server.js +2 -27
  85. package/lib/stages/postgen/annotator.js +38 -0
  86. package/lib/stages/postgen/annotator.poku.js +107 -1
  87. package/lib/stages/postgen/auditBom.js +121 -18
  88. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  89. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  90. package/lib/stages/postgen/postgen.js +192 -1
  91. package/lib/stages/postgen/postgen.poku.js +321 -0
  92. package/lib/stages/postgen/ruleEngine.js +116 -0
  93. package/lib/stages/pregen/envAudit.js +14 -3
  94. package/package.json +23 -21
  95. package/types/bin/hbom.d.ts +3 -0
  96. package/types/bin/hbom.d.ts.map +1 -0
  97. package/types/bin/repl.d.ts.map +1 -1
  98. package/types/lib/audit/index.d.ts +44 -0
  99. package/types/lib/audit/index.d.ts.map +1 -1
  100. package/types/lib/audit/reporters.d.ts +16 -0
  101. package/types/lib/audit/reporters.d.ts.map +1 -1
  102. package/types/lib/audit/targets.d.ts.map +1 -1
  103. package/types/lib/cli/index.d.ts +16 -0
  104. package/types/lib/cli/index.d.ts.map +1 -1
  105. package/types/lib/evinser/evinser.d.ts +4 -0
  106. package/types/lib/evinser/evinser.d.ts.map +1 -1
  107. package/types/lib/helpers/analyzer.d.ts +33 -0
  108. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  109. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  110. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  111. package/types/lib/helpers/asarutils.d.ts +34 -0
  112. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  113. package/types/lib/helpers/auditCategories.d.ts +5 -0
  114. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  115. package/types/lib/helpers/cbomutils.d.ts +3 -2
  116. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  117. package/types/lib/helpers/display.d.ts.map +1 -1
  118. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  119. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  120. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  121. package/types/lib/helpers/gtfobins.d.ts +8 -0
  122. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  123. package/types/lib/helpers/hbom.d.ts +49 -0
  124. package/types/lib/helpers/hbom.d.ts.map +1 -0
  125. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  126. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  127. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  128. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  129. package/types/lib/helpers/hostTopology.d.ts +12 -0
  130. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  131. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  132. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  133. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  134. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  135. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  136. package/types/lib/helpers/plugins.d.ts +58 -0
  137. package/types/lib/helpers/plugins.d.ts.map +1 -0
  138. package/types/lib/helpers/protobom.d.ts +3 -4
  139. package/types/lib/helpers/protobom.d.ts.map +1 -1
  140. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  141. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  142. package/types/lib/helpers/source.d.ts.map +1 -1
  143. package/types/lib/helpers/utils.d.ts +45 -8
  144. package/types/lib/helpers/utils.d.ts.map +1 -1
  145. package/types/lib/managers/binary.d.ts +5 -0
  146. package/types/lib/managers/binary.d.ts.map +1 -1
  147. package/types/lib/managers/docker.d.ts.map +1 -1
  148. package/types/lib/server/server.d.ts +2 -1
  149. package/types/lib/server/server.d.ts.map +1 -1
  150. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  151. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  152. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  153. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  154. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  155. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  156. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  157. package/data/spdx-model-v3.0.1.jsonld +0 -15999
package/bin/cdxgen.js CHANGED
@@ -38,6 +38,12 @@ import {
38
38
  createOutputPlan,
39
39
  getOutputDirectory,
40
40
  } from "../lib/helpers/exportUtils.js";
41
+ import {
42
+ ensureNoMixedHbomProjectTypes,
43
+ ensureSupportedHbomSpecVersion,
44
+ hasHbomProjectType,
45
+ isHbomOnlyProjectTypes,
46
+ } from "../lib/helpers/hbom.js";
41
47
  import { TRACE_MODE, thoughtEnd, thoughtLog } from "../lib/helpers/logger.js";
42
48
  import {
43
49
  cleanupSourceDir,
@@ -57,7 +63,9 @@ import {
57
63
  import {
58
64
  commandsExecuted,
59
65
  DEBUG_MODE,
66
+ getDefaultBomAuditCategories,
60
67
  getTmpDir,
68
+ isAllowedHttpHost,
61
69
  isBun,
62
70
  isDeno,
63
71
  isDryRun,
@@ -65,7 +73,9 @@ import {
65
73
  isNode,
66
74
  isSecureMode,
67
75
  isWin,
76
+ readEnvironmentVariable,
68
77
  recordActivity,
78
+ recordSensitiveFileRead,
69
79
  remoteHostsAccessed,
70
80
  retrieveCdxgenVersion,
71
81
  safeExistsSync,
@@ -73,6 +83,7 @@ import {
73
83
  safeWriteSync,
74
84
  setActivityContext,
75
85
  setDryRunMode,
86
+ shouldRunPredictiveBomAudit,
76
87
  toCamel,
77
88
  } from "../lib/helpers/utils.js";
78
89
  import { postProcess } from "../lib/stages/postgen/postgen.js";
@@ -244,6 +255,12 @@ const args = _yargs
244
255
  description:
245
256
  "Read-only mode. cdxgen only performs file reads and reports blocked writes, command execution, temp creation, network access, and submissions.",
246
257
  })
258
+ .option("include-runtime", {
259
+ type: "boolean",
260
+ default: false,
261
+ description:
262
+ "For HBOM runs, also collect OBOM runtime inventory and emit a merged host view with strict hardware/runtime topology links.",
263
+ })
247
264
  .option("activity-report", {
248
265
  choices: ["json", "jsonl"],
249
266
  description: "Render the activity report as JSON or JSON Lines.",
@@ -556,6 +573,7 @@ const args = _yargs
556
573
  "$0 -t java -t js .",
557
574
  "Generate a SBOM for Java and JavaScript in the current directory",
558
575
  ],
576
+ ["$0 -t hbom .", "Generate an HBOM for the current host"],
559
577
  [
560
578
  "$0 -t java --profile ml .",
561
579
  "Generate a Java SBOM for machine learning purposes.",
@@ -699,6 +717,15 @@ for (const outputFile of Object.values(outputPlan.outputs)) {
699
717
  if (options.projectType && Array.isArray(options.projectType)) {
700
718
  options.projectType = Array.from(new Set(options.projectType));
701
719
  }
720
+ try {
721
+ ensureNoMixedHbomProjectTypes(options.projectType);
722
+ if (hasHbomProjectType(options.projectType)) {
723
+ ensureSupportedHbomSpecVersion(options.specVersion);
724
+ }
725
+ } catch (error) {
726
+ console.error(error.message);
727
+ process.exit(1);
728
+ }
702
729
  if (!options.projectType) {
703
730
  thoughtLog(
704
731
  "Ok, the user wants me to identify all the project types and generate a consolidated BOM document.",
@@ -744,7 +771,16 @@ if (isDryRun) {
744
771
  if (options.standard) {
745
772
  options.specVersion = 1.7;
746
773
  }
747
- if (options.includeFormulation) {
774
+ const isHbomOnlyInvocation = isHbomOnlyProjectTypes(options.projectType);
775
+ if (options.includeFormulation && isHbomOnlyInvocation) {
776
+ thoughtLog(
777
+ "HBOM-only invocations do not benefit from formulation data. Let's ignore this option to keep the resulting document focused on hardware inventory.",
778
+ );
779
+ console.log(
780
+ "NOTE: Ignoring formulation collection for HBOM-only invocations because the resulting hardware BOM does not need workflow or dependency-tree enrichment.",
781
+ );
782
+ options.includeFormulation = false;
783
+ } else if (options.includeFormulation) {
748
784
  if (options.serverUrl) {
749
785
  thoughtLog(
750
786
  "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 +934,32 @@ const applyAdvancedOptions = (options) => {
898
934
  );
899
935
  }
900
936
  if (options.bomAudit) {
901
- if (!options.includeFormulation) {
937
+ if (isHbomOnlyInvocation) {
938
+ thoughtLog(
939
+ "HBOM-only bom-audit runs should stay focused on hardware inventory. Skipping automatic formulation collection.",
940
+ );
941
+ } else if (!options.includeFormulation) {
902
942
  console.log(
903
943
  "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
944
  );
945
+ options.includeFormulation = true;
905
946
  }
906
- options.includeFormulation = true;
907
947
  }
908
948
  return options;
909
949
  };
910
950
  applyAdvancedOptions(options);
951
+ if (options.bomAudit && !options.bomAuditCategories) {
952
+ const defaultBomAuditCategories = getDefaultBomAuditCategories(
953
+ options,
954
+ process.argv[1],
955
+ );
956
+ if (defaultBomAuditCategories) {
957
+ options.bomAuditCategories = defaultBomAuditCategories;
958
+ thoughtLog(
959
+ `Defaulting BOM audit categories to '${defaultBomAuditCategories}' for this OBOM or explicit os-only invocation.`,
960
+ );
961
+ }
962
+ }
911
963
 
912
964
  const envAuditFindings = auditEnvironment();
913
965
  if (options.envAudit) {
@@ -1063,11 +1115,27 @@ const checkPermissions = (filePath, options) => {
1063
1115
 
1064
1116
  const needsBomSigning = ({ generateKeyAndSign }) =>
1065
1117
  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));
1118
+ (() => {
1119
+ const sbomSignAlgorithm = readEnvironmentVariable("SBOM_SIGN_ALGORITHM");
1120
+ const sbomSignPrivateKey = readEnvironmentVariable(
1121
+ "SBOM_SIGN_PRIVATE_KEY",
1122
+ {
1123
+ sensitive: true,
1124
+ },
1125
+ );
1126
+ const sbomSignPrivateKeyBase64 = readEnvironmentVariable(
1127
+ "SBOM_SIGN_PRIVATE_KEY_BASE64",
1128
+ {
1129
+ sensitive: true,
1130
+ },
1131
+ );
1132
+ return (
1133
+ sbomSignAlgorithm &&
1134
+ sbomSignAlgorithm !== "none" &&
1135
+ ((sbomSignPrivateKey && safeExistsSync(sbomSignPrivateKey)) ||
1136
+ sbomSignPrivateKeyBase64)
1137
+ );
1138
+ })();
1071
1139
 
1072
1140
  const stringifyJson = (jsonPayload, jsonPretty) =>
1073
1141
  typeof jsonPayload === "string" || jsonPayload instanceof String
@@ -1096,7 +1164,21 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1096
1164
  });
1097
1165
  return jsonPayload;
1098
1166
  }
1099
- let alg = process.env.SBOM_SIGN_ALGORITHM || "RS512";
1167
+ const sbomSignAlgorithm = readEnvironmentVariable("SBOM_SIGN_ALGORITHM");
1168
+ const sbomSignPrivateKey = readEnvironmentVariable("SBOM_SIGN_PRIVATE_KEY", {
1169
+ sensitive: true,
1170
+ });
1171
+ const sbomSignPrivateKeyBase64 = readEnvironmentVariable(
1172
+ "SBOM_SIGN_PRIVATE_KEY_BASE64",
1173
+ {
1174
+ sensitive: true,
1175
+ },
1176
+ );
1177
+ const sbomSignPublicKey = readEnvironmentVariable("SBOM_SIGN_PUBLIC_KEY");
1178
+ const sbomSignPublicKeyBase64 = readEnvironmentVariable(
1179
+ "SBOM_SIGN_PUBLIC_KEY_BASE64",
1180
+ );
1181
+ let alg = sbomSignAlgorithm || "RS512";
1100
1182
  if (alg.includes("none")) {
1101
1183
  alg = "RS512";
1102
1184
  }
@@ -1134,31 +1216,25 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1134
1216
  privateKeyToUse = privateKey;
1135
1217
  jwkPublicKey = crypto.createPublicKey(publicKey).export({ format: "jwk" });
1136
1218
  } 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) {
1219
+ if (sbomSignPrivateKey) {
1220
+ recordSensitiveFileRead(sbomSignPrivateKey, {
1221
+ label: "SBOM signing private key",
1222
+ });
1223
+ privateKeyToUse = fs.readFileSync(sbomSignPrivateKey, "utf8");
1224
+ } else if (sbomSignPrivateKeyBase64) {
1143
1225
  privateKeyToUse = Buffer.from(
1144
- process.env.SBOM_SIGN_PRIVATE_KEY_BASE64,
1226
+ sbomSignPrivateKeyBase64,
1145
1227
  "base64",
1146
1228
  ).toString("utf8");
1147
1229
  }
1148
- if (
1149
- process.env.SBOM_SIGN_PUBLIC_KEY &&
1150
- safeExistsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
1151
- ) {
1230
+ if (sbomSignPublicKey && safeExistsSync(sbomSignPublicKey)) {
1152
1231
  jwkPublicKey = crypto
1153
- .createPublicKey(
1154
- fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8"),
1155
- )
1232
+ .createPublicKey(fs.readFileSync(sbomSignPublicKey, "utf8"))
1156
1233
  .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");
1234
+ } else if (sbomSignPublicKeyBase64) {
1235
+ jwkPublicKey = Buffer.from(sbomSignPublicKeyBase64, "base64").toString(
1236
+ "utf8",
1237
+ );
1162
1238
  }
1163
1239
  }
1164
1240
  try {
@@ -1167,7 +1243,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1167
1243
  privateKey: privateKeyToUse,
1168
1244
  algorithm: alg,
1169
1245
  publicKeyJwk: jwkPublicKey,
1170
- mode: process.env.SBOM_SIGN_MODE || "replace",
1246
+ mode: readEnvironmentVariable("SBOM_SIGN_MODE") || "replace",
1171
1247
  signComponents: true,
1172
1248
  signServices: true,
1173
1249
  signAnnotations: true,
@@ -1345,10 +1421,16 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1345
1421
  cleanup = true;
1346
1422
  }
1347
1423
  setActivityContext({ sourcePath: srcDir });
1348
- prepareEnv(srcDir, options);
1424
+ if (!hasHbomProjectType(options.projectType)) {
1425
+ prepareEnv(srcDir, options);
1426
+ }
1349
1427
  thoughtLog("Getting ready to generate the BOM ⚡️.");
1350
1428
  const originalFetchPackageMetadata = process.env.CDXGEN_FETCH_PKG_METADATA;
1351
- if (options.bomAudit) {
1429
+ const shouldRunPredictiveAudit = shouldRunPredictiveBomAudit(
1430
+ options,
1431
+ process.argv[1],
1432
+ );
1433
+ if (options.bomAudit && shouldRunPredictiveAudit) {
1352
1434
  process.env.CDXGEN_FETCH_PKG_METADATA = "true";
1353
1435
  }
1354
1436
  let bomNSData;
@@ -1385,8 +1467,10 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1385
1467
  );
1386
1468
  const {
1387
1469
  auditBom,
1470
+ formatDryRunSupportSummary,
1388
1471
  formatAnnotations,
1389
1472
  formatConsoleOutput,
1473
+ getBomAuditDryRunSupportSummary,
1390
1474
  hasCriticalFindings,
1391
1475
  } = await import("../lib/stages/postgen/auditBom.js");
1392
1476
  thoughtLog("Let's run security audit...");
@@ -1396,6 +1480,15 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1396
1480
  } else if (DEBUG_MODE) {
1397
1481
  console.log("BOM audit: No findings");
1398
1482
  }
1483
+ if (isDryRun) {
1484
+ const dryRunSupportSummary =
1485
+ await getBomAuditDryRunSupportSummary(options);
1486
+ const dryRunSupportMessage =
1487
+ formatDryRunSupportSummary(dryRunSupportSummary);
1488
+ if (dryRunSupportMessage) {
1489
+ console.log(dryRunSupportMessage);
1490
+ }
1491
+ }
1399
1492
  if (postAuditFindings.length && options.specVersion >= 1.4) {
1400
1493
  bomNSData.bomJson.annotations = [
1401
1494
  ...(bomNSData.bomJson.annotations || []),
@@ -1416,37 +1509,21 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1416
1509
  process.exit(1);
1417
1510
  }
1418
1511
 
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(
1512
+ if (!shouldRunPredictiveAudit) {
1513
+ thoughtLog(
1514
+ "Skipping predictive dependency audit for this OBOM or explicit os-only invocation.",
1515
+ );
1516
+ } else {
1517
+ thoughtLog("Let's run predictive dependency audit...");
1518
+ const progressTracker = createProgressTracker();
1519
+ const predictiveAuditScope =
1520
+ options.bomAuditScope === "required" ? "required" : undefined;
1521
+ const predictiveAuditTrusted = options.bomAuditOnlyTrusted
1522
+ ? "only"
1523
+ : options.bomAuditIncludeTrusted
1524
+ ? "include"
1525
+ : undefined;
1526
+ const requiredAuditTargetCount = collectAuditTargets(
1450
1527
  [
1451
1528
  {
1452
1529
  bomJson: bomNSData.bomJson,
@@ -1454,66 +1531,90 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1454
1531
  },
1455
1532
  ],
1456
1533
  {
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,
1534
+ scope: "required",
1468
1535
  trusted: predictiveAuditTrusted,
1469
- trustedSelectionHelp:
1470
- "Use --bom-audit-include-trusted to include them or --bom-audit-only-trusted to audit just those packages.",
1471
1536
  },
1472
- );
1473
- } finally {
1474
- progressTracker.stop();
1475
- }
1476
- if (predictiveReport.summary.totalTargets > 0) {
1477
- process.stderr.write(
1478
- renderConsoleReport(predictiveReport, {
1537
+ ).targets.length;
1538
+ const predictiveAuditMaxTargets =
1539
+ typeof options.bomAuditMaxTargets === "number" &&
1540
+ options.bomAuditMaxTargets > 0
1541
+ ? options.bomAuditMaxTargets
1542
+ : predictiveAuditScope === "required"
1543
+ ? undefined
1544
+ : Math.max(50, requiredAuditTargetCount);
1545
+ let predictiveReport;
1546
+ try {
1547
+ predictiveReport = await runAuditFromBoms(
1548
+ [
1549
+ {
1550
+ bomJson: bomNSData.bomJson,
1551
+ source: filePath,
1552
+ },
1553
+ ],
1554
+ {
1555
+ categories: options.bomAuditCategories
1556
+ ? options.bomAuditCategories
1557
+ .split(",")
1558
+ .map((category) => category.trim())
1559
+ .filter(Boolean)
1560
+ : undefined,
1561
+ failSeverity: options.bomAuditFailSeverity,
1562
+ maxTargets: predictiveAuditMaxTargets,
1563
+ minSeverity: options.bomAuditMinSeverity,
1564
+ onProgress: progressTracker.onProgress,
1565
+ scope: predictiveAuditScope,
1566
+ trusted: predictiveAuditTrusted,
1567
+ trustedSelectionHelp:
1568
+ "Use --bom-audit-include-trusted to include them or --bom-audit-only-trusted to audit just those packages.",
1569
+ },
1570
+ );
1571
+ } finally {
1572
+ progressTracker.stop();
1573
+ }
1574
+ if (predictiveReport.summary.totalTargets > 0) {
1575
+ process.stderr.write(
1576
+ renderConsoleReport(predictiveReport, {
1577
+ minSeverity: options.bomAuditMinSeverity,
1578
+ }),
1579
+ );
1580
+ } else if (DEBUG_MODE) {
1581
+ console.log(
1582
+ "Predictive BOM audit: No supported npm/PyPI targets found",
1583
+ );
1584
+ }
1585
+ const predictiveAnnotations = formatPredictiveAnnotations(
1586
+ predictiveReport,
1587
+ bomNSData.bomJson,
1588
+ {
1479
1589
  minSeverity: options.bomAuditMinSeverity,
1480
- }),
1590
+ },
1481
1591
  );
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
- {
1592
+ if (predictiveAnnotations.length && options.specVersion >= 1.4) {
1593
+ bomNSData.bomJson.annotations = [
1594
+ ...(bomNSData.bomJson.annotations || []),
1595
+ ...predictiveAnnotations,
1596
+ ];
1597
+ thoughtLog(
1598
+ `Embedded ${predictiveAnnotations.length} predictive audit annotations`,
1599
+ );
1600
+ }
1601
+ const predictiveResult = finalizeAuditReport(predictiveReport, {
1602
+ failSeverity: options.bomAuditFailSeverity,
1489
1603
  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);
1604
+ report: "console",
1605
+ });
1606
+ if (isSecureMode && predictiveResult.exitCode === 3) {
1607
+ console.error(
1608
+ "\nSecure mode: Predictive audit findings exceeded the configured threshold.",
1609
+ );
1610
+ console.error(
1611
+ "Review findings above or adjust --bom-audit-fail-severity to proceed.",
1612
+ );
1613
+ if (cleanup) {
1614
+ cleanupSourceDir(srcDir);
1615
+ }
1616
+ process.exit(1);
1515
1617
  }
1516
- process.exit(1);
1517
1618
  }
1518
1619
  }
1519
1620
  let internalCycloneDxInputPath = outputPlan.outputs.cyclonedx;
@@ -1684,6 +1785,27 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1684
1785
  // Automatically submit the bom data
1685
1786
  // biome-ignore lint/suspicious/noDoubleEquals: yargs passes true for empty values
1686
1787
  if (options.serverUrl && options.serverUrl != true && options.apiKey) {
1788
+ if (isSecureMode) {
1789
+ let serverHostname;
1790
+ try {
1791
+ serverHostname = new URL(options.serverUrl).hostname;
1792
+ } catch (err) {
1793
+ console.log("Invalid Dependency-Track server URL", err);
1794
+ process.exit(1);
1795
+ }
1796
+ if (!isAllowedHttpHost(serverHostname)) {
1797
+ recordActivity({
1798
+ kind: "submit",
1799
+ reason: "The URL host is not allowed as per the allowlist.",
1800
+ status: "blocked",
1801
+ target: options.serverUrl,
1802
+ });
1803
+ console.log(
1804
+ `Dependency-Track server host '${serverHostname}' is not allowed by CDXGEN_ALLOWED_HOSTS.`,
1805
+ );
1806
+ process.exit(1);
1807
+ }
1808
+ }
1687
1809
  if (isDryRun) {
1688
1810
  recordActivity({
1689
1811
  kind: "submit",
package/bin/convert.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  retrieveCdxgenVersion,
17
17
  safeExistsSync,
18
18
  safeMkdirSync,
19
+ safeWriteSync,
19
20
  } from "../lib/helpers/utils.js";
20
21
  import { convertCycloneDxToSpdx } from "../lib/stages/postgen/spdxConverter.js";
21
22
  import { validateSpdx } from "../lib/validator/bomValidator.js";
@@ -26,7 +27,7 @@ const args = _yargs
26
27
  .option("input", {
27
28
  alias: "i",
28
29
  default: "bom.json",
29
- description: "Input CycloneDX BOM JSON file.",
30
+ description: "Input CycloneDX BOM JSON or protobuf file.",
30
31
  })
31
32
  .option("output", {
32
33
  alias: "o",
@@ -50,18 +51,32 @@ const args = _yargs
50
51
  .help()
51
52
  .wrap(Math.min(120, yargs().terminalWidth())).argv;
52
53
 
53
- if (!safeExistsSync(args.input)) {
54
- console.error(`Input file '${args.input}' not found.`);
55
- process.exit(1);
56
- }
54
+ const loadCycloneDxBom = async (inputPath) => {
55
+ if (!safeExistsSync(inputPath)) {
56
+ console.error(`Input file '${inputPath}' not found.`);
57
+ process.exit(1);
58
+ }
59
+ const normalizedInputPath = `${inputPath}`.toLowerCase();
60
+ const isProtoInput =
61
+ normalizedInputPath.endsWith(".cdx") ||
62
+ normalizedInputPath.endsWith(".cdx.bin") ||
63
+ normalizedInputPath.endsWith(".proto");
64
+ try {
65
+ if (isProtoInput) {
66
+ const { readBinary } = await import("../lib/helpers/protobom.js");
67
+ return readBinary(inputPath, true);
68
+ }
69
+ return JSON.parse(fs.readFileSync(inputPath, "utf8"));
70
+ } catch (error) {
71
+ const inputType = isProtoInput ? "protobuf" : "JSON";
72
+ console.error(
73
+ `Failed to parse '${inputPath}' as CycloneDX ${inputType}: ${error.message}`,
74
+ );
75
+ process.exit(1);
76
+ }
77
+ };
57
78
 
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
- }
79
+ const bomJson = await loadCycloneDxBom(args.input);
65
80
 
66
81
  if (!isCycloneDxBom(bomJson)) {
67
82
  console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-convert"));
@@ -92,7 +107,7 @@ if (outputParent && outputParent !== "." && !safeExistsSync(outputParent)) {
92
107
  safeMkdirSync(outputParent, { recursive: true });
93
108
  }
94
109
 
95
- fs.writeFileSync(
110
+ safeWriteSync(
96
111
  outputPath,
97
112
  JSON.stringify(spdxJson, null, args.jsonPretty ? 2 : null),
98
113
  );