@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
@@ -0,0 +1,294 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { thoughtLog } from "../core/logger.js";
5
+ import { dirNameStr } from "../core/paths.js";
6
+ import { applyAlgorithmProperties } from "./cryptoAlgorithmFamily.js";
7
+
8
+ const cryptoOids = JSON.parse(
9
+ readFileSync(join(dirNameStr, "data", "crypto-oid.json"), "utf-8"),
10
+ );
11
+
12
+ // TLS key-exchange groups by their IANA registry name. A hybrid group pairs a
13
+ // classical curve with ML-KEM: each half has an OID, the pair has none, so the
14
+ // group is recorded as both algorithms.
15
+ const GROUP_ALGORITHMS = {
16
+ X25519MLKEM768: ["x25519", "ml-kem-768"],
17
+ SecP256r1MLKEM768: ["secp256r1", "ml-kem-768"],
18
+ SecP384r1MLKEM1024: ["secp384r1", "ml-kem-1024"],
19
+ X25519Kyber768Draft00: ["x25519"],
20
+ MLKEM512: ["ml-kem-512"],
21
+ MLKEM768: ["ml-kem-768"],
22
+ MLKEM1024: ["ml-kem-1024"],
23
+ x448: ["curveX448"],
24
+ };
25
+
26
+ const MLKEM_SECURITY_LEVELS = {
27
+ "ml-kem-512": 1,
28
+ "ml-kem-768": 3,
29
+ "ml-kem-1024": 5,
30
+ };
31
+
32
+ // The curve each classical key-exchange algorithm is defined over.
33
+ const KEY_AGREEMENT_CURVES = {
34
+ x25519: "x25519",
35
+ curveX448: "x448",
36
+ };
37
+
38
+ // Closed enums of cryptoProperties.algorithmProperties in bom-1.7.schema.json.
39
+ const MODES = new Set(["cbc", "ecb", "ccm", "gcm", "cfb", "ofb", "ctr"]);
40
+ const CRYPTO_FUNCTIONS = new Set([
41
+ "generate",
42
+ "keygen",
43
+ "encrypt",
44
+ "decrypt",
45
+ "digest",
46
+ "tag",
47
+ "keyderive",
48
+ "sign",
49
+ "verify",
50
+ "encapsulate",
51
+ "decapsulate",
52
+ ]);
53
+
54
+ /**
55
+ * Names the bulk cipher of a TLS cipher suite as a crypto-oid.json key, from
56
+ * either its IANA name (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) or its OpenSSL
57
+ * name (ECDHE-RSA-AES128-GCM-SHA256).
58
+ *
59
+ * @param {string} suiteName Cipher suite name
60
+ * @returns {{name: string, bits: number, mode: string|undefined}|undefined} The cipher, when it has an OID
61
+ */
62
+ export function suiteCipher(suiteName) {
63
+ const tokens = String(suiteName || "")
64
+ .toUpperCase()
65
+ .split(/[-_]/);
66
+ let cipher;
67
+ if (tokens.includes("CHACHA20")) {
68
+ cipher = { name: "chaCha20Poly1305", bits: 256, mode: undefined };
69
+ } else if (tokens.includes("3DES") || tokens.includes("CBC3")) {
70
+ cipher = { name: "des-EDE3-CBC", bits: 112, mode: "cbc" };
71
+ } else {
72
+ const aesIdx = tokens.findIndex((t) => t.startsWith("AES"));
73
+ if (aesIdx < 0) {
74
+ return undefined;
75
+ }
76
+ const bits =
77
+ Number.parseInt(tokens[aesIdx].substring(3), 10) ||
78
+ Number.parseInt(tokens[aesIdx + 1], 10);
79
+ const mode = ["GCM", "CCM"].find((m) => tokens.includes(m)) || "CBC";
80
+ cipher = { name: `aes${bits}-${mode}`, bits, mode: mode.toLowerCase() };
81
+ }
82
+ return cryptoOids[cipher.name]?.oid ? cipher : undefined;
83
+ }
84
+
85
+ function oidKey(name) {
86
+ if (!name) {
87
+ return undefined;
88
+ }
89
+ if (cryptoOids[name]?.oid) {
90
+ return name;
91
+ }
92
+ const lower = String(name).toLowerCase();
93
+ return cryptoOids[lower]?.oid ? lower : undefined;
94
+ }
95
+
96
+ function cipherProperties(cipher) {
97
+ const props = {
98
+ primitive: cipher.mode === "cbc" ? "block-cipher" : "ae",
99
+ parameterSetIdentifier: String(cipher.bits),
100
+ cryptoFunctions: ["encrypt", "decrypt"],
101
+ classicalSecurityLevel: cipher.bits,
102
+ };
103
+ if (cipher.mode) {
104
+ props.mode = cipher.mode;
105
+ }
106
+ return props;
107
+ }
108
+
109
+ function keyExchangeProperties(name) {
110
+ const level = MLKEM_SECURITY_LEVELS[name];
111
+ if (level) {
112
+ return {
113
+ primitive: "kem",
114
+ cryptoFunctions: ["keygen", "encapsulate", "decapsulate"],
115
+ nistQuantumSecurityLevel: level,
116
+ };
117
+ }
118
+ return { primitive: "key-agree", cryptoFunctions: ["keygen"] };
119
+ }
120
+
121
+ // Keeps the values of an algorithm the traced CBOM described that fit the
122
+ // schema; the primitive itself is checked by applyAlgorithmProperties.
123
+ function knownAlgorithmProperties(props) {
124
+ const known = {};
125
+ if (typeof props?.primitive === "string") {
126
+ known.primitive = props.primitive === "aead" ? "ae" : props.primitive;
127
+ }
128
+ const mode = String(props?.mode || "").toLowerCase();
129
+ if (MODES.has(mode)) {
130
+ known.mode = mode;
131
+ }
132
+ const functions = (props?.cryptoFunctions || []).filter((f) =>
133
+ CRYPTO_FUNCTIONS.has(f),
134
+ );
135
+ if (functions.length) {
136
+ known.cryptoFunctions = functions;
137
+ }
138
+ for (const level of ["classicalSecurityLevel", "nistQuantumSecurityLevel"]) {
139
+ if (Number.isInteger(props?.[level])) {
140
+ known[level] = props[level];
141
+ }
142
+ }
143
+ return known;
144
+ }
145
+
146
+ /**
147
+ * Turns the CBOM safer-exec writes for a traced command into components that
148
+ * cdxgen can merge and validate:
149
+ *
150
+ * - every asset gets a bom-ref, without which the assets merge into one
151
+ * - algorithms carry their OID, and those without one are left out
152
+ * - hybrid key-exchange groups become their classical and ML-KEM halves
153
+ * - cipher suites become their bulk cipher, which the protocol's suites refer
154
+ * to by bom-ref
155
+ * - algorithm properties keep only values the schema allows
156
+ * - crypto libraries are left out: they are the traced library components,
157
+ * marked with cdx:dynamic:cryptoLibrary
158
+ *
159
+ * @param {Object[]} cbomComponents Components of the safer-exec CBOM
160
+ * @returns {Object[]} Normalized cryptographic-asset components
161
+ */
162
+ export function normalizeTracedCryptoComponents(cbomComponents) {
163
+ const byRef = new Map();
164
+ const addAlgorithm = (name, algorithmProperties, curve) => {
165
+ const oid = cryptoOids[name].oid;
166
+ const ref = `crypto/algorithm/${encodeURIComponent(name)}@${oid}`;
167
+ if (!byRef.has(ref)) {
168
+ const component = {
169
+ type: "cryptographic-asset",
170
+ name,
171
+ "bom-ref": ref,
172
+ description: cryptoOids[name].description,
173
+ cryptoProperties: {
174
+ assetType: "algorithm",
175
+ algorithmProperties,
176
+ oid,
177
+ },
178
+ };
179
+ applyAlgorithmProperties(component, { name, curve });
180
+ byRef.set(ref, component);
181
+ }
182
+ return ref;
183
+ };
184
+ const protocols = [];
185
+ for (const comp of cbomComponents || []) {
186
+ const props = comp?.cryptoProperties;
187
+ if (comp?.type !== "cryptographic-asset" || !props) {
188
+ continue;
189
+ }
190
+ if (props.assetType === "protocol") {
191
+ protocols.push(comp);
192
+ continue;
193
+ }
194
+ if (props.assetType !== "algorithm") {
195
+ continue;
196
+ }
197
+ const groupHalves = GROUP_ALGORITHMS[comp.name];
198
+ if (groupHalves) {
199
+ for (const half of groupHalves) {
200
+ addAlgorithm(
201
+ half,
202
+ keyExchangeProperties(half),
203
+ KEY_AGREEMENT_CURVES[half] ||
204
+ (MLKEM_SECURITY_LEVELS[half] ? "" : half),
205
+ );
206
+ }
207
+ continue;
208
+ }
209
+ const name = oidKey(comp.name);
210
+ if (name) {
211
+ addAlgorithm(
212
+ name,
213
+ MLKEM_SECURITY_LEVELS[name] || KEY_AGREEMENT_CURVES[name]
214
+ ? keyExchangeProperties(name)
215
+ : knownAlgorithmProperties(props.algorithmProperties),
216
+ KEY_AGREEMENT_CURVES[name] ||
217
+ (/^(secp|brainpool)/.test(name) ? name : ""),
218
+ );
219
+ continue;
220
+ }
221
+ const cipher = suiteCipher(comp.name);
222
+ if (cipher) {
223
+ addAlgorithm(cipher.name, cipherProperties(cipher));
224
+ continue;
225
+ }
226
+ thoughtLog(`Skipping traced algorithm '${comp.name}' without an OID.`);
227
+ }
228
+ for (const comp of protocols) {
229
+ const ref = `crypto/protocol/${encodeURIComponent(comp.name)}@${encodeURIComponent(comp.version || "unknown")}`;
230
+ const protocolProperties = {
231
+ ...comp.cryptoProperties.protocolProperties,
232
+ };
233
+ // The traced suites list algorithm names; the schema wants bom-refs.
234
+ const cipherSuites = (protocolProperties.cipherSuites || []).map(
235
+ ({ algorithms: _names, ...suite }) => {
236
+ const cipher = suiteCipher(suite.name);
237
+ return cipher
238
+ ? {
239
+ ...suite,
240
+ algorithms: [addAlgorithm(cipher.name, cipherProperties(cipher))],
241
+ }
242
+ : suite;
243
+ },
244
+ );
245
+ const existing = byRef.get(ref);
246
+ if (existing) {
247
+ const suites = existing.cryptoProperties.protocolProperties.cipherSuites;
248
+ for (const suite of cipherSuites) {
249
+ if (!suites.some((s) => s.name === suite.name)) {
250
+ suites.push(suite);
251
+ }
252
+ }
253
+ continue;
254
+ }
255
+ protocolProperties.cipherSuites = cipherSuites;
256
+ const component = {
257
+ type: "cryptographic-asset",
258
+ name: comp.name,
259
+ "bom-ref": ref,
260
+ cryptoProperties: {
261
+ assetType: "protocol",
262
+ protocolProperties,
263
+ },
264
+ };
265
+ if (comp.version) {
266
+ component.version = comp.version;
267
+ }
268
+ byRef.set(ref, component);
269
+ }
270
+ return Array.from(byRef.values());
271
+ }
272
+
273
+ /**
274
+ * Maps the crypto libraries safer-exec reported to the paths they were loaded
275
+ * from.
276
+ *
277
+ * @param {Object[]} cryptoEntries safer-exec crypto-library and crypto-cipher audit entries
278
+ * @returns {Map<string, {name: string, version: string}>} Crypto libraries by path
279
+ */
280
+ export function cryptoLibrariesByPath(cryptoEntries) {
281
+ const libraries = new Map();
282
+ for (const entry of cryptoEntries || []) {
283
+ if (entry?.type !== "crypto-library" || !entry.path || !entry.name) {
284
+ continue;
285
+ }
286
+ if (!libraries.has(entry.path)) {
287
+ libraries.set(entry.path, {
288
+ name: String(entry.name),
289
+ version: entry.version ? String(entry.version) : "",
290
+ });
291
+ }
292
+ }
293
+ return libraries;
294
+ }
@@ -7,6 +7,7 @@ import {
7
7
  } from "node:fs";
8
8
  import { platform as _platform, homedir } from "node:os";
9
9
  import { basename, dirname, extname, join, relative, resolve } from "node:path";
10
+ import process from "node:process";
10
11
 
11
12
  import { build, Purl } from "@cdxgen/cdx-purl";
12
13
 
@@ -54,7 +55,11 @@ import {
54
55
  resolvePluginBinary,
55
56
  setPluginsPathEnv,
56
57
  } from "../inventory/plugins.js";
57
- import { genericPurl, tryBuildPurl } from "../inventory/purl.js";
58
+ import {
59
+ genericPurl,
60
+ parsePurlLoose,
61
+ tryBuildPurl,
62
+ } from "../inventory/purl.js";
58
63
  import {
59
64
  adjustLicenseInformation,
60
65
  findLicenseId,
@@ -531,28 +536,89 @@ export function getCargoAuditableInfo(src) {
531
536
  return undefined;
532
537
  }
533
538
 
539
+ /**
540
+ * Check that sourcekitten (bundled plugin or SOURCEKITTEN_CMD) answers a
541
+ * trivial syntax request.
542
+ *
543
+ * @param env {Object} Optional environment overrides merged over process.env
544
+ * @returns {String} `ok`; `unavailable` when there is no bundled plugin and
545
+ * SOURCEKITTEN_CMD is unset; `not-found` when the configured executable does
546
+ * not exist; `failed` when it ran but could not answer
547
+ */
548
+ export function probeSourcekitten(env) {
549
+ if (!SOURCEKITTEN_BIN) {
550
+ return "unavailable";
551
+ }
552
+ const result = safeSpawnSync(
553
+ SOURCEKITTEN_BIN,
554
+ ["syntax", "--text", "import Swift"],
555
+ env ? { env: { ...process.env, ...env } } : undefined,
556
+ );
557
+ if (result?.error?.code === "ENOENT") {
558
+ return "not-found";
559
+ }
560
+ if (result?.status === 0 && result.stdout) {
561
+ try {
562
+ JSON.parse(result.stdout);
563
+ return "ok";
564
+ } catch (_e) {
565
+ // Plain-text errors on stdout are failures
566
+ }
567
+ }
568
+ if (DEBUG_MODE) {
569
+ console.log(
570
+ "sourcekitten syntax probe failed:",
571
+ `${result?.stderr || result?.error || ""}`
572
+ .split("\n")
573
+ .slice(0, 5)
574
+ .map((l) => l.slice(0, 400))
575
+ .join("\n"),
576
+ );
577
+ }
578
+ return "failed";
579
+ }
580
+
534
581
  /**
535
582
  * Execute sourcekitten plugin with the given arguments
536
583
  *
537
584
  * @param args {Array} Arguments
585
+ * @param env {Object} Optional environment overrides merged over process.env,
586
+ * used to align sourcekitten with a specific Swift toolchain
538
587
  * @returns {undefined|Object} Command output
539
588
  */
540
- export function executeSourcekitten(args) {
541
- if (SOURCEKITTEN_BIN) {
542
- const result = safeSpawnSync(SOURCEKITTEN_BIN, args);
543
- if (result.status !== 0 || result.error) {
544
- if (result.stdout || result.stderr) {
545
- console.error(result.stdout, result.stderr);
546
- }
547
- }
548
- if (result) {
549
- const stdout = result.stdout;
550
- if (stdout) {
551
- return JSON.parse(stdout);
552
- }
589
+ export function executeSourcekitten(args, env) {
590
+ if (!SOURCEKITTEN_BIN) {
591
+ return undefined;
592
+ }
593
+ const result = safeSpawnSync(
594
+ SOURCEKITTEN_BIN,
595
+ args,
596
+ env ? { env: { ...process.env, ...env } } : undefined,
597
+ );
598
+ if ((result.status !== 0 || result.error) && DEBUG_MODE) {
599
+ // Failures echo whole clang invocations, so only the first diagnostic
600
+ // lines are worth showing
601
+ const diagnostics = `${result.stderr || ""}`.split("\n").slice(0, 5);
602
+ console.log(
603
+ `sourcekitten ${args?.[0]} failed:`,
604
+ diagnostics.map((l) => l.slice(0, 400)).join("\n"),
605
+ );
606
+ }
607
+ if (!result?.stdout) {
608
+ return undefined;
609
+ }
610
+ try {
611
+ return JSON.parse(result.stdout);
612
+ } catch (_e) {
613
+ // Oversized outputs are truncated at the spawn buffer limit, and
614
+ // sourcekitten prints plain-text errors to stdout on some failures
615
+ if (DEBUG_MODE) {
616
+ console.log(
617
+ `Ignoring unparseable sourcekitten ${args?.[0]} output of ${result.stdout.length} bytes.`,
618
+ );
553
619
  }
620
+ return undefined;
554
621
  }
555
- return undefined;
556
622
  }
557
623
 
558
624
  /**
@@ -796,8 +862,16 @@ export async function getOSPackages(src, imageConfig, options = {}) {
796
862
  );
797
863
  }
798
864
  }
799
- if (TRIVY_BIN) {
800
- let imageType = "image";
865
+ // trivy-cdxgen only scans a root filesystem; it has no `image` command. The
866
+ // caller hands over the exported image directory, so a missing path means
867
+ // the export failed and there is nothing for trivy to scan.
868
+ const rootfsPresent = TRIVY_BIN ? safeExistsSync(src) : false;
869
+ if (TRIVY_BIN && !rootfsPresent) {
870
+ console.warn(
871
+ `Skipping the trivy OS package scan: the root filesystem ${src} does not exist.`,
872
+ );
873
+ }
874
+ if (TRIVY_BIN && rootfsPresent) {
801
875
  const trivyCacheDir = join(homedir(), ".cache", "trivy");
802
876
  try {
803
877
  safeMkdirSync(join(trivyCacheDir, "db"), { recursive: true });
@@ -805,13 +879,10 @@ export async function getOSPackages(src, imageConfig, options = {}) {
805
879
  } catch (_err) {
806
880
  // ignore errors
807
881
  }
808
- if (safeExistsSync(src)) {
809
- imageType = "rootfs";
810
- }
811
882
  const tempDir = safeMkdtempSync(join(getTmpDir(), "trivy-cdxgen-"));
812
883
  const bomJsonFile = join(tempDir, "trivy-bom.json");
813
884
  const args = [
814
- imageType,
885
+ "rootfs",
815
886
  "--cache-dir",
816
887
  trivyCacheDir,
817
888
  "--output",
@@ -932,7 +1003,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
932
1003
  ? tmpBom.metadata.tools
933
1004
  : tmpBom?.metadata?.tools?.components || []
934
1005
  ).filter((tool) => tool?.["bom-ref"] && tool?.name !== "cdxgen"),
935
- ["trivy"].concat(imageType === "rootfs" ? ["trustinspector"] : []),
1006
+ ["trivy", "trustinspector"],
936
1007
  );
937
1008
  const toolRefs = extractToolRefs(
938
1009
  { components: tools },
@@ -2977,7 +3048,20 @@ function detectSdksRuntimes(comp, bundledSdks, bundledRuntimes) {
2977
3048
  }
2978
3049
  }
2979
3050
 
2980
- const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
3051
+ /**
3052
+ * Collect the dependency edges trivy recorded for one OS package component.
3053
+ *
3054
+ * Trivy keys its `dependencies` by its own bom-refs, which may leave the `+` of
3055
+ * an rpm or deb version unescaped (`...libstdc%2B%2B6@14.2.0+git10526-...`), so
3056
+ * each ref is read structurally and re-rendered in cdxgen's decoded bom-ref
3057
+ * form with the component's type, namespace and distro qualifiers.
3058
+ *
3059
+ * @param {Object<string, string[]>} tmpDependencies Trivy's refs mapped to the refs they depend on
3060
+ * @param {string} origBomRef The component's bom-ref as trivy wrote it
3061
+ * @param {object} comp The component after cdxgen rewrote its purl and bom-ref
3062
+ * @returns {{ref: string, dependsOn: string[]}|undefined} Dependency entry for the component
3063
+ */
3064
+ export const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
2981
3065
  try {
2982
3066
  const tmpDependsOn =
2983
3067
  tmpDependencies[origBomRef] || tmpDependencies[comp["bom-ref"]] || [];
@@ -2985,7 +3069,14 @@ const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
2985
3069
  tmpDependsOn.forEach((d) => {
2986
3070
  try {
2987
3071
  const compPurl = Purl.parse(comp.purl);
2988
- const tmpPurl = Purl.parse(d.replace("none", compPurl.type));
3072
+ // Trivy's dependency refs are its bom-refs, which can leave the `+` of
3073
+ // an rpm or deb version unescaped; read them structurally so the edge
3074
+ // survives.
3075
+ const depParts = parsePurlLoose(d.replace("none", compPurl.type));
3076
+ if (!depParts) {
3077
+ return;
3078
+ }
3079
+ const tmpPurl = new Purl(depParts);
2989
3080
  tmpPurl.type = compPurl.type;
2990
3081
  // FIXME: Check if this hack is still needed with the latest trivy
2991
3082
  if (OS_PURL_TYPES.includes(compPurl.type)) {