@cdxgen/cdxgen 13.3.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 (255) hide show
  1. package/README.md +9 -6
  2. package/bin/cdxgen.js +90 -26
  3. package/bin/evinse.js +18 -1
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +45 -15
  6. package/bin/tracebom.js +5 -1
  7. package/bin/validate.js +84 -18
  8. package/bin/verify.js +155 -86
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +61 -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/index.js +113 -54
  15. package/lib/cli/jsBom.js +50 -29
  16. package/lib/cli/jvmBom.js +665 -138
  17. package/lib/cli/managedBom.js +320 -200
  18. package/lib/cli/nativeBom.js +139 -30
  19. package/lib/core/activity.js +12 -0
  20. package/lib/core/env.js +17 -9
  21. package/lib/core/fs.js +3 -2
  22. package/lib/core/httpClient.js +159 -24
  23. package/lib/core/pluginRun.js +184 -0
  24. package/lib/core/processTree.js +100 -0
  25. package/lib/core/runState.js +39 -0
  26. package/lib/core/toml.js +72 -0
  27. package/lib/ecosystems/asarutils.js +4 -2
  28. package/lib/ecosystems/bunutils.js +5 -7
  29. package/lib/ecosystems/cargoCache.js +231 -8
  30. package/lib/ecosystems/cmakeResolver.js +10 -21
  31. package/lib/ecosystems/cppBuildContext.js +631 -0
  32. package/lib/ecosystems/cppEvidence.js +337 -22
  33. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  34. package/lib/ecosystems/denoutils.js +93 -66
  35. package/lib/ecosystems/ecosystems.js +1967 -788
  36. package/lib/ecosystems/golem.js +28 -12
  37. package/lib/ecosystems/gradleutils.js +314 -30
  38. package/lib/ecosystems/jsEvidence.js +85 -24
  39. package/lib/ecosystems/kosi.js +21 -4
  40. package/lib/ecosystems/npmutils.js +64 -0
  41. package/lib/ecosystems/parsers-dotnet.js +567 -151
  42. package/lib/ecosystems/parsers-elm.js +46 -28
  43. package/lib/ecosystems/parsers-gleam.js +3 -4
  44. package/lib/ecosystems/parsers-go.js +324 -61
  45. package/lib/ecosystems/parsers-js.js +92 -26
  46. package/lib/ecosystems/parsers-julia.js +2 -3
  47. package/lib/ecosystems/parsers-jvm.js +204 -12
  48. package/lib/ecosystems/parsers-misc.js +182 -55
  49. package/lib/ecosystems/parsers-python.js +141 -10
  50. package/lib/ecosystems/parsers-rust.js +4 -4
  51. package/lib/ecosystems/parsers-terraform.js +1573 -19
  52. package/lib/ecosystems/pythonutils.js +102 -6
  53. package/lib/ecosystems/remote/huggingface.js +3 -0
  54. package/lib/ecosystems/rubyutils.js +67 -17
  55. package/lib/ecosystems/rusi.js +22 -9
  56. package/lib/ecosystems/sbtutils.js +243 -187
  57. package/lib/ecosystems/scalacliutils.js +342 -0
  58. package/lib/ecosystems/scalasem.js +2618 -0
  59. package/lib/ecosystems/terraformRegistry.js +288 -0
  60. package/lib/ecosystems/utils.js +2 -0
  61. package/lib/ecosystems/vendoredCode.js +156 -0
  62. package/lib/evinser/evinser.js +578 -69
  63. package/lib/helpers/agenticToolutils.js +2 -2
  64. package/lib/helpers/bomSigner.js +576 -128
  65. package/lib/inventory/aiCollector.js +4 -1
  66. package/lib/inventory/analyzer.js +110 -127
  67. package/lib/inventory/atomUtils.js +171 -48
  68. package/lib/inventory/auditCategories.js +7 -0
  69. package/lib/inventory/buildToolRateLimit.js +144 -0
  70. package/lib/inventory/cacheCommands.js +2 -1
  71. package/lib/inventory/cmakeBuildDirs.js +254 -0
  72. package/lib/inventory/deps.js +501 -124
  73. package/lib/inventory/dosai.js +248 -44
  74. package/lib/inventory/dosaiParsers.js +310 -15
  75. package/lib/inventory/dynamic.js +52 -6
  76. package/lib/inventory/evidenceUtils.js +154 -109
  77. package/lib/inventory/fetchBatch.js +574 -38
  78. package/lib/inventory/fetchDiskCache.js +597 -0
  79. package/lib/inventory/forgeEnricher.js +14 -5
  80. package/lib/inventory/jvmLocalRepos.js +917 -0
  81. package/lib/inventory/mcpConfigParser.js +2 -3
  82. package/lib/inventory/parentSelection.js +146 -0
  83. package/lib/inventory/rustFormulationParser.js +3 -4
  84. package/lib/inventory/scalaCoords.js +153 -0
  85. package/lib/inventory/source.js +8 -8
  86. package/lib/inventory/spdx.js +101 -0
  87. package/lib/inventory/traceRunner.js +88 -7
  88. package/lib/inventory/tracedCrypto.js +294 -0
  89. package/lib/managers/docker.js +33 -32
  90. package/lib/managers/oci.js +24 -2
  91. package/lib/parsers/cmakeCache.js +69 -0
  92. package/lib/parsers/cmakeLists.js +16 -2
  93. package/lib/parsers/cmakePresets.js +400 -0
  94. package/lib/parsers/compileCommands.js +557 -0
  95. package/lib/parsers/strictJson.js +322 -0
  96. package/lib/parsers/terraformConfig.js +1073 -0
  97. package/lib/parsers/terraformSource.js +652 -0
  98. package/lib/server/server.js +23 -0
  99. package/lib/stages/postgen/introspection/markers.js +1 -1
  100. package/lib/stages/postgen/postgen.js +15 -1
  101. package/lib/stages/postgen/specVersionCompat.js +32 -0
  102. package/lib/validator/index.js +25 -10
  103. package/lib/validator/reporters/json.js +1 -0
  104. package/package.json +21 -21
  105. package/types/lib/audit/index.d.ts.map +1 -1
  106. package/types/lib/cli/index.d.ts.map +1 -1
  107. package/types/lib/cli/jsBom.d.ts.map +1 -1
  108. package/types/lib/cli/jvmBom.d.ts +24 -1
  109. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  110. package/types/lib/cli/managedBom.d.ts +7 -4
  111. package/types/lib/cli/managedBom.d.ts.map +1 -1
  112. package/types/lib/cli/nativeBom.d.ts +3 -1
  113. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  114. package/types/lib/core/activity.d.ts.map +1 -1
  115. package/types/lib/core/env.d.ts +9 -0
  116. package/types/lib/core/env.d.ts.map +1 -1
  117. package/types/lib/core/fs.d.ts.map +1 -1
  118. package/types/lib/core/httpClient.d.ts +13 -8
  119. package/types/lib/core/httpClient.d.ts.map +1 -1
  120. package/types/lib/core/pluginRun.d.ts +97 -0
  121. package/types/lib/core/pluginRun.d.ts.map +1 -0
  122. package/types/lib/core/processTree.d.ts +36 -0
  123. package/types/lib/core/processTree.d.ts.map +1 -0
  124. package/types/lib/core/runState.d.ts +29 -0
  125. package/types/lib/core/runState.d.ts.map +1 -0
  126. package/types/lib/core/toml.d.ts +20 -0
  127. package/types/lib/core/toml.d.ts.map +1 -0
  128. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  129. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  130. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  131. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  132. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  133. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  134. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  135. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  136. package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
  137. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  139. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  140. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  141. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  143. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  144. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  145. package/types/lib/ecosystems/gradleutils.d.ts +50 -0
  146. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  147. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  148. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  149. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  150. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  151. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  152. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  153. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  154. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  155. package/types/lib/ecosystems/parsers-go.d.ts +44 -2
  156. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  157. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  158. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  159. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  160. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  161. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  162. package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
  163. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  164. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  165. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  166. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  167. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  168. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  169. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  170. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  171. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  172. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  173. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  174. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  175. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  176. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  177. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  178. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  179. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  180. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  181. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  182. package/types/lib/ecosystems/utils.d.ts +2 -2
  183. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  184. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  185. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  186. package/types/lib/evinser/evinser.d.ts +75 -8
  187. package/types/lib/evinser/evinser.d.ts.map +1 -1
  188. package/types/lib/helpers/bomSigner.d.ts +80 -4
  189. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  190. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  191. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  192. package/types/lib/inventory/atomUtils.d.ts +82 -10
  193. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  194. package/types/lib/inventory/auditCategories.d.ts +3 -0
  195. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  196. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  197. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  198. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  199. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  200. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  201. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  202. package/types/lib/inventory/deps.d.ts +59 -1
  203. package/types/lib/inventory/deps.d.ts.map +1 -1
  204. package/types/lib/inventory/dosai.d.ts +57 -8
  205. package/types/lib/inventory/dosai.d.ts.map +1 -1
  206. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  207. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  208. package/types/lib/inventory/dynamic.d.ts +10 -0
  209. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  210. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  211. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  212. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  213. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  214. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  215. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  216. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  217. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  218. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  219. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  220. package/types/lib/inventory/parentSelection.d.ts +34 -0
  221. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  222. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  223. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  224. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  225. package/types/lib/inventory/source.d.ts.map +1 -1
  226. package/types/lib/inventory/spdx.d.ts +9 -0
  227. package/types/lib/inventory/spdx.d.ts.map +1 -1
  228. package/types/lib/inventory/traceRunner.d.ts +12 -0
  229. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  230. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  231. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  232. package/types/lib/managers/docker.d.ts.map +1 -1
  233. package/types/lib/managers/oci.d.ts +3 -1
  234. package/types/lib/managers/oci.d.ts.map +1 -1
  235. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  236. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  237. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  238. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  239. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  240. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  241. package/types/lib/parsers/compileCommands.d.ts +115 -0
  242. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  243. package/types/lib/parsers/strictJson.d.ts +46 -0
  244. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  245. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  246. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  247. package/types/lib/parsers/terraformSource.d.ts +66 -0
  248. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  249. package/types/lib/server/server.d.ts.map +1 -1
  250. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  252. package/types/lib/validator/index.d.ts +8 -0
  253. package/types/lib/validator/index.d.ts.map +1 -1
  254. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  255. 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
+ }
@@ -1336,58 +1336,59 @@ export const getImage = async (fullImageName) => {
1336
1336
  let dockerCmd;
1337
1337
  if (needsCliFallback()) {
1338
1338
  dockerCmd = getContainerCliCmd();
1339
- let needsPull = true;
1340
- // Let's check the local cache first
1341
- let result = safeSpawnSync(dockerCmd, ["images", "--format=json"]);
1342
- if (result.status === 0 && result.stdout) {
1343
- for (const imgLine of result.stdout.split("\n")) {
1344
- try {
1345
- const imgObj = JSON.parse(Buffer.from(imgLine).toString());
1346
- if (`${imgObj.Repository}:${imgObj.Tag}` === fullImageName) {
1347
- needsPull = false;
1348
- break;
1349
- }
1350
- } catch (_err) {
1351
- // continue regardless of error
1352
- }
1353
- }
1354
- }
1355
- if (needsPull) {
1356
- result = safeSpawnSync(dockerCmd, ["pull", fullImageName]);
1357
- if (result.status !== 0 || result.error) {
1339
+ // The local image is checked with the reference itself. `image inspect`
1340
+ // resolves the reference exactly as the pull below would, so a fully
1341
+ // qualified docker.io/library/... name and a digest reference both find
1342
+ // a local image. The `images` listing compared Repository:Tag strings
1343
+ // and missed both. docker, podman, nerdctl and wslc all implement
1344
+ // `image inspect`.
1345
+ let inspectResult = safeSpawnSync(dockerCmd, [
1346
+ "image",
1347
+ "inspect",
1348
+ fullImageName,
1349
+ ]);
1350
+ if (inspectResult.status !== 0 || inspectResult.error) {
1351
+ const pullResult = safeSpawnSync(dockerCmd, ["pull", fullImageName]);
1352
+ if (pullResult.status !== 0 || pullResult.error) {
1358
1353
  if (isWslcCmd(dockerCmd)) {
1359
1354
  // wslc runs Docker Engine inside its own VM, so its errors may
1360
1355
  // mention a docker daemon; the Docker Desktop and DOCKER_CMD hints
1361
1356
  // below do not apply. Show wslc's own error instead.
1362
- if (isWslcSessionFailure(result.stderr)) {
1357
+ if (isWslcSessionFailure(pullResult.stderr)) {
1363
1358
  printWslcSessionFailureHint();
1364
- } else if (result.stderr) {
1365
- console.log(result.stderr);
1359
+ } else if (pullResult.stderr) {
1360
+ console.log(pullResult.stderr);
1366
1361
  }
1367
- } else if (result.stderr?.includes("docker daemon is not running")) {
1362
+ } else if (
1363
+ pullResult.stderr?.includes("docker daemon is not running")
1364
+ ) {
1368
1365
  console.log(
1369
1366
  "Ensure Docker for Desktop is running as an administrator with 'Exposing daemon on TCP without TLS' setting turned on.",
1370
1367
  );
1371
- } else if (result.stderr?.includes("not found")) {
1368
+ } else if (pullResult.stderr?.includes("not found")) {
1372
1369
  console.log(
1373
1370
  "Set the environment variable DOCKER_CMD to use an alternative command such as nerdctl or podman.",
1374
1371
  );
1375
- } else if (result.stderr) {
1376
- console.log(result.stderr);
1372
+ } else if (pullResult.stderr) {
1373
+ console.log(pullResult.stderr);
1377
1374
  }
1378
1375
  }
1379
- }
1380
- result = safeSpawnSync(dockerCmd, ["inspect", fullImageName]);
1381
- if (result.status !== 0 || result.error) {
1382
- if (result.stderr) {
1383
- console.log(result.stderr);
1376
+ inspectResult = safeSpawnSync(dockerCmd, [
1377
+ "image",
1378
+ "inspect",
1379
+ fullImageName,
1380
+ ]);
1381
+ }
1382
+ if (inspectResult.status !== 0 || inspectResult.error) {
1383
+ if (inspectResult.stderr) {
1384
+ console.log(inspectResult.stderr);
1384
1385
  }
1385
1386
  suggestWslcAlternative(dockerCmd);
1386
1387
  // Continue with the daemon client when the CLI fallback is unavailable
1387
1388
  // or unable to inspect the image.
1388
1389
  } else {
1389
1390
  try {
1390
- const stdout = result.stdout;
1391
+ const stdout = inspectResult.stdout;
1391
1392
  if (stdout) {
1392
1393
  const inspectData = JSON.parse(Buffer.from(stdout).toString());
1393
1394
  if (inspectData && Array.isArray(inspectData)) {
@@ -283,7 +283,10 @@ async function getOciToken(registry, repository, scope) {
283
283
  if (params.service)
284
284
  reqUrl.searchParams.set("service", params.service);
285
285
  reqUrl.searchParams.set("scope", `repository:${repository}:${scope}`);
286
+ // Tokens expire within minutes, so a cached one would be replayed
287
+ // after it stopped working.
286
288
  const options = {
289
+ cache: false,
287
290
  responseType: "json",
288
291
  timeout: getOciTimeoutMs(),
289
292
  };
@@ -410,11 +413,18 @@ async function pullBlob(registry, repository, digest, token) {
410
413
  *
411
414
  * @param {string} image OCI image reference (e.g. `"registry.example.com/org/app:tag"`)
412
415
  * @param {string} [platform] OCI platform string (e.g. `"linux/amd64"`); no-op for JS implementation
416
+ * @param {Object} [options]
417
+ * @param {function(Buffer): void} [options.checkBytes] Called with the raw bytes of the BOM layer that is about to be returned. An error it throws is rethrown, and no other referrer is tried.
413
418
  * @returns {Promise<Object|undefined>} Parsed CycloneDX BOM JSON object, or `undefined` if not found
414
419
  */
415
- export async function getBomWithOras(image, _platform = undefined) {
420
+ export async function getBomWithOras(
421
+ image,
422
+ _platform = undefined,
423
+ options = {},
424
+ ) {
416
425
  const { registry, repository, reference } = parseImageRef(image);
417
426
  const token = await getOciToken(registry, repository, "pull");
427
+ let rejection;
418
428
 
419
429
  try {
420
430
  const targetManifest = await fetchManifest(
@@ -488,9 +498,10 @@ export async function getBomWithOras(image, _platform = undefined) {
488
498
  bomLayer.digest,
489
499
  token,
490
500
  );
501
+ const text = blob.toString("utf8");
491
502
  let bomJson;
492
503
  try {
493
- bomJson = JSON.parse(blob.toString("utf8"));
504
+ bomJson = JSON.parse(text);
494
505
  } catch {
495
506
  // Defensive: the mediaType claimed JSON but the layer did not parse
496
507
  // (truncated/corrupt blob). Skip it and try the next candidate.
@@ -509,6 +520,14 @@ export async function getBomWithOras(image, _platform = undefined) {
509
520
  }
510
521
 
511
522
  if (isCycloneDxBom(bomJson)) {
523
+ if (options.checkBytes) {
524
+ try {
525
+ options.checkBytes(blob);
526
+ } catch (err) {
527
+ rejection = err;
528
+ break;
529
+ }
530
+ }
512
531
  return bomJson;
513
532
  }
514
533
  }
@@ -517,6 +536,9 @@ export async function getBomWithOras(image, _platform = undefined) {
517
536
  `Unable to pull the SBOM attachment for ${image} natively! ${e.message}`,
518
537
  );
519
538
  }
539
+ if (rejection) {
540
+ throw rejection;
541
+ }
520
542
  return undefined;
521
543
  }
522
544
 
@@ -197,3 +197,72 @@ export function resolveCmakeCacheFacts(map) {
197
197
  fetchContentBase,
198
198
  };
199
199
  }
200
+
201
+ /** CMake's compiler ids, mapped to the family names the C/C++ toolchain uses. */
202
+ const CMAKE_COMPILER_FAMILIES = {
203
+ GNU: "gcc",
204
+ Clang: "clang",
205
+ AppleClang: "apple-clang",
206
+ MSVC: "msvc",
207
+ IntelLLVM: "icx",
208
+ Intel: "icc",
209
+ NVIDIA: "nvcc",
210
+ NVHPC: "nvhpc",
211
+ PGI: "nvhpc",
212
+ };
213
+
214
+ /**
215
+ * Parse a compiler description CMake writes when it configures a build
216
+ * (`CMakeFiles/<cmake version>/CMake<LANG>Compiler.cmake`): the compiler's
217
+ * path, CMake's id for it and its version, all identified by CMake itself.
218
+ *
219
+ * @param {string} text File contents
220
+ * @returns {{language: string, compiler: string, family: string, id: string, version: string}|null}
221
+ * `null` when the text names no compiler
222
+ */
223
+ export function parseCmakeCompilerFile(text) {
224
+ if (!text || typeof text !== "string") {
225
+ return null;
226
+ }
227
+ const values = new Map();
228
+ for (const rawLine of text.split(/\r\n|\r|\n/)) {
229
+ const line = rawLine.trim();
230
+ if (!line.startsWith("set(CMAKE_") || !line.endsWith(")")) {
231
+ continue;
232
+ }
233
+ const body = line.slice(4, -1).trim();
234
+ const space = body.search(/\s/);
235
+ if (space < 0) {
236
+ continue;
237
+ }
238
+ const key = body.slice(0, space);
239
+ let value = body.slice(space).trim();
240
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
241
+ value = value.slice(1, -1);
242
+ }
243
+ if (!values.has(key)) {
244
+ values.set(key, value);
245
+ }
246
+ }
247
+ for (const [key, compiler] of values) {
248
+ const match = key.match(/^CMAKE_([A-Z]+)_COMPILER$/);
249
+ if (!match || !compiler) {
250
+ continue;
251
+ }
252
+ const language = match[1];
253
+ const id = values.get(`CMAKE_${language}_COMPILER_ID`) || "";
254
+ const variant = values.get(`CMAKE_${language}_COMPILER_FRONTEND_VARIANT`);
255
+ let family = CMAKE_COMPILER_FAMILIES[id] || id.toLowerCase() || "unknown";
256
+ if (id === "Clang" && variant === "MSVC") {
257
+ family = "clang-cl";
258
+ }
259
+ return {
260
+ language,
261
+ compiler,
262
+ family,
263
+ id,
264
+ version: values.get(`CMAKE_${language}_COMPILER_VERSION`) || "",
265
+ };
266
+ }
267
+ return null;
268
+ }
@@ -188,10 +188,15 @@ export function parseCmakeCommands(text) {
188
188
  return commands;
189
189
  }
190
190
 
191
+ // Upper bound on an expanded value; a set() chain that doubles a variable can
192
+ // otherwise grow it beyond the maximum string length.
193
+ const MAX_EXPANDED_LENGTH = 65536;
194
+
191
195
  /**
192
196
  * Replace `${VAR}` references with the values of simple `set()` calls. A
193
197
  * reference to an unknown variable is left in place, so callers can tell a
194
- * resolved value from an unresolved one.
198
+ * resolved value from an unresolved one. Expansion stops at the previous
199
+ * round once the text would exceed 64 KiB.
195
200
  *
196
201
  * @param {string} value Argument text
197
202
  * @param {Map<string,string>} variables Known variable values
@@ -203,6 +208,9 @@ export function expandCmakeVariables(value, variables) {
203
208
  let next = "";
204
209
  let i = 0;
205
210
  while (i < result.length) {
211
+ if (next.length > MAX_EXPANDED_LENGTH) {
212
+ return result;
213
+ }
206
214
  const start = result.indexOf("${", i);
207
215
  if (start === -1) {
208
216
  next += result.slice(i);
@@ -247,7 +255,13 @@ export function cmakeSetVariables(commands) {
247
255
  rest[0] === "CACHE" ||
248
256
  rest[0] === "PARENT_SCOPE"
249
257
  ) {
250
- variables.set(key, expandCmakeVariables(value, variables));
258
+ // An appending set(FOO "${FOO} ...") of an undefined FOO starts from an
259
+ // empty value in CMake; dropping the self-reference also keeps it from
260
+ // being substituted into itself on every later expansion round.
261
+ variables.set(
262
+ key,
263
+ expandCmakeVariables(value, variables).replaceAll(`\${${key}}`, ""),
264
+ );
251
265
  }
252
266
  }
253
267
  return variables;