@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
@@ -23,6 +23,12 @@ export const GOLEM_AUDIT_CATEGORIES = Object.freeze([
23
23
  "golem-compliance",
24
24
  ]);
25
25
 
26
+ /** Frozen list of Scala Evinse (scalasem) audit categories. */
27
+ export const SCALA_AUDIT_CATEGORIES = Object.freeze([
28
+ "scala-security",
29
+ "scala-compliance",
30
+ ]);
31
+
26
32
  /** Frozen list of Cargo (Rust) audit categories. */
27
33
  export const CARGO_AUDIT_CATEGORIES = Object.freeze([
28
34
  "cargo-security",
@@ -64,6 +70,7 @@ export const BOM_AUDIT_CATEGORY_ALIASES = Object.freeze({
64
70
  golem: [...GOLEM_AUDIT_CATEGORIES],
65
71
  hbom: [...HBOM_AUDIT_CATEGORIES],
66
72
  rust: [...CARGO_AUDIT_CATEGORIES],
73
+ scala: [...SCALA_AUDIT_CATEGORIES],
67
74
  host: [...HBOM_AUDIT_CATEGORIES, ...HOST_TOPOLOGY_AUDIT_CATEGORIES],
68
75
  });
69
76
 
@@ -0,0 +1,144 @@
1
+ import { recordDegradation } from "../core/buildLedger.js";
2
+ import { registerRunReset } from "../core/runState.js";
3
+
4
+ /**
5
+ * Text a build tool prints when its Maven repository answers HTTP 429, as
6
+ * recorded from each tool against a repository that rate limits. One small
7
+ * matcher per tool, plain substring tests on the combined output: the tools
8
+ * reword their messages between versions, so the matched fragment is the one
9
+ * every observed phrasing keeps (the status number in the tool's own wording).
10
+ *
11
+ * sbt, Mill and scala-cli resolve through Coursier, so they share the
12
+ * coursier matcher. The Coursier inside sbt 1.x reports the JDK's
13
+ * `Server returned HTTP response code: 429`; the one in sbt 2, Mill and
14
+ * scala-cli reports `retryable HTTP error: <url> (HTTP 429)`, with a
15
+ * `Retry-After` inside the parentheses when the server sent one.
16
+ *
17
+ * @type {Object<string, string[]>}
18
+ */
19
+ const RATE_LIMIT_MARKERS = {
20
+ maven: ["status code: 429", "Too Many Requests"],
21
+ gradle: ["Received status code 429", "[HTTP 429: http"],
22
+ coursier: ["response code: 429", "(HTTP 429"],
23
+ };
24
+
25
+ /**
26
+ * The matcher family a tool belongs to.
27
+ *
28
+ * @type {Object<string, string>}
29
+ */
30
+ const TOOL_MATCHER = {
31
+ maven: "maven",
32
+ gradle: "gradle",
33
+ sbt: "coursier",
34
+ mill: "coursier",
35
+ "scala-cli": "coursier",
36
+ };
37
+
38
+ /**
39
+ * How each tool is moved to a mirror, named in one hint per tool and scan.
40
+ * sbt reads its own repositories file; Mill and scala-cli read Coursier's
41
+ * mirror.properties, whose entry spells the semicolon-separated format
42
+ * because a comma list is silently ignored.
43
+ *
44
+ * @type {Object<string, string>}
45
+ */
46
+ const MIRROR_SETTINGS = {
47
+ maven:
48
+ "Point Maven at a mirror with a <mirror> entry in the Maven settings.xml, or set MVNW_REPOURL for the Maven wrapper.",
49
+ gradle:
50
+ "Point Gradle at a mirror by declaring the repository in a Gradle init script.",
51
+ sbt: "Point sbt at a mirror by listing it in ~/.sbt/repositories and passing -Dsbt.override.build.repos=true in SBT_OPTS or .sbtopts.",
52
+ coursier:
53
+ "Point Coursier at a mirror with mirror.properties, setting central.from and central.to (separate several central.from URLs with semicolons, not commas).",
54
+ };
55
+
56
+ /**
57
+ * Tools whose later invocations or retries cdxgen skips once one met a rate
58
+ * limit. Mill and scala-cli resolve in a single invocation.
59
+ *
60
+ * @type {Set<string>}
61
+ */
62
+ const STOPPED_TOOLS = new Set(["maven", "gradle", "sbt"]);
63
+
64
+ /**
65
+ * Tools whose repository answered HTTP 429 during the current scan. Further
66
+ * invocations of that tool would only pay the same back-off for the same
67
+ * empty answer.
68
+ *
69
+ * @type {Set<string>}
70
+ */
71
+ const rateLimitedTools = new Set();
72
+
73
+ registerRunReset("inventory.buildToolRateLimit", () => {
74
+ rateLimitedTools.clear();
75
+ });
76
+
77
+ /**
78
+ * Whether a tool's combined output names a rate limit from its repository.
79
+ * The answer text is treated as untrusted: only fixed substrings are looked
80
+ * for, nothing from the output is kept.
81
+ *
82
+ * @param {string} output Combined stdout and stderr of one invocation.
83
+ * @param {string} tool One of maven, gradle, sbt, mill, scala-cli.
84
+ * @returns {boolean} True when the output names an HTTP 429 answer.
85
+ */
86
+ export function detectBuildToolRateLimit(output, tool) {
87
+ const markers = RATE_LIMIT_MARKERS[TOOL_MATCHER[tool]];
88
+ if (!markers || typeof output !== "string" || !output) {
89
+ return false;
90
+ }
91
+ return markers.some((marker) => output.includes(marker));
92
+ }
93
+
94
+ /**
95
+ * Whether a tool was already found rate limited in this scan, so the caller
96
+ * can skip its remaining invocations.
97
+ *
98
+ * @param {string} tool One of maven, gradle, sbt, mill, scala-cli.
99
+ * @returns {boolean}
100
+ */
101
+ export function isBuildToolRateLimited(tool) {
102
+ return rateLimitedTools.has(tool);
103
+ }
104
+
105
+ /**
106
+ * Read a completed invocation's output for a rate limit. When one is found,
107
+ * the tool is remembered for the scan, one hint naming the tool's mirror
108
+ * setting is printed, and the `build.rate-limited` degradation is recorded.
109
+ * The first invocation that shows the rate limit is the one recorded; later
110
+ * ones add nothing, because the same answer ends them all.
111
+ *
112
+ * @param {string} tool One of maven, gradle, sbt, mill, scala-cli.
113
+ * @param {string|undefined} output Combined output, as commandOutputText
114
+ * returns it for a spawn result.
115
+ * @param {Object} [context] Call-site facts for the degradation event.
116
+ * @param {string} [context.command] The redacted command that was attempted.
117
+ * @param {number} [context.exitCode] The invocation's exit status.
118
+ * @returns {boolean} True when this output names a rate limit.
119
+ */
120
+ export function noteBuildToolRateLimit(tool, output, context = {}) {
121
+ if (!detectBuildToolRateLimit(output, tool)) {
122
+ return false;
123
+ }
124
+ if (isBuildToolRateLimited(tool)) {
125
+ return true;
126
+ }
127
+ rateLimitedTools.add(tool);
128
+ const stopped = STOPPED_TOOLS.has(tool)
129
+ ? `, so cdxgen runs no further ${tool} resolution in this scan and`
130
+ : ", so";
131
+ console.warn(
132
+ `The repository ${tool} downloads from is rate limiting this machine (HTTP 429)${stopped} its dependency tree is incomplete. ${MIRROR_SETTINGS[tool] || MIRROR_SETTINGS[TOOL_MATCHER[tool]]} cdxgen's own lookups can be moved with MAVEN_CENTRAL_URL.`,
133
+ );
134
+ recordDegradation("build.rate-limited", {
135
+ ecosystem: "java",
136
+ tool,
137
+ impact: "transitive-deps",
138
+ command: context.command,
139
+ exitCode: context.exitCode,
140
+ detail: `The repository ${tool} downloads from answered HTTP 429, so ${tool} resolved no dependencies.`,
141
+ outputExcerpt: output,
142
+ });
143
+ return true;
144
+ }
@@ -1,5 +1,6 @@
1
1
  /**
2
- * `cdxgen cache` subcommand — inspect and purge the metadata fetch cache.
2
+ * `cdxgen cache` subcommand — inspect and purge the metadata fetch cache that
3
+ * cdxrs and the JS batch pool share.
3
4
  *
4
5
  * This module operates directly on the filesystem because JS is the authority
5
6
  * for the cache directory. The cache layout is:
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * This module is the ONLY place that spawns the `cdxrs` binary. It enforces
5
5
  * the governing invariant: **no failure mode may abort SBOM generation.**
6
- * Every failure (missing binary, non-zero exit, timeout, malformed stdout,
7
- * version-major mismatch, CDXGEN_RS_DISABLE) logs once at `warn` and returns
8
- * a sentinel that makes the caller take the JS path.
6
+ * Every failure (missing binary, non-zero exit, timeout, oversized stdout,
7
+ * malformed stdout, version-major mismatch, CDXGEN_RS_DISABLE) logs once at
8
+ * `warn` and returns a sentinel that makes the caller take the JS path.
9
9
  *
10
10
  * Protocol:
11
11
  * cdxrs <subcommand> --input <file|-> --output <file|-> --format json
@@ -42,6 +42,45 @@ const DEFAULT_TIMEOUT_MS = 30_000;
42
42
  */
43
43
  const MAX_STDIN_BYTES = 32 * 1024 * 1024;
44
44
 
45
+ /**
46
+ * V8's maximum string length on 64-bit builds (2^29 − 24). Node exposes no
47
+ * API for it, so it is stated exactly as the runtime's own ERR_STRING_TOO_LONG
48
+ * message does. `Buffer#toString()` throws above it — the crash from issue
49
+ * 4393, where a pnpm workspace's registry fetch crossed the ceiling.
50
+ */
51
+ const V8_MAX_STRING_CHARS = 0x1fffffe8;
52
+
53
+ /**
54
+ * Ceiling on the stdout bytes collected from a cdxrs child. stdout must end up
55
+ * as a single JS string, so a payload above V8's string limit can never be
56
+ * delivered no matter how long the child runs: buffering it only burns memory
57
+ * until the concat throws. Past the ceiling the child is killed and the caller
58
+ * takes the JS path. Counting bytes (never smaller than the decoded character
59
+ * count) keeps the boundary safe for multibyte output.
60
+ */
61
+ const DEFAULT_MAX_STDOUT_BYTES = V8_MAX_STRING_CHARS;
62
+
63
+ /**
64
+ * Resolve the stdout ceiling for a run. CDXGEN_RS_MAX_STDOUT_BYTES lowers (or
65
+ * raises) it — used by tests and by hosts that want the failover earlier.
66
+ *
67
+ * @returns {number} Maximum stdout bytes to collect before failing over.
68
+ */
69
+ function maxStdoutBytes() {
70
+ const raw = readEnvironmentVariable("CDXGEN_RS_MAX_STDOUT_BYTES");
71
+ if (!raw) {
72
+ return DEFAULT_MAX_STDOUT_BYTES;
73
+ }
74
+ const parsed = Number.parseInt(raw, 10);
75
+ if (Number.isInteger(parsed) && parsed > 0) {
76
+ return parsed;
77
+ }
78
+ console.warn(
79
+ `cdxrs: ignoring malformed CDXGEN_RS_MAX_STDOUT_BYTES "${raw}"; using the default ${DEFAULT_MAX_STDOUT_BYTES} bytes.`,
80
+ );
81
+ return DEFAULT_MAX_STDOUT_BYTES;
82
+ }
83
+
45
84
  /** Sentinel returned when the Rust path is unavailable. Callers take JS path. */
46
85
  export const CDXRS_FALLBACK = Object.freeze({
47
86
  ok: false,
@@ -164,6 +203,11 @@ function probeVersion(binPath) {
164
203
  *
165
204
  * Passing `content` is the normal case, since cdxgen holds BOMs in memory.
166
205
  *
206
+ * Collected stdout is bounded: it must become a single JS string, so a child
207
+ * that would exceed V8's string limit (by default; see
208
+ * CDXGEN_RS_MAX_STDOUT_BYTES) is killed and reported as a fallback instead of
209
+ * crashing the process at the concat.
210
+ *
167
211
  * @param {string} subcommand The subcommand to run (e.g. "info").
168
212
  * @param {Object} opts
169
213
  * @param {string|Buffer} [opts.content] BOM data to feed over stdin.
@@ -284,6 +328,9 @@ export async function runCdxrs(subcommand, opts = {}) {
284
328
  let timedOut = false;
285
329
  const stdoutChunks = [];
286
330
  const stderrChunks = [];
331
+ const stdoutLimit = maxStdoutBytes();
332
+ let stdoutBytes = 0;
333
+ let stdoutOverflow = false;
287
334
 
288
335
  const pluginRuntime = getDefaultPluginRuntime();
289
336
  const env = { ...process.env };
@@ -313,6 +360,17 @@ export async function runCdxrs(subcommand, opts = {}) {
313
360
  child.stdin.end(stdinContent);
314
361
  }
315
362
 
363
+ // Kill the process group (-pid) so any child processes of cdxrs die too.
364
+ // Best effort: the process may have already exited, in which case the
365
+ // close/error handlers take over.
366
+ const killProcessGroup = () => {
367
+ try {
368
+ process.kill(-child.pid, "SIGKILL");
369
+ } catch {
370
+ child.kill("SIGKILL");
371
+ }
372
+ };
373
+
316
374
  const timer = setTimeout(() => {
317
375
  timedOut = true;
318
376
  if (DEBUG_MODE) {
@@ -320,16 +378,21 @@ export async function runCdxrs(subcommand, opts = {}) {
320
378
  `cdxrs: timeout after ${timeoutMs}ms, killing process group.`,
321
379
  );
322
380
  }
323
- try {
324
- // Kill the process group (-pid) so any child processes of cdxrs die too.
325
- process.kill(-child.pid, "SIGKILL");
326
- } catch {
327
- // Process may have already exited; fall through to error handling.
328
- child.kill("SIGKILL");
329
- }
381
+ killProcessGroup();
330
382
  }, timeoutMs);
331
383
 
332
384
  child.stdout?.on("data", (chunk) => {
385
+ if (stdoutOverflow) {
386
+ return;
387
+ }
388
+ stdoutBytes += chunk.length;
389
+ if (stdoutBytes > stdoutLimit) {
390
+ // The payload can never become a deliverable string, so stop
391
+ // buffering, kill the child, and let the close handler fall back.
392
+ stdoutOverflow = true;
393
+ killProcessGroup();
394
+ return;
395
+ }
333
396
  stdoutChunks.push(chunk);
334
397
  });
335
398
 
@@ -365,8 +428,36 @@ export async function runCdxrs(subcommand, opts = {}) {
365
428
  return;
366
429
  }
367
430
 
368
- const stdout = Buffer.concat(stdoutChunks).toString();
369
- const stderr = Buffer.concat(stderrChunks).toString();
431
+ // Checked before the exit code: the child was killed mid-stream for
432
+ // producing more stdout than can ever be delivered as a string, so even
433
+ // an exit-0 result is an undeliverable truncation (issue 4393).
434
+ if (stdoutOverflow) {
435
+ console.warn(
436
+ `cdxrs: stdout passed ${stdoutLimit} bytes (a string cannot exceed ${V8_MAX_STRING_CHARS} characters), so the child was stopped.`,
437
+ );
438
+ resolve({ ...CDXRS_FALLBACK, reason: "stdout-too-large" });
439
+ return;
440
+ }
441
+
442
+ let stdout;
443
+ try {
444
+ stdout = Buffer.concat(stdoutChunks).toString();
445
+ } catch (err) {
446
+ // The byte cap above should make this unreachable; it is kept so that
447
+ // no failure inside this handler can abort SBOM generation.
448
+ console.warn(
449
+ `cdxrs: could not collect child stdout (${err.message}), falling back to JS path.`,
450
+ );
451
+ resolve({ ...CDXRS_FALLBACK, reason: "stdout-collect-failed" });
452
+ return;
453
+ }
454
+ let stderr = "";
455
+ try {
456
+ stderr = Buffer.concat(stderrChunks).toString();
457
+ } catch {
458
+ // stderr carries NDJSON logs, already forwarded as they arrived;
459
+ // dropping the raw tail must not fail a run with valid stdout.
460
+ }
370
461
 
371
462
  // For the validate subcommand, exit code 3 means "validation failure
372
463
  // with findings on stdout" — the operation succeeded, the BOM just has
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Where a C/C++ project's build trees are: the build directories its CMake
3
+ * presets configure, and the conventional ones (`build`, `build-<name>`,
4
+ * `out`, `builddir`, `cmake-build-<name>`). Shared by the lookups for
5
+ * `CMakeCache.txt` and for the compilation database, so both search the same
6
+ * places in the same order.
7
+ */
8
+
9
+ import { readdirSync, readFileSync, statSync } from "node:fs";
10
+ import { platform } from "node:os";
11
+ import {
12
+ basename,
13
+ delimiter,
14
+ dirname,
15
+ isAbsolute,
16
+ join,
17
+ relative,
18
+ resolve,
19
+ } from "node:path";
20
+
21
+ import { isSecureMode, readEnvironmentVariable } from "../core/activity.js";
22
+ import {
23
+ expandPresetMacros,
24
+ parseCmakePresets,
25
+ resolveConfigurePresets,
26
+ } from "../parsers/cmakePresets.js";
27
+
28
+ /** Presets files CMake reads from the source directory, in order. */
29
+ const PRESETS_FILES = ["CMakePresets.json", "CMakeUserPresets.json"];
30
+
31
+ /** A presets file larger than this is not read. */
32
+ const MAX_PRESETS_FILE_BYTES = 4 * 1024 * 1024;
33
+
34
+ /** At most this many presets files are read, includes counted. */
35
+ const MAX_PRESETS_FILES = 64;
36
+
37
+ /** At most this many build directories are proposed. */
38
+ const MAX_BUILD_DIRS = 128;
39
+
40
+ const HOST_SYSTEM_NAMES = {
41
+ darwin: "Darwin",
42
+ linux: "Linux",
43
+ win32: "Windows",
44
+ freebsd: "FreeBSD",
45
+ openbsd: "OpenBSD",
46
+ netbsd: "NetBSD",
47
+ sunos: "SunOS",
48
+ aix: "AIX",
49
+ };
50
+
51
+ /**
52
+ * Whether `child` is `parent` or inside it.
53
+ *
54
+ * @param {string} child Path to test
55
+ * @param {string} parent Directory
56
+ * @returns {boolean}
57
+ */
58
+ export function isInsideDir(child, parent) {
59
+ const rel = relative(resolve(parent), resolve(child));
60
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
61
+ }
62
+
63
+ function isDirectory(p) {
64
+ try {
65
+ return statSync(p).isDirectory();
66
+ } catch (_err) {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ function readSmallFile(p) {
72
+ try {
73
+ const stat = statSync(p);
74
+ if (!stat.isFile() || stat.size > MAX_PRESETS_FILE_BYTES) {
75
+ return undefined;
76
+ }
77
+ return readFileSync(p, "utf-8");
78
+ } catch (_err) {
79
+ return undefined;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * CMake's `${hostSystemName}` for this host.
85
+ *
86
+ * @returns {string}
87
+ */
88
+ export function hostSystemName() {
89
+ return HOST_SYSTEM_NAMES[platform()] || platform();
90
+ }
91
+
92
+ /**
93
+ * Read a project's presets documents: `CMakePresets.json` and
94
+ * `CMakeUserPresets.json`, each followed by the files it includes (schema
95
+ * version 4 and later). In secure mode an include outside the project is not
96
+ * read.
97
+ *
98
+ * @param {string} root Project source directory
99
+ * @returns {Array<{file: string, fileDir: string, document: Object}>}
100
+ */
101
+ export function readCmakePresetDocuments(root) {
102
+ const sourceDir = resolve(root);
103
+ const documents = [];
104
+ const seen = new Set();
105
+ const visit = (file) => {
106
+ const abs = resolve(file);
107
+ if (seen.has(abs) || seen.size >= MAX_PRESETS_FILES) {
108
+ return;
109
+ }
110
+ seen.add(abs);
111
+ if (isSecureMode && !isInsideDir(abs, sourceDir)) {
112
+ return;
113
+ }
114
+ const document = parseCmakePresets(readSmallFile(abs));
115
+ if (!document) {
116
+ return;
117
+ }
118
+ const fileDir = dirname(abs);
119
+ documents.push({ file: abs, fileDir, document });
120
+ for (const include of document.include) {
121
+ const expanded = expandPresetMacros(include, {
122
+ sourceDir,
123
+ sourceParentDir: dirname(sourceDir),
124
+ sourceDirName: basename(sourceDir),
125
+ fileDir,
126
+ pathListSep: delimiter,
127
+ hostSystemName: hostSystemName(),
128
+ penv: readEnvironmentVariable,
129
+ });
130
+ visit(isAbsolute(expanded) ? expanded : join(fileDir, expanded));
131
+ }
132
+ };
133
+ for (const name of PRESETS_FILES) {
134
+ visit(join(sourceDir, name));
135
+ }
136
+ return documents;
137
+ }
138
+
139
+ /**
140
+ * The visible configure presets of a project, resolved and with their macros
141
+ * expanded for this host.
142
+ *
143
+ * @param {string} root Project source directory
144
+ * @returns {Object[]} See `resolveConfigurePresets`
145
+ */
146
+ export function readCmakeConfigurePresets(root) {
147
+ const sourceDir = resolve(root);
148
+ const documents = readCmakePresetDocuments(sourceDir);
149
+ if (!documents.length) {
150
+ return [];
151
+ }
152
+ return resolveConfigurePresets(documents, {
153
+ sourceDir,
154
+ sourceParentDir: dirname(sourceDir),
155
+ sourceDirName: basename(sourceDir),
156
+ hostSystemName: hostSystemName(),
157
+ pathListSep: delimiter,
158
+ penv: readEnvironmentVariable,
159
+ });
160
+ }
161
+
162
+ function childDirs(dir, accept = () => true) {
163
+ try {
164
+ return readdirSync(dir, { withFileTypes: true })
165
+ .filter((e) => e.isDirectory() && accept(e.name))
166
+ .map((e) => join(dir, e.name))
167
+ .sort();
168
+ } catch (_err) {
169
+ return [];
170
+ }
171
+ }
172
+
173
+ /** The order conventional build directories are searched in. */
174
+ function conventionalRank(dir) {
175
+ const name = basename(dir);
176
+ if (name === "build") {
177
+ return 0;
178
+ }
179
+ if (name === "out") {
180
+ return 2;
181
+ }
182
+ if (name === "builddir") {
183
+ return 3;
184
+ }
185
+ if (name.startsWith("cmake-build-")) {
186
+ return 4;
187
+ }
188
+ return 1;
189
+ }
190
+
191
+ /**
192
+ * The directories a project's build trees may be in, most specific first:
193
+ * the directory of an explicit `--cmake-cache`, the build directories of its
194
+ * configure presets, then the `build`, `build-<name>`, `out`, `builddir` and
195
+ * `cmake-build-<name>` directories at the project root, and the directories
196
+ * one level below `build`, `out` and `out/build` (where Visual Studio puts
197
+ * preset builds). Only existing directories are listed; in secure mode, only
198
+ * those inside the project.
199
+ *
200
+ * @param {string} root Project source directory
201
+ * @param {Object} [options] CLI options (`cmakeCache`)
202
+ * @returns {string[]} Absolute directories, without duplicates
203
+ */
204
+ export function cmakeBuildDirCandidates(root, options = {}) {
205
+ const sourceDir = resolve(root);
206
+ const dirs = [];
207
+ const add = (d) => {
208
+ if (!d || dirs.length >= MAX_BUILD_DIRS) {
209
+ return;
210
+ }
211
+ const abs = resolve(d);
212
+ if (dirs.includes(abs) || abs === sourceDir) {
213
+ return;
214
+ }
215
+ if (isSecureMode && !isInsideDir(abs, sourceDir)) {
216
+ return;
217
+ }
218
+ if (isDirectory(abs)) {
219
+ dirs.push(abs);
220
+ }
221
+ };
222
+ if (options.cmakeCache) {
223
+ add(dirname(resolve(`${options.cmakeCache}`)));
224
+ }
225
+ if (!isDirectory(sourceDir)) {
226
+ return dirs;
227
+ }
228
+ for (const preset of readCmakeConfigurePresets(sourceDir)) {
229
+ add(preset.binaryDir);
230
+ }
231
+ const rootBuildDirs = childDirs(
232
+ sourceDir,
233
+ (name) =>
234
+ name.startsWith("build") ||
235
+ name === "out" ||
236
+ name.startsWith("cmake-build-"),
237
+ );
238
+ rootBuildDirs.sort(
239
+ (a, b) => conventionalRank(a) - conventionalRank(b) || a.localeCompare(b),
240
+ );
241
+ for (const d of rootBuildDirs) {
242
+ add(d);
243
+ }
244
+ for (const parent of [
245
+ join(sourceDir, "build"),
246
+ join(sourceDir, "out"),
247
+ join(sourceDir, "out", "build"),
248
+ ]) {
249
+ for (const d of childDirs(parent)) {
250
+ add(d);
251
+ }
252
+ }
253
+ return dirs;
254
+ }