@cdxgen/cdxgen 13.2.0 → 13.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 (311) hide show
  1. package/README.md +33 -14
  2. package/bin/cdxgen.js +310 -138
  3. package/bin/evinse.js +22 -0
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +119 -15
  6. package/bin/tracebom.js +69 -5
  7. package/bin/validate.js +107 -19
  8. package/bin/verify.js +172 -63
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +85 -0
  11. package/data/rules/dependency-sources.yaml +67 -0
  12. package/data/rules/scala-evidence.yaml +115 -0
  13. package/lib/audit/index.js +38 -6
  14. package/lib/cli/bomAssembly.js +105 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +156 -58
  17. package/lib/cli/jsBom.js +99 -53
  18. package/lib/cli/jvmBom.js +708 -147
  19. package/lib/cli/managedBom.js +415 -224
  20. package/lib/cli/nativeBom.js +264 -44
  21. package/lib/core/activity.js +12 -0
  22. package/lib/core/env.js +22 -15
  23. package/lib/core/fs.js +176 -7
  24. package/lib/core/httpClient.js +159 -24
  25. package/lib/core/metadataProperties.js +97 -0
  26. package/lib/core/pluginRun.js +184 -0
  27. package/lib/core/processTree.js +100 -0
  28. package/lib/core/projectConfig.js +31 -4
  29. package/lib/core/runState.js +39 -0
  30. package/lib/core/toml.js +72 -0
  31. package/lib/ecosystems/asarutils.js +4 -2
  32. package/lib/ecosystems/bunutils.js +274 -108
  33. package/lib/ecosystems/cargoCache.js +231 -8
  34. package/lib/ecosystems/caxa.js +434 -2
  35. package/lib/ecosystems/cmakeResolver.js +76 -21
  36. package/lib/ecosystems/cppBuildContext.js +631 -0
  37. package/lib/ecosystems/cppEvidence.js +399 -51
  38. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  39. package/lib/ecosystems/denoutils.js +93 -66
  40. package/lib/ecosystems/ecosystems.js +1983 -793
  41. package/lib/ecosystems/golem.js +28 -12
  42. package/lib/ecosystems/gradleutils.js +356 -37
  43. package/lib/ecosystems/jsEvidence.js +104 -44
  44. package/lib/ecosystems/kosi.js +31 -7
  45. package/lib/ecosystems/npmutils.js +174 -9
  46. package/lib/ecosystems/parsers-dotnet.js +616 -159
  47. package/lib/ecosystems/parsers-elm.js +46 -28
  48. package/lib/ecosystems/parsers-gleam.js +3 -4
  49. package/lib/ecosystems/parsers-go.js +371 -70
  50. package/lib/ecosystems/parsers-js.js +263 -76
  51. package/lib/ecosystems/parsers-julia.js +2 -3
  52. package/lib/ecosystems/parsers-jvm.js +204 -12
  53. package/lib/ecosystems/parsers-misc.js +929 -216
  54. package/lib/ecosystems/parsers-python.js +167 -24
  55. package/lib/ecosystems/parsers-rust.js +28 -22
  56. package/lib/ecosystems/parsers-terraform.js +1573 -19
  57. package/lib/ecosystems/pythonutils.js +102 -6
  58. package/lib/ecosystems/remote/huggingface.js +3 -0
  59. package/lib/ecosystems/rubyutils.js +1492 -182
  60. package/lib/ecosystems/rusi.js +54 -10
  61. package/lib/ecosystems/sbtutils.js +243 -187
  62. package/lib/ecosystems/scalacliutils.js +342 -0
  63. package/lib/ecosystems/scalasem.js +2618 -0
  64. package/lib/ecosystems/terraformRegistry.js +288 -0
  65. package/lib/ecosystems/utils.js +2 -0
  66. package/lib/ecosystems/vendoredCode.js +156 -0
  67. package/lib/evinser/evinser.js +1008 -191
  68. package/lib/evinser/swiftsem.js +1914 -302
  69. package/lib/helpers/agenticToolutils.js +2 -2
  70. package/lib/helpers/bomSigner.js +1101 -222
  71. package/lib/inventory/aiCollector.js +4 -1
  72. package/lib/inventory/analyzer.js +110 -127
  73. package/lib/inventory/atomUtils.js +703 -43
  74. package/lib/inventory/auditCategories.js +7 -0
  75. package/lib/inventory/buildToolRateLimit.js +144 -0
  76. package/lib/inventory/cacheCommands.js +2 -1
  77. package/lib/inventory/cdxrs.js +103 -12
  78. package/lib/inventory/cmakeBuildDirs.js +254 -0
  79. package/lib/inventory/deps.js +501 -124
  80. package/lib/inventory/depsUtils.js +183 -11
  81. package/lib/inventory/display.js +4 -5
  82. package/lib/inventory/dosai.js +798 -57
  83. package/lib/inventory/dosaiParsers.js +310 -15
  84. package/lib/inventory/dynamic.js +90 -6
  85. package/lib/inventory/evidenceUtils.js +166 -113
  86. package/lib/inventory/fetchBatch.js +690 -55
  87. package/lib/inventory/fetchDiskCache.js +597 -0
  88. package/lib/inventory/forgeEnricher.js +14 -5
  89. package/lib/inventory/jvmLocalRepos.js +917 -0
  90. package/lib/inventory/mcpConfigParser.js +2 -3
  91. package/lib/inventory/parentSelection.js +146 -0
  92. package/lib/inventory/protobom.js +8 -1
  93. package/lib/inventory/purl.js +152 -3
  94. package/lib/inventory/rustFormulationParser.js +3 -4
  95. package/lib/inventory/sbomDocument.js +2 -70
  96. package/lib/inventory/scalaCoords.js +153 -0
  97. package/lib/inventory/source.js +45 -27
  98. package/lib/inventory/spdx.js +101 -0
  99. package/lib/inventory/traceRunner.js +295 -25
  100. package/lib/inventory/tracedCrypto.js +294 -0
  101. package/lib/managers/binary.js +115 -24
  102. package/lib/managers/docker.js +467 -46
  103. package/lib/managers/oci.js +63 -3
  104. package/lib/parsers/cmakeCache.js +69 -0
  105. package/lib/parsers/cmakeLists.js +466 -0
  106. package/lib/parsers/cmakePresets.js +400 -0
  107. package/lib/parsers/compileCommands.js +557 -0
  108. package/lib/parsers/largeJson.js +454 -0
  109. package/lib/parsers/npmrc.js +19 -1
  110. package/lib/parsers/strictJson.js +322 -0
  111. package/lib/parsers/terraformConfig.js +1073 -0
  112. package/lib/parsers/terraformSource.js +652 -0
  113. package/lib/server/server.js +23 -0
  114. package/lib/stages/postgen/introspection/markers.js +1 -1
  115. package/lib/stages/postgen/postgen.js +83 -16
  116. package/lib/stages/postgen/specVersionCompat.js +32 -0
  117. package/lib/validator/bomValidator.js +6 -16
  118. package/lib/validator/index.js +64 -11
  119. package/lib/validator/reporters/console.js +3 -0
  120. package/lib/validator/reporters/json.js +2 -0
  121. package/lib/validator/reporters/sarif.js +7 -0
  122. package/package.json +23 -23
  123. package/types/lib/audit/index.d.ts.map +1 -1
  124. package/types/lib/cli/bomAssembly.d.ts +2 -1
  125. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  126. package/types/lib/cli/cliOptions.d.ts +33 -1
  127. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  128. package/types/lib/cli/index.d.ts.map +1 -1
  129. package/types/lib/cli/jsBom.d.ts.map +1 -1
  130. package/types/lib/cli/jvmBom.d.ts +24 -1
  131. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  132. package/types/lib/cli/managedBom.d.ts +7 -4
  133. package/types/lib/cli/managedBom.d.ts.map +1 -1
  134. package/types/lib/cli/nativeBom.d.ts +3 -1
  135. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  136. package/types/lib/core/activity.d.ts.map +1 -1
  137. package/types/lib/core/env.d.ts +9 -0
  138. package/types/lib/core/env.d.ts.map +1 -1
  139. package/types/lib/core/fs.d.ts +55 -0
  140. package/types/lib/core/fs.d.ts.map +1 -1
  141. package/types/lib/core/httpClient.d.ts +13 -8
  142. package/types/lib/core/httpClient.d.ts.map +1 -1
  143. package/types/lib/core/metadataProperties.d.ts +39 -0
  144. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  145. package/types/lib/core/pluginRun.d.ts +97 -0
  146. package/types/lib/core/pluginRun.d.ts.map +1 -0
  147. package/types/lib/core/processTree.d.ts +36 -0
  148. package/types/lib/core/processTree.d.ts.map +1 -0
  149. package/types/lib/core/projectConfig.d.ts +6 -2
  150. package/types/lib/core/projectConfig.d.ts.map +1 -1
  151. package/types/lib/core/runState.d.ts +29 -0
  152. package/types/lib/core/runState.d.ts.map +1 -0
  153. package/types/lib/core/toml.d.ts +20 -0
  154. package/types/lib/core/toml.d.ts.map +1 -0
  155. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  156. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  157. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  158. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  159. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  160. package/types/lib/ecosystems/caxa.d.ts +14 -0
  161. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  162. package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
  163. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  164. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  165. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  166. package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
  167. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  168. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  169. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  170. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  171. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  172. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  173. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  174. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  175. package/types/lib/ecosystems/gradleutils.d.ts +70 -4
  176. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  177. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  178. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  179. package/types/lib/ecosystems/kosi.d.ts +13 -0
  180. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  181. package/types/lib/ecosystems/npmutils.d.ts +57 -0
  182. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  183. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  184. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  185. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  186. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  187. package/types/lib/ecosystems/parsers-go.d.ts +54 -2
  188. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  189. package/types/lib/ecosystems/parsers-js.d.ts +16 -5
  190. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  191. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  192. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  193. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  194. package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
  195. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  196. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  197. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  198. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  199. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  200. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  201. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  202. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  203. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  204. package/types/lib/ecosystems/rubyutils.d.ts +164 -3
  205. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  206. package/types/lib/ecosystems/rusi.d.ts +13 -0
  207. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  208. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  209. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  210. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  211. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  212. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  213. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  214. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  215. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  216. package/types/lib/ecosystems/utils.d.ts +2 -2
  217. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  218. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  219. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  220. package/types/lib/evinser/evinser.d.ts +132 -15
  221. package/types/lib/evinser/evinser.d.ts.map +1 -1
  222. package/types/lib/evinser/swiftsem.d.ts +280 -15
  223. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  224. package/types/lib/helpers/bomSigner.d.ts +182 -13
  225. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  226. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  227. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  228. package/types/lib/inventory/atomUtils.d.ts +219 -5
  229. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  230. package/types/lib/inventory/auditCategories.d.ts +3 -0
  231. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  232. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  233. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  234. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  235. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  236. package/types/lib/inventory/cdxrs.d.ts +8 -3
  237. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  238. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  239. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  240. package/types/lib/inventory/deps.d.ts +59 -1
  241. package/types/lib/inventory/deps.d.ts.map +1 -1
  242. package/types/lib/inventory/depsUtils.d.ts +15 -0
  243. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  244. package/types/lib/inventory/display.d.ts.map +1 -1
  245. package/types/lib/inventory/dosai.d.ts +121 -9
  246. package/types/lib/inventory/dosai.d.ts.map +1 -1
  247. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  248. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  249. package/types/lib/inventory/dynamic.d.ts +10 -0
  250. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  251. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  252. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  253. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  254. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  255. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  256. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  257. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  258. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  259. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  260. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  261. package/types/lib/inventory/parentSelection.d.ts +34 -0
  262. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  263. package/types/lib/inventory/plugins.d.ts.map +1 -1
  264. package/types/lib/inventory/protobom.d.ts.map +1 -1
  265. package/types/lib/inventory/purl.d.ts +71 -2
  266. package/types/lib/inventory/purl.d.ts.map +1 -1
  267. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  268. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  269. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  270. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  271. package/types/lib/inventory/source.d.ts.map +1 -1
  272. package/types/lib/inventory/spdx.d.ts +9 -0
  273. package/types/lib/inventory/spdx.d.ts.map +1 -1
  274. package/types/lib/inventory/traceRunner.d.ts +59 -1
  275. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  276. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  277. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  278. package/types/lib/managers/binary.d.ts +30 -1
  279. package/types/lib/managers/binary.d.ts.map +1 -1
  280. package/types/lib/managers/docker.d.ts +29 -0
  281. package/types/lib/managers/docker.d.ts.map +1 -1
  282. package/types/lib/managers/oci.d.ts +17 -1
  283. package/types/lib/managers/oci.d.ts.map +1 -1
  284. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  285. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  286. package/types/lib/parsers/cmakeLists.d.ts +77 -0
  287. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  288. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  289. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  290. package/types/lib/parsers/compileCommands.d.ts +115 -0
  291. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  292. package/types/lib/parsers/largeJson.d.ts +52 -0
  293. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  294. package/types/lib/parsers/npmrc.d.ts +5 -0
  295. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  296. package/types/lib/parsers/strictJson.d.ts +46 -0
  297. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  298. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  299. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  300. package/types/lib/parsers/terraformSource.d.ts +66 -0
  301. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  302. package/types/lib/server/server.d.ts.map +1 -1
  303. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  304. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  305. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  306. package/types/lib/validator/index.d.ts +18 -1
  307. package/types/lib/validator/index.d.ts.map +1 -1
  308. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  309. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  310. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
  311. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
package/bin/repl.js CHANGED
@@ -1741,6 +1741,92 @@ cdxgenRepl.defineCommand("golemcoverage", {
1741
1741
  this.displayPrompt();
1742
1742
  },
1743
1743
  });
1744
+ function hasScalasemProperties(component) {
1745
+ return Boolean(
1746
+ component?.properties?.some((property) =>
1747
+ property?.name?.startsWith("cdx:scalasem:"),
1748
+ ),
1749
+ );
1750
+ }
1751
+
1752
+ cdxgenRepl.defineCommand("scalasemsummary", {
1753
+ help: "summarize Scala Evinse/scalasem metadata and evidence coverage",
1754
+ action() {
1755
+ const interactiveBom = getInteractiveBom();
1756
+ if (!interactiveBom) {
1757
+ console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
1758
+ this.displayPrompt();
1759
+ return;
1760
+ }
1761
+ if (!hasScalasemProperties(interactiveBom.metadata?.component)) {
1762
+ console.log(
1763
+ "No scalasem properties found. Generate an evidence BOM with evinse -l scala or cdxgen --evidence to use this view.",
1764
+ );
1765
+ this.displayPrompt();
1766
+ return;
1767
+ }
1768
+ const rootComponent = interactiveBom.metadata?.component || {};
1769
+ const components = interactiveBom.components || [];
1770
+ const occurrences = components.reduce(
1771
+ (total, component) =>
1772
+ total + (component?.evidence?.occurrences?.length || 0),
1773
+ 0,
1774
+ );
1775
+ const occurrencesWithLine = components.reduce(
1776
+ (total, component) =>
1777
+ total +
1778
+ (component?.evidence?.occurrences || []).filter(
1779
+ (occurrence) => occurrence?.line,
1780
+ ).length,
1781
+ 0,
1782
+ );
1783
+ const callstacks = components.filter(
1784
+ (component) => component?.evidence?.callstack?.frames?.length,
1785
+ ).length;
1786
+ const cryptoAssets = components.filter(
1787
+ (component) => component?.type === "cryptographic-asset",
1788
+ ).length;
1789
+ printKeyValueTable("Scala Evinse / scalasem summary", [
1790
+ [
1791
+ "Schema version",
1792
+ getPropertyValue(rootComponent, "cdx:scalasem:schemaVersion"),
1793
+ ],
1794
+ [
1795
+ "Facts source",
1796
+ getPropertyValue(rootComponent, "cdx:scalasem:factsSource"),
1797
+ ],
1798
+ [
1799
+ "Scala versions",
1800
+ getPropertyValue(rootComponent, "cdx:scalasem:scalaVersions"),
1801
+ ],
1802
+ ["Platforms", getPropertyValue(rootComponent, "cdx:scalasem:platforms")],
1803
+ [
1804
+ "Files analyzed",
1805
+ getPropertyValue(rootComponent, "cdx:scalasem:filesAnalyzed"),
1806
+ ],
1807
+ ["Degraded", getPropertyValue(rootComponent, "cdx:scalasem:degraded")],
1808
+ ...(rootComponent.properties || [])
1809
+ .filter((property) =>
1810
+ property?.name?.startsWith("cdx:scalasem:diagnostic:"),
1811
+ )
1812
+ .map((property) => [
1813
+ `Diagnostic ${property.name.slice("cdx:scalasem:diagnostic:".length)}`,
1814
+ property.value,
1815
+ ]),
1816
+ [
1817
+ "Components with occurrences",
1818
+ components.filter((c) => c?.evidence?.occurrences?.length).length,
1819
+ ],
1820
+ ["Occurrences", occurrences],
1821
+ ["Occurrences with line", occurrencesWithLine],
1822
+ ["Components with call stacks", callstacks],
1823
+ ["Crypto assets", cryptoAssets],
1824
+ ["Services", (interactiveBom.services || []).length],
1825
+ ]);
1826
+ this.displayPrompt();
1827
+ },
1828
+ });
1829
+
1744
1830
  cdxgenRepl.defineCommand("auditfindings", {
1745
1831
  help: "summarize cdx-audit and bom-audit annotations from the loaded BOM",
1746
1832
  action() {
package/bin/sign.js CHANGED
@@ -11,11 +11,16 @@ import {
11
11
  retrieveCdxgenVersion,
12
12
  safeExistsSync,
13
13
  } from "../lib/ecosystems/utils.js";
14
- import { signBom } from "../lib/helpers/bomSigner.js";
14
+ import {
15
+ checkSignatureEntries,
16
+ displayValue,
17
+ signBom,
18
+ } from "../lib/helpers/bomSigner.js";
15
19
  import {
16
20
  getNonCycloneDxErrorMessage,
17
21
  isCycloneDxBom,
18
22
  } from "../lib/inventory/bomUtils.js";
23
+ import { parseStrictJson } from "../lib/parsers/strictJson.js";
19
24
 
20
25
  const _yargs = yargs(hideBin(process.argv));
21
26
 
@@ -31,12 +36,14 @@ const args = _yargs
31
36
  })
32
37
  .option("private-key", {
33
38
  alias: "k",
34
- description: "Private key in PEM format.",
39
+ description:
40
+ "Private key in PEM format, or the shared secret file for HS256, HS384, and HS512.",
35
41
  })
36
42
  .option("algorithm", {
37
43
  alias: "a",
38
44
  default: readEnvironmentVariable("SBOM_SIGN_ALGORITHM") || "RS512",
39
- description: "JSF Signature Algorithm (e.g., RS512, ES256, Ed25519).",
45
+ description:
46
+ "JSF Signature Algorithm (e.g., RS512, ES256, Ed25519). It must match the key type.",
40
47
  })
41
48
  .option("mode", {
42
49
  alias: "m",
@@ -45,27 +52,42 @@ const args = _yargs
45
52
  description:
46
53
  "Signature mode. Use 'signers' for multi-signing, 'chain' for sequential chaining.",
47
54
  })
55
+ .option("verify-existing-with", {
56
+ type: "array",
57
+ string: true,
58
+ description:
59
+ "Public key (PEM) of an earlier signer. Before a new entry is appended, every existing chain entry (--mode chain), or at least one existing entry (--mode signers), must verify with one of these keys.",
60
+ })
61
+ .option("allow-unverified-history", {
62
+ type: "boolean",
63
+ default: false,
64
+ description:
65
+ "With --mode chain or signers, append even when the existing signature entries cannot be verified with --verify-existing-with.",
66
+ })
67
+ .option("embed-public-key", {
68
+ type: "boolean",
69
+ default: true,
70
+ description:
71
+ "Embed the signer's public key in the root signature, which binds the signature to that key. Not used for HMAC (HS*) algorithms. Pass --no-embed-public-key to leave it out.",
72
+ })
48
73
  .option("key-id", {
49
74
  description:
50
75
  "Optional identifier for the key (keyId) to embed in the signature block.",
51
76
  })
52
77
  .option("sign-components", {
53
78
  type: "boolean",
54
- default: true,
55
79
  description:
56
- "Sign granular components. Disable (--no-sign-components) when appending multi-signatures.",
80
+ "Sign granular components. Defaults to true, or false when appending a signers or chain signature to a signed BOM.",
57
81
  })
58
82
  .option("sign-services", {
59
83
  type: "boolean",
60
- default: true,
61
84
  description:
62
- "Sign granular services. Disable (--no-sign-services) when appending multi-signatures.",
85
+ "Sign granular services. Defaults to true, or false when appending a signers or chain signature to a signed BOM.",
63
86
  })
64
87
  .option("sign-annotations", {
65
88
  type: "boolean",
66
- default: true,
67
89
  description:
68
- "Sign granular annotations. Disable (--no-sign-annotations) when appending multi-signatures.",
90
+ "Sign granular annotations. Defaults to true, or false when appending a signers or chain signature to a signed BOM.",
69
91
  })
70
92
  .option("attach", {
71
93
  type: "string",
@@ -97,7 +119,7 @@ const envPrivateKeyBase64 = readEnvironmentVariable(
97
119
 
98
120
  if (args.privateKey) {
99
121
  if (safeExistsSync(args.privateKey)) {
100
- privateKeyContent = fs.readFileSync(args.privateKey, "utf8");
122
+ privateKeyContent = fs.readFileSync(args.privateKey);
101
123
  hasPrivateKey = true;
102
124
  } else {
103
125
  console.error(`Private key file '${args.privateKey}' not found.`);
@@ -105,7 +127,7 @@ if (args.privateKey) {
105
127
  }
106
128
  } else if (envPrivateKeyFile) {
107
129
  if (safeExistsSync(envPrivateKeyFile)) {
108
- privateKeyContent = fs.readFileSync(envPrivateKeyFile, "utf8");
130
+ privateKeyContent = fs.readFileSync(envPrivateKeyFile);
109
131
  hasPrivateKey = true;
110
132
  } else {
111
133
  console.error(
@@ -115,9 +137,7 @@ if (args.privateKey) {
115
137
  }
116
138
  } else if (envPrivateKeyBase64) {
117
139
  try {
118
- privateKeyContent = Buffer.from(envPrivateKeyBase64, "base64").toString(
119
- "utf8",
120
- );
140
+ privateKeyContent = Buffer.from(envPrivateKeyBase64, "base64");
121
141
  hasPrivateKey = true;
122
142
  } catch {
123
143
  console.error(
@@ -147,8 +167,75 @@ function hasAnySignature(bomJson) {
147
167
  return false;
148
168
  }
149
169
 
170
+ function describeEntry(result) {
171
+ const parts = [];
172
+ if (result.algorithm !== undefined) {
173
+ parts.push(displayValue(result.algorithm));
174
+ }
175
+ if (result.keyId !== undefined) {
176
+ parts.push(`keyId '${displayValue(result.keyId)}'`);
177
+ }
178
+ return parts.length
179
+ ? `entry ${result.index} (${parts.join(", ")})`
180
+ : `entry ${result.index}`;
181
+ }
182
+
183
+ // A new chain entry covers every earlier entry, so the signer vouches for that
184
+ // history, and every entry must verify. A new signers entry covers the same
185
+ // content as the existing entries, so at least one must verify to show the
186
+ // content is still what an earlier signer signed.
187
+ function checkExistingSignatures(bomJson) {
188
+ const existing = bomJson.signature;
189
+ const otherForm = args.mode === "chain" ? "signers" : "chain";
190
+ if (
191
+ args.mode === "replace" ||
192
+ existing === undefined ||
193
+ (existing !== null &&
194
+ typeof existing === "object" &&
195
+ Object.hasOwn(existing, otherForm))
196
+ ) {
197
+ return;
198
+ }
199
+ const keys = (args.verifyExistingWith || []).map((keyFile) => {
200
+ if (!safeExistsSync(keyFile)) {
201
+ throw new Error(`Public key file '${keyFile}' not found.`);
202
+ }
203
+ return fs.readFileSync(keyFile, "utf8");
204
+ });
205
+ const results = checkSignatureEntries(bomJson, keys);
206
+ const unverified = results.filter((result) => !result.verified);
207
+ const chain = args.mode === "chain";
208
+ if (chain ? !unverified.length : unverified.length < results.length) {
209
+ return;
210
+ }
211
+ const details = unverified.map(
212
+ (result) =>
213
+ ` - ${describeEntry(result)}: ${result.reasons[0] || "no --verify-existing-with key was given"}`,
214
+ );
215
+ if (args.allowUnverifiedHistory) {
216
+ console.warn(
217
+ [
218
+ chain
219
+ ? `Warning: appending to a chain with ${unverified.length} unverified entry(s):`
220
+ : `Warning: appending a signers entry although none of the ${unverified.length} existing entry(s) verified:`,
221
+ ...details,
222
+ ].join("\n"),
223
+ );
224
+ return;
225
+ }
226
+ throw new Error(
227
+ [
228
+ chain
229
+ ? `the new chain entry would vouch for ${unverified.length} existing entry(s) that could not be verified:`
230
+ : `none of the ${unverified.length} existing signature entry(s) could be verified, so the content may have changed since it was signed:`,
231
+ ...details,
232
+ "Pass --verify-existing-with <public key> for each earlier signer, or --allow-unverified-history to sign anyway.",
233
+ ].join("\n"),
234
+ );
235
+ }
236
+
150
237
  try {
151
- const bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
238
+ const bomJson = parseStrictJson(fs.readFileSync(args.input));
152
239
  if (!isCycloneDxBom(bomJson)) {
153
240
  console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-sign"));
154
241
  process.exit(1);
@@ -156,9 +243,12 @@ try {
156
243
 
157
244
  let signedBom = bomJson;
158
245
  if (hasPrivateKey && privateKeyContent) {
246
+ checkExistingSignatures(bomJson);
159
247
  signedBom = signBom(bomJson, {
160
248
  privateKey: privateKeyContent,
161
249
  algorithm: args.algorithm,
250
+ embedPublicKey: args.embedPublicKey,
251
+ embedPublicKeyNested: false,
162
252
  keyId: args.keyId,
163
253
  mode: args.mode,
164
254
  signComponents: args.signComponents,
@@ -173,6 +263,20 @@ try {
173
263
  console.log(
174
264
  `Mode: ${args.mode} | Algorithm: ${args.algorithm}${args.keyId ? ` | KeyId: ${args.keyId}` : ""}`,
175
265
  );
266
+ const rootSignature = signedBom.signature;
267
+ const newEntry =
268
+ rootSignature?.signers?.at(-1) ??
269
+ rootSignature?.chain?.at(-1) ??
270
+ rootSignature;
271
+ if (
272
+ args.embedPublicKey &&
273
+ !String(args.algorithm).startsWith("HS") &&
274
+ newEntry?.publicKey === undefined
275
+ ) {
276
+ console.warn(
277
+ "Warning: this key type has no JWK form, so the public key was not embedded in the signature.",
278
+ );
279
+ }
176
280
  } else {
177
281
  if (!hasAnySignature(bomJson)) {
178
282
  console.error(
package/bin/tracebom.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  safeWriteSync,
15
15
  } from "../lib/ecosystems/utils.js";
16
16
  import { DEFAULT_CDX_SPEC_VERSION } from "../lib/inventory/bomUtils.js";
17
+ import { applySpecVersionCompatibility } from "../lib/stages/postgen/specVersionCompat.js";
17
18
 
18
19
  const _yargs = yargs(hideBin(process.argv));
19
20
 
@@ -79,7 +80,7 @@ const args = _yargs
79
80
  })
80
81
  .option("disable-network", {
81
82
  description:
82
- "Disable network inside sandbox. Automatically disabled when --trace-http-urls is set.",
83
+ "Disable network inside sandbox. Automatically disabled when --trace-http-urls or --proxy-egress is set.",
83
84
  default: true,
84
85
  type: "boolean",
85
86
  })
@@ -166,10 +167,60 @@ const args = _yargs
166
167
  })
167
168
  .option("trace-crypto", {
168
169
  description:
169
- "Enable eBPF-based cryptographic library and cipher suite tracing (Linux only).",
170
+ "Trace cryptographic libraries and TLS parameters: eBPF on Linux, crypto DLLs on Windows (elevated prompt), and TLS handshakes through --proxy-egress on every platform.",
170
171
  default: true,
171
172
  type: "boolean",
172
173
  })
174
+ .option("proxy-egress", {
175
+ description:
176
+ "Route all outbound traffic through safer-exec's hostname-pinning egress proxy. Only --allow-host (and --allow-url) hosts are reachable; allowed and denied targets are recorded as services. Enables the network.",
177
+ default: false,
178
+ type: "boolean",
179
+ })
180
+ .option("allow-loopback", {
181
+ description: "Allow the traced process to connect to loopback addresses.",
182
+ default: false,
183
+ type: "boolean",
184
+ })
185
+ .option("sandbox-dry-run", {
186
+ description:
187
+ "Deny every filesystem write and network connection and record the attempted operations as counts in the BOM metadata.",
188
+ default: false,
189
+ type: "boolean",
190
+ })
191
+ .option("policy", {
192
+ description:
193
+ "Apply a named safer-exec ecosystem policy (for example npm, pnpm, pypi, uv, maven, cargo, gomod, nuget) before the other sandbox options.",
194
+ type: "string",
195
+ })
196
+ .option("policy-file", {
197
+ description: "Apply a safer-exec JSON policy file.",
198
+ type: "string",
199
+ })
200
+ .option("block-interpreters", {
201
+ description:
202
+ "Block interpreters that carry sandbox or task-port exemptions (macOS only).",
203
+ default: false,
204
+ type: "boolean",
205
+ })
206
+ .option("deny-persistence-writes", {
207
+ description:
208
+ "Deny writes to auto-execution and persistence locations such as LaunchAgents, shell rc files and cron directories. Paths in --write-paths stay writable.",
209
+ default: false,
210
+ type: "boolean",
211
+ })
212
+ .option("private-tmp", {
213
+ description:
214
+ "Give the traced process private /tmp and /var/tmp mounts (Linux only).",
215
+ default: false,
216
+ type: "boolean",
217
+ })
218
+ .option("protect-home", {
219
+ description: "Isolate $HOME inside the sandbox (Linux only).",
220
+ choices: ["off", "read-only", "tmpfs"],
221
+ default: "off",
222
+ type: "string",
223
+ })
173
224
  .option("print", {
174
225
  description: "Print BOM to stdout.",
175
226
  default: false,
@@ -230,9 +281,10 @@ const options = {
230
281
  traceMaxMemoryMB: args.maxMemory,
231
282
  traceMaxProcesses: args.maxProcesses,
232
283
  traceTimeoutMs: args.timeout,
233
- traceDisableNetwork: args.traceHttpUrls
234
- ? false
235
- : (args.disableNetwork ?? true),
284
+ traceDisableNetwork:
285
+ args.traceHttpUrls || args.proxyEgress
286
+ ? false
287
+ : (args.disableNetwork ?? true),
236
288
  traceHTTPURLs: args.traceHttpUrls ?? false,
237
289
  tracePeriod: args.tracePeriod,
238
290
  traceMaxCPUCores: args.maxCpu,
@@ -265,6 +317,15 @@ const options = {
265
317
  ? args.blockExec.split(",").filter(Boolean)
266
318
  : [],
267
319
  traceCrypto: args.traceCrypto ?? true,
320
+ traceProxyEgress: args.proxyEgress ?? false,
321
+ traceAllowLoopback: args.allowLoopback ?? false,
322
+ traceDryRun: args.sandboxDryRun ?? false,
323
+ tracePolicy: args.policy,
324
+ tracePolicyFile: args.policyFile ? resolve(args.policyFile) : undefined,
325
+ traceBlockInterpreters: args.blockInterpreters ?? false,
326
+ traceDenyPersistenceWrites: args.denyPersistenceWrites ?? false,
327
+ tracePrivateTmp: args.privateTmp ?? false,
328
+ traceProtectHome: args.protectHome,
268
329
  cbom: undefined,
269
330
  projectType: ["dynamic"],
270
331
  output: resolve(args.output),
@@ -277,6 +338,9 @@ const options = {
277
338
  console.error("Dynamic SBOM generation failed: no output was produced.");
278
339
  process.exit(1);
279
340
  }
341
+ // tracebom skips postProcess, so the downgrade to an older --spec-version
342
+ // (for example dropping the 1.7 algorithmFamily of traced crypto) runs here.
343
+ applySpecVersionCompatibility(bomJson, options);
280
344
 
281
345
  const jsonPayload = JSON.stringify(bomJson, null, DEBUG_MODE ? 2 : null);
282
346
 
package/bin/validate.js CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  safeMkdirSync,
27
27
  safeWriteSync,
28
28
  } from "../lib/ecosystems/utils.js";
29
+ import { displayValue } from "../lib/helpers/bomSigner.js";
29
30
  import {
30
31
  getNonCycloneDxErrorMessage,
31
32
  isCycloneDxBom,
@@ -35,6 +36,7 @@ import {
35
36
  isProtoBomPath,
36
37
  } from "../lib/inventory/protobomLoader.js";
37
38
  import { getBomWithOras } from "../lib/managers/oci.js";
39
+ import { parseStrictJson } from "../lib/parsers/strictJson.js";
38
40
  import { shouldFail, validateBomAdvanced } from "../lib/validator/index.js";
39
41
  import { render as renderReport } from "../lib/validator/reporters/index.js";
40
42
 
@@ -114,11 +116,20 @@ const args = _yargs
114
116
  description:
115
117
  "Path to a PEM public key. When set, cdx-validate also verifies the BOM signature.",
116
118
  })
119
+ .option("secret-key", {
120
+ description:
121
+ "Path to the shared secret for HMAC (HS256, HS384, HS512) signatures. HMAC signatures are only accepted with this option.",
122
+ })
123
+ .option("nested-signatures", {
124
+ type: "boolean",
125
+ default: true,
126
+ description:
127
+ "Also verify component, service, and annotation signatures, as cdx-verify does. Pass --no-nested-signatures to verify only the root signature.",
128
+ })
117
129
  .option("require-signature", {
118
130
  type: "boolean",
119
- default: false,
120
131
  description:
121
- "Exit non-zero (4) when --public-key is provided but signature verification fails.",
132
+ "Exit non-zero (4) when signature verification fails. On by default when --public-key or --secret-key is given; pass --no-require-signature to only report the result.",
122
133
  })
123
134
  .option("strict", {
124
135
  type: "boolean",
@@ -133,7 +144,11 @@ const args = _yargs
133
144
  .help()
134
145
  .wrap(Math.min(120, yargs().terminalWidth())).argv;
135
146
 
136
- async function loadBom(input, platform) {
147
+ /**
148
+ * Loads the BOM. When signatures are verified, the text must also pass
149
+ * parseStrictJson(), and `strictError` explains why it did not.
150
+ */
151
+ async function loadBom(input, platform, strict, requireSignature) {
137
152
  if (safeExistsSync(input)) {
138
153
  try {
139
154
  if (isProtoBomPath(input)) {
@@ -141,9 +156,18 @@ async function loadBom(input, platform) {
141
156
  "cdx-validate",
142
157
  "protobuf BOM input",
143
158
  );
144
- return readBinary(input, true);
159
+ return { bomJson: readBinary(input, true) };
160
+ }
161
+ const bytes = fs.readFileSync(input);
162
+ const bomJson = JSON.parse(bytes.toString("utf8"));
163
+ if (strict) {
164
+ try {
165
+ parseStrictJson(bytes);
166
+ } catch (err) {
167
+ return { bomJson, strictError: err.message };
168
+ }
145
169
  }
146
- return JSON.parse(fs.readFileSync(input, "utf8"));
170
+ return { bomJson };
147
171
  } catch (err) {
148
172
  console.error(`Failed to parse ${input}: ${err.message}`);
149
173
  process.exit(1);
@@ -154,21 +178,49 @@ async function loadBom(input, platform) {
154
178
  input.includes("docker") ||
155
179
  input.includes("ghcr")
156
180
  ) {
157
- const bom = await getBomWithOras(input, platform);
158
- if (bom) return bom;
181
+ let strictError;
182
+ try {
183
+ const bom = await getBomWithOras(
184
+ input,
185
+ platform,
186
+ strict ? { checkBytes: parseStrictJson } : {},
187
+ );
188
+ if (bom) return { bomJson: bom };
189
+ } catch (err) {
190
+ if (requireSignature) {
191
+ console.error(
192
+ `cdx-validate: signature verification failed — the attached BOM cannot be verified: ${err.message}`,
193
+ );
194
+ process.exit(4);
195
+ }
196
+ strictError = err.message;
197
+ }
198
+ if (strictError) {
199
+ const bom = await getBomWithOras(input, platform);
200
+ if (bom) return { bomJson: bom, strictError };
201
+ }
159
202
  }
160
203
  console.error(`Input '${input}' is not a readable SBOM.`);
161
204
  process.exit(1);
162
205
  return undefined;
163
206
  }
164
207
 
165
- function loadPublicKey(path) {
166
- if (!path) return null;
208
+ function loadKeyFile(path, label, encoding) {
209
+ if (path === undefined) return null;
210
+ if (typeof path !== "string" || !path.trim()) {
211
+ console.error(`cdx-validate: ${label} needs exactly one file path.`);
212
+ process.exit(1);
213
+ }
167
214
  if (!safeExistsSync(path)) {
168
- console.error(`Public key '${path}' not found.`);
215
+ console.error(`${label} '${path}' not found.`);
169
216
  process.exit(1);
170
217
  }
171
- return fs.readFileSync(path, "utf8");
218
+ const content = fs.readFileSync(path, encoding);
219
+ if (!content.toString().trim()) {
220
+ console.error(`${label} '${path}' is empty.`);
221
+ process.exit(1);
222
+ }
223
+ return content;
172
224
  }
173
225
 
174
226
  function splitCsv(value) {
@@ -198,19 +250,32 @@ function isLocalProtoBomInput(input) {
198
250
  return isProtoBomPath(input);
199
251
  }
200
252
 
201
- const bomJson = await loadBom(args.input, args.platform);
202
- const publicKeyStr = loadPublicKey(args.publicKey);
253
+ const keyGiven = args.publicKey !== undefined || args.secretKey !== undefined;
254
+ const requireSignature = args.requireSignature ?? keyGiven;
255
+ const { bomJson, strictError } = await loadBom(
256
+ args.input,
257
+ args.platform,
258
+ keyGiven,
259
+ requireSignature,
260
+ );
261
+ if (args.publicKey !== undefined && args.secretKey !== undefined) {
262
+ console.error("cdx-validate: use either --public-key or --secret-key.");
263
+ process.exit(1);
264
+ }
265
+ const publicKeyStr = loadKeyFile(args.publicKey, "Public key", "utf8");
266
+ const secretKey = loadKeyFile(args.secretKey, "Shared secret");
267
+ const verificationRequested = keyGiven;
203
268
  const inputIsLocalProtoBom = isLocalProtoBomInput(args.input);
204
269
  if (!isCycloneDxBom(bomJson)) {
205
270
  console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-validate"));
206
271
  process.exit(1);
207
272
  }
208
273
 
209
- if (inputIsLocalProtoBom && publicKeyStr) {
274
+ if (inputIsLocalProtoBom && verificationRequested) {
210
275
  console.error(
211
276
  "cdx-validate: protobuf BOM input does not currently preserve JSF signature blocks. Verify signatures against the source JSON BOM instead.",
212
277
  );
213
- process.exit(args.requireSignature ? 4 : 1);
278
+ process.exit(requireSignature ? 4 : 1);
214
279
  }
215
280
 
216
281
  const report = await validateBomAdvanced(bomJson, {
@@ -221,8 +286,17 @@ const report = await validateBomAdvanced(bomJson, {
221
286
  minSeverity: args.minSeverity,
222
287
  includeManual: args.includeManual,
223
288
  includePass: args.includePass,
224
- publicKey: publicKeyStr || undefined,
289
+ publicKey: publicKeyStr ?? undefined,
290
+ secretKey: secretKey ?? undefined,
291
+ nestedSignatures: args.nestedSignatures,
225
292
  });
293
+ if (strictError && verificationRequested) {
294
+ report.signatureVerified = false;
295
+ report.signatureEntries = null;
296
+ report.signatureDetails = {
297
+ error: `the BOM cannot be verified: ${strictError}`,
298
+ };
299
+ }
226
300
 
227
301
  let output;
228
302
  try {
@@ -242,17 +316,31 @@ try {
242
316
 
243
317
  writeOrPrint(output, args.reportFile);
244
318
 
319
+ for (const e of report.validationErrors || []) {
320
+ console.error(`cdx-validate: ${e.stage} validation threw: ${e.message}`);
321
+ }
322
+
245
323
  const { shouldFail: fail, reason } = shouldFail(report, {
246
324
  failSeverity: args.failSeverity,
247
325
  strict: args.strict,
248
- requireSignature: Boolean(args.requireSignature && publicKeyStr),
326
+ requireSignature: Boolean(requireSignature && verificationRequested),
249
327
  });
250
328
 
251
- if (report.signatureVerified === false && args.requireSignature) {
329
+ if (report.signatureVerified === false && verificationRequested) {
252
330
  console.error(
253
331
  `cdx-validate: signature verification failed — ${report.signatureDetails?.error || "no matching key"}.`,
254
332
  );
255
- process.exit(4);
333
+ if (requireSignature) {
334
+ process.exit(4);
335
+ }
336
+ }
337
+ const unverifiedEntries = (report.signatureEntries || []).filter(
338
+ (entry) => !entry.verified,
339
+ );
340
+ if (report.signatureVerified && unverifiedEntries.length) {
341
+ console.error(
342
+ `cdx-validate: warning — ${unverifiedEntries.length} of ${report.signatureEntries.length} signature entries did not verify with the given key: ${unverifiedEntries.map((entry) => `entry ${entry.index}${entry.keyId === undefined ? "" : ` (keyId '${displayValue(entry.keyId)}')`}`).join(", ")}.`,
343
+ );
256
344
  }
257
345
 
258
346
  if (args.strict && report.schemaValid === false) {