@cdxgen/cdxgen 13.6.0 → 13.7.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 (264) hide show
  1. package/README.md +15 -8
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +256 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +25 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +18 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/pqc-migration.json +239 -0
  15. package/data/remediations.json +52 -0
  16. package/data/rules/cbom-security.yaml +4 -3
  17. package/data/rules/package-integrity.yaml +28 -0
  18. package/lib/audit/index.js +25 -3
  19. package/lib/cli/bomAssembly.js +80 -1
  20. package/lib/cli/cliOptions.js +121 -17
  21. package/lib/cli/index.js +609 -898
  22. package/lib/cli/jsBom.js +3 -0
  23. package/lib/cli/jvmBom.js +365 -40
  24. package/lib/cli/managedBom.js +19 -3
  25. package/lib/cli/nativeBom.js +10 -0
  26. package/lib/core/env.js +26 -0
  27. package/lib/core/fs.js +126 -3
  28. package/lib/core/globPatterns.js +218 -0
  29. package/lib/core/processTree.js +99 -12
  30. package/lib/core/projectConfig.js +332 -25
  31. package/lib/core/toolRegistry.js +15 -1
  32. package/lib/ecosystems/denoutils.js +2 -2
  33. package/lib/ecosystems/golem.js +3 -8
  34. package/lib/ecosystems/gradleutils.js +1082 -6
  35. package/lib/ecosystems/jsEvidence.js +90 -38
  36. package/lib/ecosystems/kosi.js +3 -5
  37. package/lib/ecosystems/npmVersionResolution.js +376 -0
  38. package/lib/ecosystems/parsers-python.js +89 -28
  39. package/lib/ecosystems/parsers-rust.js +117 -15
  40. package/lib/ecosystems/remote/huggingface.js +22 -0
  41. package/lib/ecosystems/rusi.js +2 -4
  42. package/lib/ecosystems/scalasem.js +2 -1
  43. package/lib/evinser/evinser.js +196 -34
  44. package/lib/evinser/scalasem.js +5 -1
  45. package/lib/helpers/agenticToolutils.js +112 -1
  46. package/lib/helpers/bomSigner.js +43 -9
  47. package/lib/helpers/jssSigner.js +1276 -0
  48. package/lib/inventory/agentBlueprint.js +193 -0
  49. package/lib/inventory/agentToolNames.js +145 -0
  50. package/lib/inventory/aiCollector.js +153 -7
  51. package/lib/inventory/aiInventory.js +44 -8
  52. package/lib/inventory/aiProviders.js +324 -0
  53. package/lib/inventory/bomUtils.js +8 -1
  54. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  55. package/lib/inventory/citations.js +31 -2
  56. package/lib/inventory/communityAiConfigParser.js +978 -80
  57. package/lib/inventory/cyclonedx20.js +152 -0
  58. package/lib/inventory/cyclonedx20Common.js +286 -0
  59. package/lib/inventory/cyclonedx20Derived.js +159 -0
  60. package/lib/inventory/cyclonedx20Parties.js +552 -0
  61. package/lib/inventory/cyclonedx20Read.js +768 -0
  62. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  63. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  64. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  65. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  66. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  67. package/lib/inventory/cyclonedx20Render.js +766 -0
  68. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  69. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  70. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  71. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  72. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  73. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  74. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  75. package/lib/inventory/cyclonedx20Tables.js +594 -0
  76. package/lib/inventory/deps.js +106 -13
  77. package/lib/inventory/dosai.js +236 -1
  78. package/lib/inventory/dosaiParsers.js +3 -6
  79. package/lib/inventory/dotnetRuntime.js +164 -14
  80. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  81. package/lib/inventory/envcontext.js +132 -38
  82. package/lib/inventory/evidenceUtils.js +2 -1
  83. package/lib/inventory/formulationParsers.js +95 -13
  84. package/lib/inventory/hbom.js +45 -6
  85. package/lib/inventory/hbomLoader.js +166 -0
  86. package/lib/inventory/mcpConfigParser.js +5 -0
  87. package/lib/inventory/occurrenceKey.js +90 -0
  88. package/lib/inventory/pqcMigration.js +426 -0
  89. package/lib/inventory/rustFormulationParser.js +21 -10
  90. package/lib/inventory/source.js +65 -6
  91. package/lib/parsers/huggingfaceManifest.js +9 -1
  92. package/lib/parsers/huggingfaceUtils.js +18 -0
  93. package/lib/parsers/kiroPermissions.js +166 -0
  94. package/lib/server/server.js +36 -34
  95. package/lib/stages/postgen/postgen.js +41 -1
  96. package/lib/stages/postgen/specVersionCompat.js +76 -0
  97. package/lib/validator/bomValidator.js +8 -1
  98. package/lib/validator/complianceEngine.js +30 -3
  99. package/lib/validator/complianceRules.js +1416 -4
  100. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  101. package/lib/validator/index.js +48 -7
  102. package/lib/validator/reporters/json.js +6 -0
  103. package/package.json +9 -9
  104. package/types/bin/repl.d.ts.map +1 -1
  105. package/types/lib/audit/index.d.ts +6 -2
  106. package/types/lib/audit/index.d.ts.map +1 -1
  107. package/types/lib/cli/bomAssembly.d.ts +16 -0
  108. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  109. package/types/lib/cli/cliOptions.d.ts +6 -0
  110. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  111. package/types/lib/cli/index.d.ts.map +1 -1
  112. package/types/lib/cli/jsBom.d.ts +2 -1
  113. package/types/lib/cli/jsBom.d.ts.map +1 -1
  114. package/types/lib/cli/jvmBom.d.ts +15 -0
  115. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  116. package/types/lib/cli/managedBom.d.ts.map +1 -1
  117. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  118. package/types/lib/core/boundedFile.d.ts +30 -0
  119. package/types/lib/core/boundedFile.d.ts.map +1 -1
  120. package/types/lib/core/env.d.ts +10 -0
  121. package/types/lib/core/env.d.ts.map +1 -1
  122. package/types/lib/core/fs.d.ts +4 -0
  123. package/types/lib/core/fs.d.ts.map +1 -1
  124. package/types/lib/core/globPatterns.d.ts +70 -0
  125. package/types/lib/core/globPatterns.d.ts.map +1 -0
  126. package/types/lib/core/paths.d.ts +9 -0
  127. package/types/lib/core/paths.d.ts.map +1 -1
  128. package/types/lib/core/processTree.d.ts +16 -5
  129. package/types/lib/core/processTree.d.ts.map +1 -1
  130. package/types/lib/core/projectConfig.d.ts +47 -8
  131. package/types/lib/core/projectConfig.d.ts.map +1 -1
  132. package/types/lib/core/toolRegistry.d.ts.map +1 -1
  133. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  134. package/types/lib/ecosystems/gradleutils.d.ts +248 -8
  135. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  136. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  137. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  139. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  140. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  141. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  142. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  143. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  144. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  145. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  146. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  147. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  148. package/types/lib/evinser/evinser.d.ts +61 -9
  149. package/types/lib/evinser/evinser.d.ts.map +1 -1
  150. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  151. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  152. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  153. package/types/lib/helpers/bomSigner.d.ts +20 -3
  154. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  155. package/types/lib/helpers/jssSigner.d.ts +288 -0
  156. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  157. package/types/lib/helpers/vsixutils.d.ts +303 -19
  158. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  159. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  160. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  161. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  162. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  163. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  164. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  165. package/types/lib/inventory/aiProviders.d.ts +85 -0
  166. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  167. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  168. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  169. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  170. package/types/lib/inventory/citations.d.ts +5 -0
  171. package/types/lib/inventory/citations.d.ts.map +1 -1
  172. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  173. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  174. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  175. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  176. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  177. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  178. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  179. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  180. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  181. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  182. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  183. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  184. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  185. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  186. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  187. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  188. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  189. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  190. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  191. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  192. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  193. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  194. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  195. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  196. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  197. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  198. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  199. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  200. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  201. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  202. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  203. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  204. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  205. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  206. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  207. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  208. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  209. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  210. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  211. package/types/lib/inventory/deps.d.ts +35 -2
  212. package/types/lib/inventory/deps.d.ts.map +1 -1
  213. package/types/lib/inventory/dosai.d.ts +19 -0
  214. package/types/lib/inventory/dosai.d.ts.map +1 -1
  215. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  216. package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
  217. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
  218. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  219. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  220. package/types/lib/inventory/envcontext.d.ts +22 -6
  221. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  222. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  224. package/types/lib/inventory/hbom.d.ts +11 -0
  225. package/types/lib/inventory/hbom.d.ts.map +1 -1
  226. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  227. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  228. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  229. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  230. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  231. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  232. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  233. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  234. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  235. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  236. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  237. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  238. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  239. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  240. package/types/lib/inventory/source.d.ts +15 -0
  241. package/types/lib/inventory/source.d.ts.map +1 -1
  242. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  243. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  244. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  245. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  246. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  247. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  248. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  249. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  250. package/types/lib/server/server.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  252. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  253. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  254. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  255. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  256. package/types/lib/validator/bomValidator.d.ts +5 -1
  257. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  258. package/types/lib/validator/complianceEngine.d.ts +4 -4
  259. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  260. package/types/lib/validator/complianceRules.d.ts +14 -2
  261. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  262. package/types/lib/validator/index.d.ts +10 -0
  263. package/types/lib/validator/index.d.ts.map +1 -1
  264. package/types/lib/validator/reporters/json.d.ts.map +1 -1
package/README.md CHANGED
@@ -121,7 +121,9 @@ Sections include:
121
121
  - [MCP Inventory](docs/MCP.md)
122
122
  - [REPL Guide](docs/REPL.md)
123
123
  - [Custom Properties](docs/CUSTOM_PROPERTIES.md)
124
- - [Claude Code Plugin](claude-plugin/README.md)
124
+ - [Coding Agent Plugin](agent-plugin/README.md)
125
+ - [Installing the plugin in coding agents](docs/AGENT_PLUGINS.md)
126
+ - [Agent CI recipes](docs/AGENT_CI.md)
125
127
  - [Deprecations](docs/DEPRECATIONS.md)
126
128
  - [Security Policy](SECURITY.md)
127
129
  - [Threat Model](docs/THREAT_MODEL.md)
@@ -216,7 +218,7 @@ Common asset names:
216
218
  #### Linux
217
219
 
218
220
  ```bash
219
- VERSION="v13.6.0"
221
+ VERSION="v13.7.0"
220
222
  ASSET="cdx-audit-linux-amd64"
221
223
  BASE_URL="https://github.com/cdxgen/cdxgen/releases/download/${VERSION}"
222
224
 
@@ -230,7 +232,7 @@ chmod +x "${ASSET}"
230
232
  #### macOS
231
233
 
232
234
  ```bash
233
- VERSION="v13.6.0"
235
+ VERSION="v13.7.0"
234
236
  ASSET="cdx-audit-darwin-arm64"
235
237
  BASE_URL="https://github.com/cdxgen/cdxgen/releases/download/${VERSION}"
236
238
 
@@ -244,7 +246,7 @@ chmod +x "${ASSET}"
244
246
  #### Windows (PowerShell)
245
247
 
246
248
  ```powershell
247
- $Version = "v13.6.0"
249
+ $Version = "v13.7.0"
248
250
  $Asset = "cdx-audit-windows-amd64.exe"
249
251
  $BaseUrl = "https://github.com/cdxgen/cdxgen/releases/download/$Version"
250
252
 
@@ -269,7 +271,7 @@ steps:
269
271
  env:
270
272
  GH_TOKEN: ${{ github.token }}
271
273
  run: |
272
- gh release download v13.6.0 \
274
+ gh release download v13.7.0 \
273
275
  --repo cdxgen/cdxgen \
274
276
  --pattern 'cdx-audit-linux-amd64' \
275
277
  --pattern 'cdx-audit-linux-amd64.sha256'
@@ -621,16 +623,21 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
621
623
  pnpm dlx @cdxgen/cdxgen-plugins-bin
622
624
  ```
623
625
 
624
- ## Claude Code plugin
626
+ ## Coding agent plugin
625
627
 
626
- The cdxgen toolkit is also packaged as a [Claude Code](https://claude.com/claude-code) plugin, so agents drive the correct command and flags — including the dry-run-first and no-auto-install safety constraints — instead of guessing.
628
+ The cdxgen toolkit is also packaged as a plugin for coding agents, so agents drive the correct command and flags, including the dry-run-first and no-auto-install safety constraints, instead of guessing. It is meant for Claude Code, OpenAI Codex, the GitHub Copilot CLI and app, Cursor, Gemini CLI, Kiro and opencode, and has been tried in Claude Code and Codex so far.
627
629
 
628
630
  ```shell
629
631
  claude plugin marketplace add cdxgen/cdxgen
630
632
  claude plugin install cdxgen@cdxgen-plugins
631
633
  ```
632
634
 
633
- It ships skills covering SBOM generation, container and rootfs scans, OBOM/HBOM, CBOM, AI-BOM and MCP inventory, BOM auditing, signing, validation, conversion, evidence enrichment, runtime tracing, and Dependency-Track publishing. See [claude-plugin/README.md](claude-plugin/README.md).
635
+ ```shell
636
+ codex plugin marketplace add cdxgen/cdxgen
637
+ codex plugin add cdxgen@cdxgen-plugins
638
+ ```
639
+
640
+ It ships skills covering SBOM generation, container and rootfs scans, OBOM/HBOM, CBOM, AI-BOM and MCP inventory, BOM auditing, signing, validation, conversion, evidence enrichment, runtime tracing, and Dependency-Track publishing. See [agent-plugin/README.md](agent-plugin/README.md) and the [installation guide](docs/AGENT_PLUGINS.md) for each client. To run cdxgen through an agent in GitHub Actions, see the [agent CI recipes](docs/AGENT_CI.md).
634
641
 
635
642
  ## Rust accelerator (cdxrs)
636
643
 
package/bin/audit.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { writeFileSync } from "node:fs";
4
3
  import { dirname } from "node:path";
5
4
  import process from "node:process";
6
5
 
@@ -13,6 +12,7 @@ import {
13
12
  retrieveCdxgenVersion,
14
13
  safeExistsSync,
15
14
  safeMkdirSync,
15
+ safeWriteSync,
16
16
  } from "../lib/ecosystems/utils.js";
17
17
 
18
18
  const args = yargs(hideBin(process.argv))
@@ -193,7 +193,7 @@ function writeOrPrint(output, outputPath) {
193
193
  if (!safeExistsSync(parentDir)) {
194
194
  safeMkdirSync(parentDir, { recursive: true });
195
195
  }
196
- writeFileSync(outputPath, output);
196
+ safeWriteSync(outputPath, output);
197
197
  }
198
198
 
199
199
  (async () => {
@@ -235,10 +235,13 @@ function writeOrPrint(output, outputPath) {
235
235
  report: args.report,
236
236
  });
237
237
  writeOrPrint(finalized.output, reportFile);
238
- process.exit(finalized.exitCode);
238
+ // exitCode instead of process.exit lets a piped stdout write drain before
239
+ // the process ends, and the finally block below still runs.
240
+ process.exitCode = finalized.exitCode;
239
241
  } catch (error) {
240
242
  console.error(error.message);
241
- process.exit(1);
243
+ // exitCode instead of process.exit lets pending stderr output drain.
244
+ process.exitCode = 1;
242
245
  } finally {
243
246
  progressTracker.stop();
244
247
  }
package/bin/cdxgen.js CHANGED
@@ -83,13 +83,24 @@ import {
83
83
  createOutputPlan,
84
84
  getOutputDirectory,
85
85
  } from "../lib/helpers/exportUtils.js";
86
+ import {
87
+ generateJssKeyPair,
88
+ signJssBom,
89
+ verifyJssBom,
90
+ } from "../lib/helpers/jssSigner.js";
86
91
  import {
87
92
  DEFAULT_CDX_SPEC_VERSION,
88
93
  getSupportedCycloneDxComponentTypes,
94
+ isCycloneDx20SpecVersion,
89
95
  isCycloneDxBom,
90
96
  normalizeCycloneDxComponentTypeFilter,
91
97
  toCycloneDxSpecVersionString,
92
98
  } from "../lib/inventory/bomUtils.js";
99
+ import {
100
+ DEPENDENCY_TRACK_CDX20_ERROR,
101
+ getInternalBom,
102
+ refreshRenderedBom,
103
+ } from "../lib/inventory/cyclonedx20.js";
93
104
  import {
94
105
  displaySelfThreatModel,
95
106
  printActivitySummary,
@@ -155,16 +166,37 @@ for (const configPattern of PROJECT_CONFIG_FILENAMES) {
155
166
  continue;
156
167
  }
157
168
  try {
158
- if (configPath.endsWith(".yml") || configPath.endsWith(".yaml")) {
159
- config = _load(fs.readFileSync(configPath, "utf-8"));
160
- } else {
161
- config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
162
- }
169
+ const parsedConfig =
170
+ configPath.endsWith(".yml") || configPath.endsWith(".yaml")
171
+ ? _load(fs.readFileSync(configPath, "utf-8"))
172
+ : JSON.parse(fs.readFileSync(configPath, "utf-8"));
163
173
  if (isSecureMode || DEBUG_MODE) {
164
174
  console.log(`Config file '${configPath}' loaded successfully.`);
165
175
  }
166
- const sanitized = sanitizeProjectConfig(config, dirname(configPath));
176
+ const sanitized = sanitizeProjectConfig(parsedConfig, dirname(configPath));
167
177
  config = sanitized.config;
178
+ for (const key of sanitized.operatorOnly) {
179
+ console.warn(
180
+ `\x1b[1;35mConfig file '${configPath}' sets '${key}', which only the command line or a CDXGEN_ environment variable may set. Ignoring it.\x1b[0m`,
181
+ );
182
+ }
183
+ for (const projectType of sanitized.hostScopeTypes) {
184
+ console.warn(
185
+ `\x1b[1;35mConfig file '${configPath}' selects the project type '${projectType}', which inventories this host rather than the project. Ignoring it. Pass '-t ${projectType}' on the command line if this is intentional.\x1b[0m`,
186
+ );
187
+ }
188
+ for (const key of sanitized.ignored) {
189
+ console.warn(
190
+ `Config file '${configPath}' sets '${key}', which is not an option a config file can set. Ignoring it.`,
191
+ );
192
+ }
193
+ for (const { key, pattern, reason } of sanitized.refusedPatterns) {
194
+ const shownPattern =
195
+ pattern.length > 80 ? `${pattern.slice(0, 77)}...` : pattern;
196
+ console.warn(
197
+ `\x1b[1;35mConfig file '${configPath}' sets the ${key} pattern '${shownPattern}', which ${reason}. Ignoring it. Pass the pattern on the command line if this is intentional.\x1b[0m`,
198
+ );
199
+ }
168
200
  for (const entry of sanitized.rejected) {
169
201
  console.warn(
170
202
  `\x1b[1;35mConfig file '${configPath}' sets '${entry}', which points outside the project directory. Ignoring it. Pass the option on the command line if this is intentional.\x1b[0m`,
@@ -425,7 +457,7 @@ const args = _yargs
425
457
  .option("generate-key-and-sign", {
426
458
  type: "boolean",
427
459
  description:
428
- "Generate a public/private key pair for SBOM_SIGN_ALGORITHM (RS512 by default) and then sign the generated SBOM using the JSON Signature Format (JSF).",
460
+ "Generate a public/private key pair for SBOM_SIGN_ALGORITHM and then sign the generated SBOM: with the JSON Signature Format (JSF, RS512 by default) at 1.x, or the JSON Signature Scheme (JSS, ITU-T X.590; Ed25519 by default) at --spec-version 2.0.",
429
461
  })
430
462
  .option("server", {
431
463
  type: "boolean",
@@ -634,7 +666,7 @@ const args = _yargs
634
666
  type: "boolean",
635
667
  default: false,
636
668
  description:
637
- "Generate formulation section with git metadata and build tools. Defaults to false.",
669
+ "Generate formulation section with git metadata, build tools and build inputs. On by default when CI=true, for BOM audits, the research and introspect profiles, OS project types and the cbom command; --no-include-formulation turns it off.",
638
670
  })
639
671
  .option("include-crypto", {
640
672
  type: "boolean",
@@ -697,6 +729,8 @@ const args = _yargs
697
729
  "suggest-build-tools",
698
730
  "ruby-docker-install",
699
731
  "resolve-gradle-distribution",
732
+ "gradle-prefer-lockfile",
733
+ "gradle-verification-fallback",
700
734
  "jvm-tool-setup",
701
735
  ],
702
736
  })
@@ -1208,6 +1242,21 @@ try {
1208
1242
  console.error(error.message);
1209
1243
  process.exit(1);
1210
1244
  }
1245
+ // Dependency-Track ingests CycloneDX 1.x only. Refuse an upload (server URL
1246
+ // plus API key, the same condition the submission below uses) before
1247
+ // generating, so a long scan does not end in an upload that cannot succeed.
1248
+ // Server mode checks each request instead, since a request may choose its own
1249
+ // version.
1250
+ if (
1251
+ !options.server &&
1252
+ typeof options.serverUrl === "string" &&
1253
+ options.serverUrl.length > 0 &&
1254
+ options.apiKey &&
1255
+ isCycloneDx20SpecVersion(options.specVersion)
1256
+ ) {
1257
+ console.error(DEPENDENCY_TRACK_CDX20_ERROR);
1258
+ process.exit(1);
1259
+ }
1211
1260
  if (!options.projectType) {
1212
1261
  thoughtLog(
1213
1262
  "Ok, the user wants me to identify all the project types and generate a consolidated BOM document.",
@@ -1280,6 +1329,7 @@ if (options.lifecycle) {
1280
1329
  const isHbomOnlyInvocation = isHbomOnlyProjectTypes(options.projectType);
1281
1330
  const advancedWarnings = applyAdvancedOptionsImpl(options, {
1282
1331
  isSecureMode: isSecureModeActive(),
1332
+ invokedCommandName,
1283
1333
  });
1284
1334
  // The config file comes from the working directory, usually the scanned
1285
1335
  // project, so whether the project's own code may run stays the operator's
@@ -1644,6 +1694,99 @@ const signCycloneDxPayload = (jsonPayload, keyDir, target, options) => {
1644
1694
  return signedBom;
1645
1695
  };
1646
1696
 
1697
+ /**
1698
+ * Signs a CycloneDX 2.0 payload with JSS (ITU-T X.590) using the configured
1699
+ * or generated key: the root and the nested members the draft allows (see
1700
+ * lib/helpers/jssSigner.js). A generated key is checked against the fresh
1701
+ * signature, as the JSF path does.
1702
+ *
1703
+ * @param {string} jsonPayload Serialized CycloneDX 2.0 BOM
1704
+ * @param {string|undefined} keyDir Directory for --generate-key-and-sign keys
1705
+ * @param {string} target Output the signed BOM is written to (for the activity log)
1706
+ * @param {Object} options CLI options
1707
+ * @returns {Object} Signed BOM
1708
+ * @throws {Error} When signing was requested but cannot be completed
1709
+ */
1710
+ const signJssPayload = (jsonPayload, keyDir, target, options) => {
1711
+ // Signing a supplied key needs SBOM_SIGN_ALGORITHM (see needsBomSigning);
1712
+ // only --generate-key-and-sign reaches here without one.
1713
+ const algorithm = readEnvironmentVariable("SBOM_SIGN_ALGORITHM") || "Ed25519";
1714
+ const sbomSignPrivateKey = readEnvironmentVariable("SBOM_SIGN_PRIVATE_KEY", {
1715
+ sensitive: true,
1716
+ });
1717
+ const sbomSignPrivateKeyBase64 = readEnvironmentVariable(
1718
+ "SBOM_SIGN_PRIVATE_KEY_BASE64",
1719
+ { sensitive: true },
1720
+ );
1721
+ let privateKey;
1722
+ let publicKeyFile;
1723
+ if (options.generateKeyAndSign) {
1724
+ if (!keyDir) {
1725
+ throw new Error(
1726
+ "--generate-key-and-sign needs a file output (-o) to write the generated keys next to.",
1727
+ );
1728
+ }
1729
+ publicKeyFile = join(keyDir, "public.key");
1730
+ const privateKeyFile = join(keyDir, "private.key");
1731
+ const generated = generateJssKeyPair(algorithm);
1732
+ safeWriteSync(
1733
+ publicKeyFile,
1734
+ generated.publicKey.export({ type: "spki", format: "pem" }),
1735
+ );
1736
+ // The private key is readable by the owner only.
1737
+ safeWriteSync(
1738
+ privateKeyFile,
1739
+ generated.privateKey.export({ type: "pkcs8", format: "pem" }),
1740
+ { mode: 0o600 },
1741
+ );
1742
+ console.log(
1743
+ "Created public/private key pairs for testing purposes",
1744
+ publicKeyFile,
1745
+ privateKeyFile,
1746
+ );
1747
+ privateKey = generated.privateKey;
1748
+ } else if (sbomSignPrivateKey) {
1749
+ if (!safeExistsSync(sbomSignPrivateKey)) {
1750
+ throw new Error(
1751
+ `The SBOM_SIGN_PRIVATE_KEY file '${sbomSignPrivateKey}' was not found.`,
1752
+ );
1753
+ }
1754
+ recordSensitiveFileRead(sbomSignPrivateKey, {
1755
+ label: "SBOM signing private key",
1756
+ });
1757
+ privateKey = fs.readFileSync(sbomSignPrivateKey);
1758
+ } else if (sbomSignPrivateKeyBase64) {
1759
+ privateKey = Buffer.from(sbomSignPrivateKeyBase64, "base64");
1760
+ } else {
1761
+ throw new Error(
1762
+ `Signing a CycloneDX 2.0 document needs a private key: set SBOM_SIGN_PRIVATE_KEY (PEM), SBOM_SIGN_PRIVATE_KEY_BASE64, or pass --generate-key-and-sign. Algorithm in use: ${algorithm}.`,
1763
+ );
1764
+ }
1765
+ thoughtLog(`Signing the BOM for "${target}" with JSS (${algorithm}).`);
1766
+ const signedBom = signJssBom(JSON.parse(jsonPayload), {
1767
+ algorithm,
1768
+ privateKey,
1769
+ });
1770
+ if (publicKeyFile) {
1771
+ const verdict = verifyJssBom(signedBom, {
1772
+ publicKey: fs.readFileSync(publicKeyFile, "utf8"),
1773
+ requireAll: true,
1774
+ });
1775
+ if (!verdict.valid) {
1776
+ throw new Error(
1777
+ `The new JSS signature does not verify with ${publicKeyFile}.`,
1778
+ );
1779
+ }
1780
+ console.log(
1781
+ "SBOM signature is verifiable natively with the public key and the algorithm",
1782
+ publicKeyFile,
1783
+ algorithm,
1784
+ );
1785
+ }
1786
+ recordActivity({ kind: "sign", status: "completed", target });
1787
+ return signedBom;
1788
+ };
1789
+
1647
1790
  /**
1648
1791
  * Signs the BOM for an output when signing is configured. A failure is
1649
1792
  * recorded and returned so that the caller can stop with a non-zero exit
@@ -1664,6 +1807,24 @@ const signForOutput = (bomJson, jsonPayload, keyDir, target, options) => {
1664
1807
  });
1665
1808
  return { bomJson, jsonPayload };
1666
1809
  }
1810
+ if (isCycloneDx20SpecVersion(bomJson?.specVersion || options?.specVersion)) {
1811
+ // A CycloneDX 2.0 document signs with JSS (ITU-T X.590) instead of JSF.
1812
+ try {
1813
+ const signedBom = signJssPayload(jsonPayload, keyDir, target, options);
1814
+ return {
1815
+ bomJson: signedBom,
1816
+ jsonPayload: stringifyJson(signedBom, options.jsonPretty),
1817
+ };
1818
+ } catch (err) {
1819
+ recordActivity({
1820
+ kind: "sign",
1821
+ reason: err.message,
1822
+ status: "failed",
1823
+ target,
1824
+ });
1825
+ return { bomJson, jsonPayload, signingError: err };
1826
+ }
1827
+ }
1667
1828
  try {
1668
1829
  const signedBom = signCycloneDxPayload(
1669
1830
  jsonPayload,
@@ -2002,7 +2163,10 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2002
2163
  hasCriticalFindings,
2003
2164
  } = await import("../lib/stages/postgen/auditBom.js");
2004
2165
  thoughtLog("Let's run security audit...");
2005
- const postAuditFindings = await auditBom(bomNSData.bomJson, {
2166
+ // The audit and its annotations read the 1.x-shaped model; at 2.0 the
2167
+ // rendered document is refreshed once after the last mutation.
2168
+ const auditInternalBom = getInternalBom(bomNSData);
2169
+ const postAuditFindings = await auditBom(auditInternalBom, {
2006
2170
  ...options,
2007
2171
  getBomWithOras,
2008
2172
  });
@@ -2021,9 +2185,9 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2021
2185
  }
2022
2186
  }
2023
2187
  if (postAuditFindings.length && options.specVersion >= 1.4) {
2024
- bomNSData.bomJson.annotations = [
2025
- ...(bomNSData.bomJson.annotations || []),
2026
- ...formatAnnotations(postAuditFindings, bomNSData.bomJson),
2188
+ auditInternalBom.annotations = [
2189
+ ...(auditInternalBom.annotations || []),
2190
+ ...formatAnnotations(postAuditFindings, auditInternalBom),
2027
2191
  ];
2028
2192
  thoughtLog(
2029
2193
  `Embedded ${postAuditFindings.length} audit findings as CycloneDX annotations`,
@@ -2057,7 +2221,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2057
2221
  const requiredAuditTargetCount = collectAuditTargets(
2058
2222
  [
2059
2223
  {
2060
- bomJson: bomNSData.bomJson,
2224
+ bomJson: auditInternalBom,
2061
2225
  source: filePath,
2062
2226
  },
2063
2227
  ],
@@ -2078,7 +2242,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2078
2242
  predictiveReport = await runAuditFromBoms(
2079
2243
  [
2080
2244
  {
2081
- bomJson: bomNSData.bomJson,
2245
+ bomJson: auditInternalBom,
2082
2246
  source: filePath,
2083
2247
  },
2084
2248
  ],
@@ -2115,14 +2279,14 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2115
2279
  }
2116
2280
  const predictiveAnnotations = formatPredictiveAnnotations(
2117
2281
  predictiveReport,
2118
- bomNSData.bomJson,
2282
+ auditInternalBom,
2119
2283
  {
2120
2284
  minSeverity: options.bomAuditMinSeverity,
2121
2285
  },
2122
2286
  );
2123
2287
  if (predictiveAnnotations.length && options.specVersion >= 1.4) {
2124
- bomNSData.bomJson.annotations = [
2125
- ...(bomNSData.bomJson.annotations || []),
2288
+ auditInternalBom.annotations = [
2289
+ ...(auditInternalBom.annotations || []),
2126
2290
  ...predictiveAnnotations,
2127
2291
  ];
2128
2292
  thoughtLog(
@@ -2165,9 +2329,11 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2165
2329
  target: internalCycloneDxInputPath,
2166
2330
  });
2167
2331
  } else {
2332
+ // Evinse reads and writes the 1.x shape, so the temp input is the
2333
+ // internal model, not the rendered 2.0 document.
2168
2334
  safeWriteSync(
2169
2335
  internalCycloneDxInputPath,
2170
- stringifyJson(bomNSData.bomJson, options.jsonPretty),
2336
+ stringifyJson(getInternalBom(bomNSData), options.jsonPretty),
2171
2337
  );
2172
2338
  }
2173
2339
  }
@@ -2197,7 +2363,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2197
2363
  if (!options.projectType) {
2198
2364
  const evidenceLanguage = evinserModule.defaultEvidenceLanguage(
2199
2365
  filePath,
2200
- bomNSData?.bomJson,
2366
+ getInternalBom(bomNSData),
2201
2367
  options,
2202
2368
  );
2203
2369
  if (evidenceLanguage) {
@@ -2209,10 +2375,17 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2209
2375
  "Skipping the evidence analysis since the BOM lists no package in a language it supports. Pass -t to choose the language.",
2210
2376
  );
2211
2377
  if (bomNSData) {
2212
- bomNSData.bomJson = applyEvidenceBasedFilter(
2213
- bomNSData.bomJson,
2378
+ // The filter mutates the 1.x model; the rendered 2.0 document is
2379
+ // refreshed below, once, after this last mutation.
2380
+ const filteredBom = applyEvidenceBasedFilter(
2381
+ getInternalBom(bomNSData),
2214
2382
  options,
2215
2383
  );
2384
+ if (bomNSData.internalBomJson) {
2385
+ bomNSData.internalBomJson = filteredBom;
2386
+ } else {
2387
+ bomNSData.bomJson = filteredBom;
2388
+ }
2216
2389
  }
2217
2390
  } else {
2218
2391
  const evinseOptions = evinserModule.buildEvinseOptions(
@@ -2220,6 +2393,12 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2220
2393
  args,
2221
2394
  internalCycloneDxInputPath || options.output,
2222
2395
  );
2396
+ // The analysis reads the in-memory 1.x model. At 2.0 that model is
2397
+ // labelled 2.0 but is not a 2.0 document, so it is handed over
2398
+ // directly instead of being read back from the temporary file.
2399
+ if (bomNSData?.internalBomJson) {
2400
+ evinseOptions.inputBomJson = bomNSData.internalBomJson;
2401
+ }
2223
2402
  try {
2224
2403
  const dbObjMap = await evinserModule.prepareDB(evinseOptions);
2225
2404
  if (dbObjMap) {
@@ -2229,16 +2408,31 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2229
2408
  );
2230
2409
  const evinseJson = await evinserModule.createEvinseFile(
2231
2410
  sliceArtefacts,
2232
- evinseOptions,
2411
+ { ...evinseOptions, returnInternalModel: true },
2233
2412
  );
2234
2413
  // The scope filter is deferred until here so the analyzers get to
2235
- // prove that an optional dependency is actually used.
2236
- bomNSData.bomJson = applyEvidenceBasedFilter(evinseJson, options);
2237
- if (options.print && evinseJson) {
2238
- printOccurrences(evinseJson);
2239
- printCallStack(evinseJson);
2414
+ // prove that an optional dependency is actually used. At 2.0
2415
+ // createEvinseFile runs postProcess again and would return the
2416
+ // rendered 2.0 document; `returnInternalModel` asks for the 1.x
2417
+ // model instead, which becomes the internal model and is
2418
+ // re-rendered below.
2419
+ const evinseInternalBom = evinseJson;
2420
+ if (bomNSData.internalBomJson) {
2421
+ bomNSData.internalBomJson = applyEvidenceBasedFilter(
2422
+ evinseInternalBom,
2423
+ options,
2424
+ );
2425
+ } else {
2426
+ bomNSData.bomJson = applyEvidenceBasedFilter(
2427
+ evinseInternalBom,
2428
+ options,
2429
+ );
2430
+ }
2431
+ if (options.print && evinseInternalBom) {
2432
+ printOccurrences(evinseInternalBom);
2433
+ printCallStack(evinseInternalBom);
2240
2434
  printReachables(sliceArtefacts);
2241
- printServices(evinseJson);
2435
+ printServices(evinseInternalBom);
2242
2436
  }
2243
2437
  }
2244
2438
  } catch (err) {
@@ -2253,6 +2447,10 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2253
2447
  }
2254
2448
  }
2255
2449
  }
2450
+ // The last post-postProcess mutation is done; at 2.0 the rendered document
2451
+ // is refreshed from the internal model once, before validation, signing
2452
+ // and writing. At 1.x this is a no-op.
2453
+ refreshRenderedBom(bomNSData, options);
2256
2454
  // Perform automatic validation
2257
2455
  if (options.validate && bomNSData?.bomJson) {
2258
2456
  thoughtLog("Wait, let's check the generated BOM file for any issues.");
@@ -2306,7 +2504,12 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2306
2504
  target: "spdx",
2307
2505
  });
2308
2506
  } else {
2309
- bomNSData.spdxJson = convertCycloneDxToSpdx(bomNSData.bomJson, options);
2507
+ // The SPDX converter reads the 1.x shape; at 2.0 that is the internal
2508
+ // model.
2509
+ bomNSData.spdxJson = convertCycloneDxToSpdx(
2510
+ getInternalBom(bomNSData),
2511
+ options,
2512
+ );
2310
2513
  recordActivity({
2311
2514
  kind: "convert",
2312
2515
  status: "completed",
@@ -2509,6 +2712,20 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2509
2712
  status: "blocked",
2510
2713
  target: options.serverUrl,
2511
2714
  });
2715
+ } else if (isCycloneDx20SpecVersion(bomNSData.bomJson?.specVersion)) {
2716
+ // Unreachable from the CLI, which refuses this before generating; kept
2717
+ // so a 2.0 document can never reach Dependency-Track.
2718
+ recordActivity({
2719
+ kind: "submit",
2720
+ reason: DEPENDENCY_TRACK_CDX20_ERROR,
2721
+ status: "blocked",
2722
+ target: options.serverUrl,
2723
+ });
2724
+ console.error(DEPENDENCY_TRACK_CDX20_ERROR);
2725
+ if (cleanup) {
2726
+ cleanupSourceDir(srcDir);
2727
+ }
2728
+ process.exit(1);
2512
2729
  } else {
2513
2730
  try {
2514
2731
  recordActivity({
@@ -2547,7 +2764,8 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2547
2764
  try {
2548
2765
  const { buildPublishCollectionPayload, publishTeaCollection } =
2549
2766
  await import("../lib/ecosystems/tea.js");
2550
- const parentComponent = bomNSData.bomJson.metadata?.component || {};
2767
+ const parentComponent =
2768
+ getInternalBom(bomNSData).metadata?.component || {};
2551
2769
  const projectName =
2552
2770
  options.teaCollectionName ||
2553
2771
  parentComponent.name ||
@@ -2623,17 +2841,18 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
2623
2841
  thoughtLog("BOM file is also available in .proto format!");
2624
2842
  }
2625
2843
  }
2626
- if (options.print && bomNSData.bomJson?.components) {
2627
- printSummary(bomNSData.bomJson);
2844
+ if (options.print && getInternalBom(bomNSData)?.components) {
2845
+ const printBom = getInternalBom(bomNSData);
2846
+ printSummary(printBom);
2628
2847
  if (options.includeFormulation) {
2629
- printFormulation(bomNSData.bomJson);
2848
+ printFormulation(printBom);
2630
2849
  }
2631
- printDependencyTree(bomNSData.bomJson);
2632
- printTable(bomNSData.bomJson);
2850
+ printDependencyTree(printBom);
2851
+ printTable(printBom);
2633
2852
  // CBOM related print
2634
2853
  if (options.includeCrypto) {
2635
- printTable(bomNSData.bomJson, ["cryptographic-asset"]);
2636
- printDependencyTree(bomNSData.bomJson, "provides");
2854
+ printTable(printBom, ["cryptographic-asset"]);
2855
+ printDependencyTree(printBom, "provides");
2637
2856
  }
2638
2857
  }
2639
2858
  if (isDryRun || DEBUG_MODE) {