@cyclonedx/cdxgen 12.2.0 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +242 -90
  2. package/bin/audit.js +191 -0
  3. package/bin/cdxgen.js +532 -168
  4. package/bin/convert.js +99 -0
  5. package/bin/evinse.js +23 -0
  6. package/bin/repl.js +339 -8
  7. package/bin/sign.js +8 -0
  8. package/bin/validate.js +8 -0
  9. package/bin/verify.js +8 -0
  10. package/data/container-knowledge-index.json +125 -0
  11. package/data/gtfobins-index.json +6296 -0
  12. package/data/lolbas-index.json +150 -0
  13. package/data/queries-darwin.json +63 -3
  14. package/data/queries-win.json +45 -3
  15. package/data/queries.json +74 -2
  16. package/data/rules/chrome-extensions.yaml +240 -0
  17. package/data/rules/ci-permissions.yaml +478 -18
  18. package/data/rules/container-risk.yaml +270 -0
  19. package/data/rules/obom-runtime.yaml +891 -0
  20. package/data/rules/package-integrity.yaml +49 -0
  21. package/data/spdx-export.schema.json +6794 -0
  22. package/data/spdx-model-v3.0.1.jsonld +15999 -0
  23. package/lib/audit/index.js +1924 -0
  24. package/lib/audit/index.poku.js +1488 -0
  25. package/lib/audit/progress.js +137 -0
  26. package/lib/audit/progress.poku.js +188 -0
  27. package/lib/audit/reporters.js +618 -0
  28. package/lib/audit/scoring.js +310 -0
  29. package/lib/audit/scoring.poku.js +341 -0
  30. package/lib/audit/targets.js +260 -0
  31. package/lib/audit/targets.poku.js +331 -0
  32. package/lib/cli/index.js +276 -68
  33. package/lib/cli/index.poku.js +368 -0
  34. package/lib/helpers/analyzer.js +1052 -5
  35. package/lib/helpers/analyzer.poku.js +301 -0
  36. package/lib/helpers/annotationFormatter.js +49 -0
  37. package/lib/helpers/annotationFormatter.poku.js +44 -0
  38. package/lib/helpers/bomUtils.js +36 -0
  39. package/lib/helpers/bomUtils.poku.js +51 -0
  40. package/lib/helpers/caxa.js +2 -2
  41. package/lib/helpers/chromextutils.js +1153 -0
  42. package/lib/helpers/chromextutils.poku.js +493 -0
  43. package/lib/helpers/ciParsers/githubActions.js +1632 -45
  44. package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
  45. package/lib/helpers/containerRisk.js +186 -0
  46. package/lib/helpers/containerRisk.poku.js +52 -0
  47. package/lib/helpers/depsUtils.js +16 -0
  48. package/lib/helpers/depsUtils.poku.js +58 -1
  49. package/lib/helpers/display.js +245 -61
  50. package/lib/helpers/display.poku.js +162 -2
  51. package/lib/helpers/exportUtils.js +123 -0
  52. package/lib/helpers/exportUtils.poku.js +60 -0
  53. package/lib/helpers/formulationParsers.js +69 -0
  54. package/lib/helpers/formulationParsers.poku.js +44 -0
  55. package/lib/helpers/gtfobins.js +189 -0
  56. package/lib/helpers/gtfobins.poku.js +49 -0
  57. package/lib/helpers/lolbas.js +267 -0
  58. package/lib/helpers/lolbas.poku.js +39 -0
  59. package/lib/helpers/osqueryTransform.js +84 -0
  60. package/lib/helpers/osqueryTransform.poku.js +49 -0
  61. package/lib/helpers/provenanceUtils.js +193 -0
  62. package/lib/helpers/provenanceUtils.poku.js +145 -0
  63. package/lib/helpers/pylockutils.js +281 -0
  64. package/lib/helpers/pylockutils.poku.js +48 -0
  65. package/lib/helpers/registryProvenance.js +793 -0
  66. package/lib/helpers/registryProvenance.poku.js +452 -0
  67. package/lib/helpers/remote/dependency-track.js +84 -0
  68. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  69. package/lib/helpers/source.js +1267 -0
  70. package/lib/helpers/source.poku.js +771 -0
  71. package/lib/helpers/spdxUtils.js +97 -0
  72. package/lib/helpers/spdxUtils.poku.js +70 -0
  73. package/lib/helpers/table.js +384 -0
  74. package/lib/helpers/table.poku.js +186 -0
  75. package/lib/helpers/unicodeScan.js +147 -0
  76. package/lib/helpers/unicodeScan.poku.js +45 -0
  77. package/lib/helpers/utils.js +882 -136
  78. package/lib/helpers/utils.poku.js +995 -91
  79. package/lib/managers/binary.js +29 -5
  80. package/lib/managers/docker.js +179 -52
  81. package/lib/managers/docker.poku.js +327 -28
  82. package/lib/managers/oci.js +107 -23
  83. package/lib/managers/oci.poku.js +132 -0
  84. package/lib/server/openapi.yaml +50 -0
  85. package/lib/server/server.js +228 -331
  86. package/lib/server/server.poku.js +220 -5
  87. package/lib/stages/postgen/annotator.js +7 -0
  88. package/lib/stages/postgen/annotator.poku.js +40 -0
  89. package/lib/stages/postgen/auditBom.js +20 -5
  90. package/lib/stages/postgen/auditBom.poku.js +1729 -67
  91. package/lib/stages/postgen/postgen.js +40 -0
  92. package/lib/stages/postgen/postgen.poku.js +47 -0
  93. package/lib/stages/postgen/ruleEngine.js +80 -2
  94. package/lib/stages/postgen/spdxConverter.js +796 -0
  95. package/lib/stages/postgen/spdxConverter.poku.js +341 -0
  96. package/lib/validator/bomValidator.js +232 -0
  97. package/lib/validator/bomValidator.poku.js +70 -0
  98. package/lib/validator/complianceRules.js +70 -7
  99. package/lib/validator/complianceRules.poku.js +30 -0
  100. package/lib/validator/reporters/annotations.js +2 -2
  101. package/lib/validator/reporters/console.js +13 -2
  102. package/lib/validator/reporters.poku.js +13 -0
  103. package/package.json +10 -8
  104. package/types/bin/audit.d.ts +3 -0
  105. package/types/bin/audit.d.ts.map +1 -0
  106. package/types/bin/convert.d.ts +3 -0
  107. package/types/bin/convert.d.ts.map +1 -0
  108. package/types/bin/repl.d.ts.map +1 -1
  109. package/types/lib/audit/index.d.ts +115 -0
  110. package/types/lib/audit/index.d.ts.map +1 -0
  111. package/types/lib/audit/progress.d.ts +27 -0
  112. package/types/lib/audit/progress.d.ts.map +1 -0
  113. package/types/lib/audit/reporters.d.ts +35 -0
  114. package/types/lib/audit/reporters.d.ts.map +1 -0
  115. package/types/lib/audit/scoring.d.ts +35 -0
  116. package/types/lib/audit/scoring.d.ts.map +1 -0
  117. package/types/lib/audit/targets.d.ts +63 -0
  118. package/types/lib/audit/targets.d.ts.map +1 -0
  119. package/types/lib/cli/index.d.ts +8 -0
  120. package/types/lib/cli/index.d.ts.map +1 -1
  121. package/types/lib/helpers/analyzer.d.ts +13 -0
  122. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  123. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  124. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  125. package/types/lib/helpers/bomUtils.d.ts +5 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -0
  127. package/types/lib/helpers/chromextutils.d.ts +97 -0
  128. package/types/lib/helpers/chromextutils.d.ts.map +1 -0
  129. package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
  130. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  131. package/types/lib/helpers/containerRisk.d.ts +17 -0
  132. package/types/lib/helpers/containerRisk.d.ts.map +1 -0
  133. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  134. package/types/lib/helpers/display.d.ts +4 -1
  135. package/types/lib/helpers/display.d.ts.map +1 -1
  136. package/types/lib/helpers/exportUtils.d.ts +40 -0
  137. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  138. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  139. package/types/lib/helpers/gtfobins.d.ts +17 -0
  140. package/types/lib/helpers/gtfobins.d.ts.map +1 -0
  141. package/types/lib/helpers/lolbas.d.ts +16 -0
  142. package/types/lib/helpers/lolbas.d.ts.map +1 -0
  143. package/types/lib/helpers/osqueryTransform.d.ts +7 -0
  144. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
  145. package/types/lib/helpers/provenanceUtils.d.ts +90 -0
  146. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
  147. package/types/lib/helpers/pylockutils.d.ts +51 -0
  148. package/types/lib/helpers/pylockutils.d.ts.map +1 -0
  149. package/types/lib/helpers/registryProvenance.d.ts +17 -0
  150. package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
  151. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  152. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  153. package/types/lib/helpers/source.d.ts +141 -0
  154. package/types/lib/helpers/source.d.ts.map +1 -0
  155. package/types/lib/helpers/spdxUtils.d.ts +2 -0
  156. package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
  157. package/types/lib/helpers/table.d.ts +6 -0
  158. package/types/lib/helpers/table.d.ts.map +1 -0
  159. package/types/lib/helpers/unicodeScan.d.ts +46 -0
  160. package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
  161. package/types/lib/helpers/utils.d.ts +30 -11
  162. package/types/lib/helpers/utils.d.ts.map +1 -1
  163. package/types/lib/managers/binary.d.ts.map +1 -1
  164. package/types/lib/managers/docker.d.ts.map +1 -1
  165. package/types/lib/managers/oci.d.ts.map +1 -1
  166. package/types/lib/server/server.d.ts +0 -35
  167. package/types/lib/server/server.d.ts.map +1 -1
  168. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  170. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  171. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  172. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  173. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  174. package/types/lib/validator/bomValidator.d.ts +1 -0
  175. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  176. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  177. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  178. package/types/bin/dependencies.d.ts +0 -3
  179. package/types/bin/dependencies.d.ts.map +0 -1
  180. package/types/bin/licenses.d.ts +0 -3
  181. package/types/bin/licenses.d.ts.map +0 -1
package/bin/cdxgen.js CHANGED
@@ -4,7 +4,14 @@ import crypto from "node:crypto";
4
4
  import fs from "node:fs";
5
5
  import http from "node:http";
6
6
  import https from "node:https";
7
- import { basename, dirname, join, resolve } from "node:path";
7
+ import {
8
+ basename,
9
+ dirname,
10
+ isAbsolute,
11
+ join,
12
+ relative,
13
+ resolve,
14
+ } from "node:path";
8
15
  import process from "node:process";
9
16
 
10
17
  import { parse as _load } from "yaml";
@@ -25,7 +32,26 @@ import {
25
32
  printSummary,
26
33
  printTable,
27
34
  } from "../lib/helpers/display.js";
35
+ import {
36
+ createOutputPlan,
37
+ getOutputDirectory,
38
+ } from "../lib/helpers/exportUtils.js";
28
39
  import { TRACE_MODE, thoughtEnd, thoughtLog } from "../lib/helpers/logger.js";
40
+ import {
41
+ cleanupSourceDir,
42
+ findGitRefForPurlVersion,
43
+ gitClone,
44
+ isAllowedPath,
45
+ isAllowedWinPath,
46
+ maybePurlSource,
47
+ maybeRemotePath,
48
+ PURL_REGISTRY_LOOKUP_WARNING,
49
+ resolveGitUrlFromPurl,
50
+ resolvePurlSourceDirectory,
51
+ sanitizeRemoteUrlForLogs,
52
+ validateAndRejectGitSource,
53
+ validatePurlSource,
54
+ } from "../lib/helpers/source.js";
29
55
  import {
30
56
  commandsExecuted,
31
57
  DEBUG_MODE,
@@ -42,9 +68,10 @@ import {
42
68
  toCamel,
43
69
  } from "../lib/helpers/utils.js";
44
70
  import { postProcess } from "../lib/stages/postgen/postgen.js";
71
+ import { convertCycloneDxToSpdx } from "../lib/stages/postgen/spdxConverter.js";
45
72
  import { auditEnvironment } from "../lib/stages/pregen/envAudit.js";
46
73
  import { prepareEnv } from "../lib/stages/pregen/pregen.js";
47
- import { validateBom } from "../lib/validator/bomValidator.js";
74
+ import { validateBom, validateSpdx } from "../lib/validator/bomValidator.js";
48
75
 
49
76
  // Support for config files
50
77
  const configPaths = [
@@ -134,6 +161,10 @@ const args = _yargs
134
161
  description:
135
162
  "Perform deep searches for components. Useful while scanning C/C++ apps, live OS and oci images.",
136
163
  })
164
+ .option("git-branch", {
165
+ description: "Git branch to clone when the source is a git URL or purl",
166
+ type: "string",
167
+ })
137
168
  .option("server-url", {
138
169
  description: "Dependency track url. Eg: https://deptrack.cyclonedx.io",
139
170
  type: "string",
@@ -171,6 +202,24 @@ const args = _yargs
171
202
  description: "Dependency track parent project id",
172
203
  type: "string",
173
204
  })
205
+ .option("parent-project-name", {
206
+ description: "Dependency track parent project name",
207
+ type: "string",
208
+ })
209
+ .option("parent-project-version", {
210
+ description: "Dependency track parent project version",
211
+ type: "string",
212
+ })
213
+ .option("auto-create", {
214
+ description: "Dependency track autoCreate value for BOM uploads",
215
+ type: "boolean",
216
+ hidden: true,
217
+ })
218
+ .option("is-latest", {
219
+ description: "Dependency track isLatest value for BOM uploads",
220
+ type: "boolean",
221
+ hidden: true,
222
+ })
174
223
  .option("required-only", {
175
224
  type: "boolean",
176
225
  description:
@@ -249,7 +298,7 @@ const args = _yargs
249
298
  hidden: true,
250
299
  })
251
300
  .option("spec-version", {
252
- description: "CycloneDX Specification version to use. Defaults to 1.6",
301
+ description: "CycloneDX Specification version to use. Defaults to 1.7",
253
302
  default: 1.7,
254
303
  type: "number",
255
304
  choices: [1.4, 1.5, 1.6, 1.7],
@@ -289,6 +338,25 @@ const args = _yargs
289
338
  hidden: true,
290
339
  choices: ["pre-build", "build", "post-build"],
291
340
  })
341
+ .option("include-release-notes", {
342
+ type: "boolean",
343
+ default: false,
344
+ hidden: true,
345
+ description:
346
+ "Attach CycloneDX releaseNotes to the cdxgen tool component in metadata.",
347
+ })
348
+ .option("release-notes-current-tag", {
349
+ type: "string",
350
+ hidden: true,
351
+ description:
352
+ "Current git tag used to build CycloneDX releaseNotes for cdxgen metadata.",
353
+ })
354
+ .option("release-notes-previous-tag", {
355
+ type: "string",
356
+ hidden: true,
357
+ description:
358
+ "Previous git tag used to build CycloneDX releaseNotes for cdxgen metadata.",
359
+ })
292
360
  .option("include-regex", {
293
361
  description:
294
362
  "glob pattern to include. This overrides the default pattern used during auto-detection.",
@@ -304,6 +372,10 @@ const args = _yargs
304
372
  default: false,
305
373
  description: "Serialize and export BOM as protobuf binary.",
306
374
  })
375
+ .option("format", {
376
+ description:
377
+ "Export format(s). Supports cyclonedx, spdx, repeated --format flags, or a comma-separated list such as cyclonedx,spdx.",
378
+ })
307
379
  .option("proto-bin-file", {
308
380
  description: "Path for the serialized protobuf binary.",
309
381
  default: "bom.cdx",
@@ -415,12 +487,41 @@ const args = _yargs
415
487
  default: "high",
416
488
  hidden: true,
417
489
  })
490
+ .option("bom-audit-scope", {
491
+ description:
492
+ "Predictive audit target scope. Use 'required' to scan only dependencies with scope=required (missing scope is treated as required).",
493
+ type: "string",
494
+ choices: ["all", "required"],
495
+ default: "all",
496
+ hidden: true,
497
+ })
498
+ .option("bom-audit-max-targets", {
499
+ description:
500
+ "Optional upper bound for predictive audit targets. By default cdxgen scans required dependencies first and expands to at least 50 targets.",
501
+ type: "number",
502
+ hidden: true,
503
+ })
504
+ .option("bom-audit-include-trusted", {
505
+ description:
506
+ "Include packages already marked with trusted publishing metadata in predictive BOM audit target selection.",
507
+ type: "boolean",
508
+ default: false,
509
+ hidden: true,
510
+ })
511
+ .option("bom-audit-only-trusted", {
512
+ description:
513
+ "Restrict predictive BOM audit target selection to packages marked with trusted publishing metadata.",
514
+ type: "boolean",
515
+ default: false,
516
+ hidden: true,
517
+ })
418
518
  .completion("completion", "Generate bash/zsh completion")
419
519
  .array("type")
420
520
  .array("excludeType")
421
521
  .array("filter")
422
522
  .array("only")
423
523
  .array("author")
524
+ .array("format")
424
525
  .array("standard")
425
526
  .array("feature-flags")
426
527
  .array("technique")
@@ -468,6 +569,12 @@ if (args.help) {
468
569
  _yargs.showHelp();
469
570
  process.exit(0);
470
571
  }
572
+ if (args.bomAuditIncludeTrusted && args.bomAuditOnlyTrusted) {
573
+ console.error(
574
+ "Use either --bom-audit-include-trusted or --bom-audit-only-trusted, not both.",
575
+ );
576
+ process.exit(1);
577
+ }
471
578
 
472
579
  // Native Enterprise Network Configuration (Node.js v22.21+, Bun, Deno)
473
580
  // https://nodejs.org/en/learn/http/enterprise-network-configuration
@@ -500,6 +607,8 @@ if (!process.env.NODE_USE_SYSTEM_CA) {
500
607
  }
501
608
 
502
609
  const filePath = args._[0] || process.cwd();
610
+ const sourceInputIsRemoteOrPurl =
611
+ maybeRemotePath(filePath) || maybePurlSource(filePath);
503
612
  if (!args.projectName) {
504
613
  if (filePath !== ".") {
505
614
  args.projectName = basename(filePath);
@@ -509,9 +618,8 @@ if (!args.projectName) {
509
618
  }
510
619
  thoughtLog(`Let's try to generate a CycloneDX BOM for the path '${filePath}'`);
511
620
  if (
512
- filePath.includes(" ") ||
513
- filePath.includes("\r") ||
514
- filePath.includes("\n")
621
+ !sourceInputIsRemoteOrPurl &&
622
+ (filePath.includes(" ") || filePath.includes("\r") || filePath.includes("\n"))
515
623
  ) {
516
624
  console.log(
517
625
  `'${filePath}' contains spaces. This could lead to bugs when invoking external build tools.`,
@@ -527,6 +635,10 @@ if (process.argv[1].includes("obom") && !args.type) {
527
635
  "Ok, the user wants to generate an Operations Bill-of-Materials (OBOM).",
528
636
  );
529
637
  }
638
+ if (process.argv[1].includes("spdxgen") && !args.format) {
639
+ args.format = "spdx";
640
+ thoughtLog("Ok, defaulting the export format to SPDX.");
641
+ }
530
642
 
531
643
  /**
532
644
  * Command line options
@@ -539,19 +651,23 @@ const options = Object.assign({}, args, {
539
651
  deep: args.deep || args.evidence,
540
652
  output:
541
653
  isSecureMode && args.output === "bom.json"
542
- ? resolve(join(filePath, args.output))
654
+ ? sourceInputIsRemoteOrPurl
655
+ ? resolve(args.output)
656
+ : resolve(join(filePath, args.output))
543
657
  : args.output,
544
658
  exclude: args.exclude || args.excludeRegex,
545
659
  include: args.include || args.includeRegex,
546
660
  });
547
- // Should we create the output directory?
548
- const outputDirectory = dirname(options.output);
549
- if (
550
- outputDirectory &&
551
- outputDirectory !== process.cwd() &&
552
- !safeExistsSync(outputDirectory)
553
- ) {
554
- fs.mkdirSync(outputDirectory, { recursive: true });
661
+ const outputPlan = createOutputPlan(options);
662
+ for (const outputFile of Object.values(outputPlan.outputs)) {
663
+ const outputDirectory = getOutputDirectory(outputFile);
664
+ if (
665
+ outputDirectory &&
666
+ outputDirectory !== process.cwd() &&
667
+ !safeExistsSync(outputDirectory)
668
+ ) {
669
+ fs.mkdirSync(outputDirectory, { recursive: true });
670
+ }
555
671
  }
556
672
  // Filter duplicate types. Eg: -t gradle -t gradle
557
673
  if (options.projectType && Array.isArray(options.projectType)) {
@@ -911,6 +1027,129 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
911
1027
  safeExistsSync(process.env.SBOM_SIGN_PRIVATE_KEY)) ||
912
1028
  process.env.SBOM_SIGN_PRIVATE_KEY_BASE64));
913
1029
 
1030
+ const stringifyJson = (jsonPayload, jsonPretty) =>
1031
+ typeof jsonPayload === "string" || jsonPayload instanceof String
1032
+ ? jsonPayload
1033
+ : JSON.stringify(jsonPayload, null, jsonPretty ? 2 : null);
1034
+
1035
+ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
1036
+ const jsonPayload = stringifyJson(bomJson, options.jsonPretty);
1037
+ fs.writeFileSync(jsonFile, jsonPayload);
1038
+ if (jsonFile.endsWith("bom.json")) {
1039
+ thoughtLog(
1040
+ `Let's save the file to "${jsonFile}". Should I suggest the '.cdx.json' file extension for better semantics?`,
1041
+ );
1042
+ } else {
1043
+ thoughtLog(`Let's save the file to "${jsonFile}".`);
1044
+ }
1045
+ if (!jsonPayload || !needsBomSigning(options)) {
1046
+ return jsonPayload;
1047
+ }
1048
+ let alg = process.env.SBOM_SIGN_ALGORITHM || "RS512";
1049
+ if (alg.includes("none")) {
1050
+ alg = "RS512";
1051
+ }
1052
+ let privateKeyToUse;
1053
+ let jwkPublicKey;
1054
+ let publicKeyFile;
1055
+ if (options.generateKeyAndSign) {
1056
+ const jdirName = dirname(jsonFile);
1057
+ publicKeyFile = join(jdirName, "public.key");
1058
+ const privateKeyFile = join(jdirName, "private.key");
1059
+ const privateKeyB64File = join(jdirName, "private.key.base64");
1060
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
1061
+ modulusLength: 4096,
1062
+ publicKeyEncoding: {
1063
+ type: "spki",
1064
+ format: "pem",
1065
+ },
1066
+ privateKeyEncoding: {
1067
+ type: "pkcs8",
1068
+ format: "pem",
1069
+ },
1070
+ });
1071
+ fs.writeFileSync(publicKeyFile, publicKey);
1072
+ fs.writeFileSync(privateKeyFile, privateKey);
1073
+ fs.writeFileSync(
1074
+ privateKeyB64File,
1075
+ Buffer.from(privateKey, "utf8").toString("base64"),
1076
+ );
1077
+ console.log(
1078
+ "Created public/private key pairs for testing purposes",
1079
+ publicKeyFile,
1080
+ privateKeyFile,
1081
+ privateKeyB64File,
1082
+ );
1083
+ privateKeyToUse = privateKey;
1084
+ jwkPublicKey = crypto.createPublicKey(publicKey).export({ format: "jwk" });
1085
+ } else {
1086
+ if (process.env?.SBOM_SIGN_PRIVATE_KEY) {
1087
+ privateKeyToUse = fs.readFileSync(
1088
+ process.env.SBOM_SIGN_PRIVATE_KEY,
1089
+ "utf8",
1090
+ );
1091
+ } else if (process.env?.SBOM_SIGN_PRIVATE_KEY_BASE64) {
1092
+ privateKeyToUse = Buffer.from(
1093
+ process.env.SBOM_SIGN_PRIVATE_KEY_BASE64,
1094
+ "base64",
1095
+ ).toString("utf8");
1096
+ }
1097
+ if (
1098
+ process.env.SBOM_SIGN_PUBLIC_KEY &&
1099
+ safeExistsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
1100
+ ) {
1101
+ jwkPublicKey = crypto
1102
+ .createPublicKey(
1103
+ fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8"),
1104
+ )
1105
+ .export({ format: "jwk" });
1106
+ } else if (process.env?.SBOM_SIGN_PUBLIC_KEY_BASE64) {
1107
+ jwkPublicKey = Buffer.from(
1108
+ process.env.SBOM_SIGN_PUBLIC_KEY_BASE64,
1109
+ "base64",
1110
+ ).toString("utf8");
1111
+ }
1112
+ }
1113
+ try {
1114
+ const bomJsonUnsignedObj = JSON.parse(jsonPayload);
1115
+ const signOptions = {
1116
+ privateKey: privateKeyToUse,
1117
+ algorithm: alg,
1118
+ publicKeyJwk: jwkPublicKey,
1119
+ mode: process.env.SBOM_SIGN_MODE || "replace",
1120
+ signComponents: true,
1121
+ signServices: true,
1122
+ signAnnotations: true,
1123
+ };
1124
+ thoughtLog(`Signing the BOM file "${jsonFile}".`);
1125
+ const signedBom = signBom(bomJsonUnsignedObj, signOptions);
1126
+ fs.writeFileSync(
1127
+ jsonFile,
1128
+ JSON.stringify(signedBom, null, options.jsonPretty ? 2 : null),
1129
+ );
1130
+ if (publicKeyFile) {
1131
+ const publicKeyStr = fs.readFileSync(publicKeyFile, "utf8");
1132
+ const signatureVerification = verifyBom(signedBom, publicKeyStr);
1133
+ if (signatureVerification) {
1134
+ console.log(
1135
+ "SBOM signature is verifiable natively with the public key and the algorithm",
1136
+ publicKeyFile,
1137
+ alg,
1138
+ );
1139
+ } else {
1140
+ console.log("SBOM signature verification was unsuccessful");
1141
+ console.log("Check if the public key was exported in PEM format");
1142
+ }
1143
+ }
1144
+ } catch (ex) {
1145
+ console.log("SBOM signing was unsuccessful:", ex.message);
1146
+ console.log(
1147
+ "Check if the private key was exported in PEM format and the algorithm is JSF-compliant.",
1148
+ );
1149
+ }
1150
+ return jsonPayload;
1151
+ };
1152
+
914
1153
  /**
915
1154
  * Method to start the bom creation process
916
1155
  */
@@ -936,24 +1175,130 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
936
1175
  const serverModule = await import("../lib/server/server.js");
937
1176
  return serverModule.start(options);
938
1177
  }
939
- // Check if cdxgen has the required permissions
940
- if (!checkPermissions(filePath, options)) {
1178
+ let sourcePath = filePath;
1179
+ let purlResolution;
1180
+ if (maybePurlSource(sourcePath)) {
1181
+ const purlValidationError = validatePurlSource(sourcePath);
1182
+ if (purlValidationError) {
1183
+ console.error(purlValidationError.error, purlValidationError.details);
1184
+ process.exit(1);
1185
+ }
1186
+ purlResolution = await resolveGitUrlFromPurl(sourcePath);
1187
+ if (!purlResolution?.repoUrl) {
1188
+ console.error(
1189
+ "Unable to resolve the provided package URL to a repository URL.",
1190
+ );
1191
+ process.exit(1);
1192
+ }
1193
+ console.warn(
1194
+ `${PURL_REGISTRY_LOOKUP_WARNING} Registry: ${purlResolution.registry}, purl type: ${purlResolution.type}, resolved URL: ${sanitizeRemoteUrlForLogs(purlResolution.repoUrl)}`,
1195
+ );
1196
+ sourcePath = purlResolution.repoUrl;
1197
+ }
1198
+ if (
1199
+ maybeRemotePath(sourcePath) &&
1200
+ isSecureMode &&
1201
+ !process.env.CDXGEN_GIT_ALLOWED_HOSTS &&
1202
+ !process.env.CDXGEN_SERVER_ALLOWED_HOSTS
1203
+ ) {
1204
+ console.error(
1205
+ "SECURE MODE: Configure CDXGEN_GIT_ALLOWED_HOSTS (or CDXGEN_SERVER_ALLOWED_HOSTS) before using git URL or purl sources.",
1206
+ );
1207
+ process.exit(1);
1208
+ }
1209
+ if (!maybeRemotePath(sourcePath) && !isAllowedPath(resolve(sourcePath))) {
1210
+ console.error(
1211
+ "Path is not allowed as per CDXGEN_ALLOWED_PATHS/CDXGEN_SERVER_ALLOWED_PATHS.",
1212
+ );
1213
+ process.exit(1);
1214
+ }
1215
+ if (!maybeRemotePath(sourcePath) && !isAllowedWinPath(resolve(sourcePath))) {
1216
+ console.error("Path is not allowed on this platform.");
1217
+ process.exit(1);
1218
+ }
1219
+ if (maybeRemotePath(sourcePath)) {
1220
+ const validationError = validateAndRejectGitSource(sourcePath);
1221
+ if (validationError) {
1222
+ console.error(validationError.error, validationError.details);
1223
+ process.exit(1);
1224
+ }
1225
+ }
1226
+ const checkPath = maybeRemotePath(sourcePath) ? getTmpDir() : sourcePath;
1227
+ if (maybeRemotePath(sourcePath)) {
1228
+ options.releaseNotesGitUrl = sourcePath;
1229
+ }
1230
+ if (!checkPermissions(checkPath, options)) {
941
1231
  if (isSecureMode) {
942
1232
  process.exit(1);
943
1233
  }
944
1234
  return;
945
1235
  }
946
- prepareEnv(filePath, options);
1236
+ let srcDir = sourcePath;
1237
+ let cleanup = false;
1238
+ let gitRef = options.gitBranch;
1239
+ if (maybeRemotePath(sourcePath)) {
1240
+ if (!gitRef && purlResolution?.version) {
1241
+ gitRef = findGitRefForPurlVersion(sourcePath, purlResolution);
1242
+ if (!gitRef) {
1243
+ console.warn(
1244
+ `Unable to find a matching git tag for version '${purlResolution.version}'. Falling back to repository default branch.`,
1245
+ );
1246
+ }
1247
+ }
1248
+ srcDir = gitClone(sourcePath, gitRef);
1249
+ if (purlResolution?.type === "npm") {
1250
+ const cloneRootDir = srcDir;
1251
+ const purlSourceDir = resolvePurlSourceDirectory(srcDir, purlResolution);
1252
+ if (purlSourceDir) {
1253
+ if (purlSourceDir !== cloneRootDir) {
1254
+ const relativeDir = relative(cloneRootDir, purlSourceDir);
1255
+ if (relativeDir.startsWith("..") || isAbsolute(relativeDir)) {
1256
+ console.warn(
1257
+ `Ignoring detected npm package directory outside clone root: ${purlSourceDir}`,
1258
+ );
1259
+ } else {
1260
+ console.warn(
1261
+ `Using npm package directory '${purlSourceDir}' for purl '${purlResolution.namespace ? `${purlResolution.namespace}/` : ""}${purlResolution.name}'.`,
1262
+ );
1263
+ srcDir = purlSourceDir;
1264
+ }
1265
+ }
1266
+ }
1267
+ }
1268
+ cleanup = true;
1269
+ }
1270
+ prepareEnv(srcDir, options);
947
1271
  thoughtLog("Getting ready to generate the BOM ⚡️.");
948
- let bomNSData = (await createBom(filePath, options)) || {};
1272
+ const originalFetchPackageMetadata = process.env.CDXGEN_FETCH_PKG_METADATA;
1273
+ if (options.bomAudit) {
1274
+ process.env.CDXGEN_FETCH_PKG_METADATA = "true";
1275
+ }
1276
+ let bomNSData;
1277
+ try {
1278
+ bomNSData = (await createBom(srcDir, options)) || {};
1279
+ } finally {
1280
+ if (originalFetchPackageMetadata === undefined) {
1281
+ delete process.env.CDXGEN_FETCH_PKG_METADATA;
1282
+ } else {
1283
+ process.env.CDXGEN_FETCH_PKG_METADATA = originalFetchPackageMetadata;
1284
+ }
1285
+ }
949
1286
  if (bomNSData?.bomJson) {
950
1287
  thoughtLog(
951
1288
  "Tweaking the generated BOM data with useful annotations and properties.",
952
1289
  );
953
1290
  }
954
1291
  // Add extra metadata and annotations with post processing
955
- bomNSData = postProcess(bomNSData, options, filePath);
1292
+ bomNSData = postProcess(bomNSData, options, srcDir);
956
1293
  if (options.bomAudit && bomNSData?.bomJson) {
1294
+ const { finalizeAuditReport, runAuditFromBoms } = await import(
1295
+ "../lib/audit/index.js"
1296
+ );
1297
+ const { createProgressTracker } = await import("../lib/audit/progress.js");
1298
+ const { collectAuditTargets } = await import("../lib/audit/targets.js");
1299
+ const { formatPredictiveAnnotations, renderConsoleReport } = await import(
1300
+ "../lib/audit/reporters.js"
1301
+ );
957
1302
  const {
958
1303
  auditBom,
959
1304
  formatAnnotations,
@@ -981,159 +1326,124 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
981
1326
  console.error(
982
1327
  "Review findings above or adjust --bom-audit-fail-severity to proceed.",
983
1328
  );
1329
+ if (cleanup) {
1330
+ cleanupSourceDir(srcDir);
1331
+ }
984
1332
  process.exit(1);
985
1333
  }
986
- }
987
- if (
988
- options.output &&
989
- (typeof options.output === "string" || options.output instanceof String)
990
- ) {
991
- const jsonFile = options.output;
992
- // Create bom json file
993
- if (bomNSData.bomJson) {
994
- let jsonPayload;
995
- if (
996
- typeof bomNSData.bomJson === "string" ||
997
- bomNSData.bomJson instanceof String
998
- ) {
999
- fs.writeFileSync(jsonFile, bomNSData.bomJson);
1000
- jsonPayload = bomNSData.bomJson;
1001
- } else {
1002
- jsonPayload = JSON.stringify(
1003
- bomNSData.bomJson,
1004
- null,
1005
- options.jsonPretty ? 2 : null,
1006
- );
1007
- fs.writeFileSync(jsonFile, jsonPayload);
1008
- if (jsonFile.endsWith("bom.json")) {
1009
- thoughtLog(
1010
- `Let's save the file to "${jsonFile}". Should I suggest the '.cdx.json' file extension for better semantics?`,
1011
- );
1012
- } else {
1013
- thoughtLog(`Let's save the file to "${jsonFile}".`);
1014
- }
1015
- }
1016
- if (jsonPayload && needsBomSigning(options)) {
1017
- let alg = process.env.SBOM_SIGN_ALGORITHM || "RS512";
1018
- if (alg.includes("none")) {
1019
- alg = "RS512";
1020
- }
1021
- let privateKeyToUse;
1022
- let jwkPublicKey;
1023
- let publicKeyFile;
1024
- if (options.generateKeyAndSign) {
1025
- const jdirName = dirname(jsonFile);
1026
- publicKeyFile = join(jdirName, "public.key");
1027
- const privateKeyFile = join(jdirName, "private.key");
1028
- const privateKeyB64File = join(jdirName, "private.key.base64");
1029
- const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
1030
- modulusLength: 4096,
1031
- publicKeyEncoding: {
1032
- type: "spki",
1033
- format: "pem",
1034
- },
1035
- privateKeyEncoding: {
1036
- type: "pkcs8",
1037
- format: "pem",
1038
- },
1039
- });
1040
- fs.writeFileSync(publicKeyFile, publicKey);
1041
- fs.writeFileSync(privateKeyFile, privateKey);
1042
- fs.writeFileSync(
1043
- privateKeyB64File,
1044
- Buffer.from(privateKey, "utf8").toString("base64"),
1045
- );
1046
- console.log(
1047
- "Created public/private key pairs for testing purposes",
1048
- publicKeyFile,
1049
- privateKeyFile,
1050
- privateKeyB64File,
1051
- );
1052
- privateKeyToUse = privateKey;
1053
- jwkPublicKey = crypto
1054
- .createPublicKey(publicKey)
1055
- .export({ format: "jwk" });
1056
- } else {
1057
- if (process.env?.SBOM_SIGN_PRIVATE_KEY) {
1058
- privateKeyToUse = fs.readFileSync(
1059
- process.env.SBOM_SIGN_PRIVATE_KEY,
1060
- "utf8",
1061
- );
1062
- } else if (process.env?.SBOM_SIGN_PRIVATE_KEY_BASE64) {
1063
- privateKeyToUse = Buffer.from(
1064
- process.env.SBOM_SIGN_PRIVATE_KEY_BASE64,
1065
- "base64",
1066
- ).toString("utf8");
1067
- }
1068
- if (
1069
- process.env.SBOM_SIGN_PUBLIC_KEY &&
1070
- safeExistsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
1071
- ) {
1072
- jwkPublicKey = crypto
1073
- .createPublicKey(
1074
- fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8"),
1075
- )
1076
- .export({ format: "jwk" });
1077
- } else if (process.env?.SBOM_SIGN_PUBLIC_KEY_BASE64) {
1078
- jwkPublicKey = Buffer.from(
1079
- process.env.SBOM_SIGN_PUBLIC_KEY_BASE64,
1080
- "base64",
1081
- ).toString("utf8");
1082
- }
1083
- }
1084
- try {
1085
- const bomJsonUnsignedObj = JSON.parse(jsonPayload);
1086
- const signOptions = {
1087
- privateKey: privateKeyToUse,
1088
- algorithm: alg,
1089
- publicKeyJwk: jwkPublicKey,
1090
- mode: process.env.SBOM_SIGN_MODE || "replace",
1091
- signComponents: true,
1092
- signServices: true,
1093
- signAnnotations: true,
1094
- };
1095
- thoughtLog(`Signing the BOM file "${jsonFile}".`);
1096
- const signedBom = signBom(bomJsonUnsignedObj, signOptions);
1097
- fs.writeFileSync(
1098
- jsonFile,
1099
- JSON.stringify(signedBom, null, options.jsonPretty ? 2 : null),
1100
- );
1101
- if (publicKeyFile) {
1102
- const publicKeyStr = fs.readFileSync(publicKeyFile, "utf8");
1103
- const signatureVerification = verifyBom(signedBom, publicKeyStr);
1104
- if (signatureVerification) {
1105
- console.log(
1106
- "SBOM signature is verifiable natively with the public key and the algorithm",
1107
- publicKeyFile,
1108
- alg,
1109
- );
1110
- } else {
1111
- console.log("SBOM signature verification was unsuccessful");
1112
- console.log("Check if the public key was exported in PEM format");
1113
- }
1114
- }
1115
- } catch (ex) {
1116
- console.log("SBOM signing was unsuccessful:", ex.message);
1117
- console.log(
1118
- "Check if the private key was exported in PEM format and the algorithm is JSF-compliant.",
1119
- );
1120
- }
1121
- }
1334
+
1335
+ thoughtLog("Let's run predictive dependency audit...");
1336
+ const progressTracker = createProgressTracker();
1337
+ const predictiveAuditScope =
1338
+ options.bomAuditScope === "required" ? "required" : undefined;
1339
+ const predictiveAuditTrusted = options.bomAuditOnlyTrusted
1340
+ ? "only"
1341
+ : options.bomAuditIncludeTrusted
1342
+ ? "include"
1343
+ : undefined;
1344
+ const requiredAuditTargetCount = collectAuditTargets(
1345
+ [
1346
+ {
1347
+ bomJson: bomNSData.bomJson,
1348
+ source: filePath,
1349
+ },
1350
+ ],
1351
+ {
1352
+ scope: "required",
1353
+ trusted: predictiveAuditTrusted,
1354
+ },
1355
+ ).targets.length;
1356
+ const predictiveAuditMaxTargets =
1357
+ typeof options.bomAuditMaxTargets === "number" &&
1358
+ options.bomAuditMaxTargets > 0
1359
+ ? options.bomAuditMaxTargets
1360
+ : predictiveAuditScope === "required"
1361
+ ? undefined
1362
+ : Math.max(50, requiredAuditTargetCount);
1363
+ let predictiveReport;
1364
+ try {
1365
+ predictiveReport = await runAuditFromBoms(
1366
+ [
1367
+ {
1368
+ bomJson: bomNSData.bomJson,
1369
+ source: filePath,
1370
+ },
1371
+ ],
1372
+ {
1373
+ categories: options.bomAuditCategories
1374
+ ? options.bomAuditCategories
1375
+ .split(",")
1376
+ .map((category) => category.trim())
1377
+ .filter(Boolean)
1378
+ : undefined,
1379
+ failSeverity: options.bomAuditFailSeverity,
1380
+ maxTargets: predictiveAuditMaxTargets,
1381
+ minSeverity: options.bomAuditMinSeverity,
1382
+ onProgress: progressTracker.onProgress,
1383
+ scope: predictiveAuditScope,
1384
+ trusted: predictiveAuditTrusted,
1385
+ trustedSelectionHelp:
1386
+ "Use --bom-audit-include-trusted to include them or --bom-audit-only-trusted to audit just those packages.",
1387
+ },
1388
+ );
1389
+ } finally {
1390
+ progressTracker.stop();
1122
1391
  }
1123
- // bom ns mapping
1124
- if (bomNSData.nsMapping && Object.keys(bomNSData.nsMapping).length) {
1125
- const nsFile = `${jsonFile}.map`;
1126
- fs.writeFileSync(nsFile, JSON.stringify(bomNSData.nsMapping));
1392
+ if (predictiveReport.summary.totalTargets > 0) {
1393
+ process.stderr.write(
1394
+ renderConsoleReport(predictiveReport, {
1395
+ minSeverity: options.bomAuditMinSeverity,
1396
+ }),
1397
+ );
1398
+ } else if (DEBUG_MODE) {
1399
+ console.log("Predictive BOM audit: No supported npm/PyPI targets found");
1127
1400
  }
1128
- } else if (!options.print) {
1129
- if (bomNSData.bomJson) {
1130
- console.log(
1131
- JSON.stringify(bomNSData.bomJson, null, options.jsonPretty ? 2 : null),
1401
+ const predictiveAnnotations = formatPredictiveAnnotations(
1402
+ predictiveReport,
1403
+ bomNSData.bomJson,
1404
+ {
1405
+ minSeverity: options.bomAuditMinSeverity,
1406
+ },
1407
+ );
1408
+ if (predictiveAnnotations.length && options.specVersion >= 1.4) {
1409
+ bomNSData.bomJson.annotations = [
1410
+ ...(bomNSData.bomJson.annotations || []),
1411
+ ...predictiveAnnotations,
1412
+ ];
1413
+ thoughtLog(
1414
+ `Embedded ${predictiveAnnotations.length} predictive audit annotations`,
1132
1415
  );
1133
- } else {
1134
- console.log("Unable to produce BOM for", filePath);
1135
- console.log("Try running the command with -t <type> or -r argument");
1136
1416
  }
1417
+ const predictiveResult = finalizeAuditReport(predictiveReport, {
1418
+ failSeverity: options.bomAuditFailSeverity,
1419
+ minSeverity: options.bomAuditMinSeverity,
1420
+ report: "console",
1421
+ });
1422
+ if (isSecureMode && predictiveResult.exitCode === 3) {
1423
+ console.error(
1424
+ "\nSecure mode: Predictive audit findings exceeded the configured threshold.",
1425
+ );
1426
+ console.error(
1427
+ "Review findings above or adjust --bom-audit-fail-severity to proceed.",
1428
+ );
1429
+ if (cleanup) {
1430
+ cleanupSourceDir(srcDir);
1431
+ }
1432
+ process.exit(1);
1433
+ }
1434
+ }
1435
+ let internalCycloneDxInputPath = outputPlan.outputs.cyclonedx;
1436
+ if ((options.evidence || options.includeCrypto) && bomNSData?.bomJson) {
1437
+ if (!internalCycloneDxInputPath) {
1438
+ internalCycloneDxInputPath = join(
1439
+ getTmpDir(),
1440
+ `cdxgen-${Date.now()}-${basename(filePath)}.cdx.json`,
1441
+ );
1442
+ }
1443
+ fs.writeFileSync(
1444
+ internalCycloneDxInputPath,
1445
+ stringifyJson(bomNSData.bomJson, options.jsonPretty),
1446
+ );
1137
1447
  }
1138
1448
  // Evidence generation
1139
1449
  if (options.evidence || options.includeCrypto) {
@@ -1145,7 +1455,7 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
1145
1455
  options.projectType = options.projectType || ["java"];
1146
1456
  const evinseOptions = {
1147
1457
  _: args._,
1148
- input: options.output,
1458
+ input: internalCycloneDxInputPath || options.output,
1149
1459
  output: options.evinseOutput,
1150
1460
  language: options.projectType,
1151
1461
  skipMavenCollector: false,
@@ -1184,10 +1494,58 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
1184
1494
  if (options.validate && bomNSData?.bomJson) {
1185
1495
  thoughtLog("Wait, let's check the generated BOM file for any issues.");
1186
1496
  if (!validateBom(bomNSData.bomJson)) {
1497
+ if (cleanup) {
1498
+ cleanupSourceDir(srcDir);
1499
+ }
1187
1500
  process.exit(1);
1188
1501
  }
1189
1502
  thoughtLog("✅ BOM file looks valid.");
1190
1503
  }
1504
+ if (
1505
+ outputPlan.formats.has("spdx") &&
1506
+ bomNSData?.bomJson &&
1507
+ bomNSData?.bomJson?.bomFormat === "CycloneDX"
1508
+ ) {
1509
+ thoughtLog(
1510
+ "Preparing the SPDX 3.0.1 export from the validated CycloneDX BOM.",
1511
+ );
1512
+ bomNSData.spdxJson = convertCycloneDxToSpdx(bomNSData.bomJson, options);
1513
+ if (options.validate && !validateSpdx(bomNSData.spdxJson)) {
1514
+ process.exit(1);
1515
+ }
1516
+ }
1517
+ if (
1518
+ options.output &&
1519
+ (typeof options.output === "string" || options.output instanceof String)
1520
+ ) {
1521
+ if (outputPlan.outputs.cyclonedx && bomNSData.bomJson) {
1522
+ writeCycloneDxOutput(
1523
+ outputPlan.outputs.cyclonedx,
1524
+ bomNSData.bomJson,
1525
+ options,
1526
+ );
1527
+ if (bomNSData.nsMapping && Object.keys(bomNSData.nsMapping).length) {
1528
+ const nsFile = `${outputPlan.outputs.cyclonedx}.map`;
1529
+ fs.writeFileSync(nsFile, JSON.stringify(bomNSData.nsMapping));
1530
+ }
1531
+ }
1532
+ if (outputPlan.outputs.spdx && bomNSData.spdxJson) {
1533
+ fs.writeFileSync(
1534
+ outputPlan.outputs.spdx,
1535
+ stringifyJson(bomNSData.spdxJson, options.jsonPretty),
1536
+ );
1537
+ thoughtLog(`Let's save the SPDX file to "${outputPlan.outputs.spdx}".`);
1538
+ }
1539
+ } else if (!options.print) {
1540
+ if (outputPlan.formats.has("spdx") && bomNSData?.spdxJson) {
1541
+ console.log(stringifyJson(bomNSData.spdxJson, options.jsonPretty));
1542
+ } else if (bomNSData.bomJson) {
1543
+ console.log(stringifyJson(bomNSData.bomJson, options.jsonPretty));
1544
+ } else {
1545
+ console.log("Unable to produce BOM for", filePath);
1546
+ console.log("Try running the command with -t <type> or -r argument");
1547
+ }
1548
+ }
1191
1549
  thoughtEnd();
1192
1550
  // Automatically submit the bom data
1193
1551
  // biome-ignore lint/suspicious/noDoubleEquals: yargs passes true for empty values
@@ -1196,6 +1554,9 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
1196
1554
  await submitBom(options, bomNSData.bomJson);
1197
1555
  } catch (err) {
1198
1556
  console.log(err);
1557
+ if (cleanup) {
1558
+ cleanupSourceDir(srcDir);
1559
+ }
1199
1560
  process.exit(1);
1200
1561
  }
1201
1562
  }
@@ -1238,4 +1599,7 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
1238
1599
  console.log(allowListSuggestion);
1239
1600
  }
1240
1601
  }
1602
+ if (cleanup) {
1603
+ cleanupSourceDir(srcDir);
1604
+ }
1241
1605
  })();