@cdxgen/cdxgen 13.0.1 → 13.2.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 (286) hide show
  1. package/README.md +30 -16
  2. package/bin/audit.js +6 -0
  3. package/bin/cdxgen.js +141 -7
  4. package/bin/evinse.js +3 -1
  5. package/data/component-tags.json +8 -1
  6. package/data/crypto-oid.json +88 -12
  7. package/data/frameworks-list.json +86 -1
  8. package/data/remediations.json +1167 -0
  9. package/data/rules/ai-bom.yaml +78 -0
  10. package/data/rules/asar-archives.yaml +1 -1
  11. package/data/rules/build-fidelity.yaml +782 -0
  12. package/data/rules/cargo-rust.yaml +132 -0
  13. package/data/rules/mcp-servers.yaml +122 -0
  14. package/data/rules/package-integrity.yaml +13 -13
  15. package/lib/audit/index.js +48 -4
  16. package/lib/audit/scoring.js +2 -8
  17. package/lib/audit/targets.js +6 -1
  18. package/lib/cli/bomAssembly.js +32 -1
  19. package/lib/cli/cliOptions.js +94 -7
  20. package/lib/cli/index.js +268 -49
  21. package/lib/cli/jsBom.js +91 -5
  22. package/lib/cli/jvmBom.js +1515 -1314
  23. package/lib/cli/managedBom.js +249 -28
  24. package/lib/cli/nativeBom.js +986 -10
  25. package/lib/core/activity.js +120 -2
  26. package/lib/core/buildLedger.js +799 -0
  27. package/lib/core/deferredExit.js +141 -0
  28. package/lib/core/env.js +108 -2
  29. package/lib/core/fs.js +60 -0
  30. package/lib/core/logger.js +12 -99
  31. package/lib/core/severity.js +16 -0
  32. package/lib/core/syncFileWriter.js +106 -0
  33. package/lib/core/userAgent.js +151 -0
  34. package/lib/ecosystems/cargoCache.js +364 -0
  35. package/lib/ecosystems/caxa.js +13 -5
  36. package/lib/ecosystems/cppEvidence.js +40 -2
  37. package/lib/ecosystems/ecosystems.js +250 -91
  38. package/lib/ecosystems/golem.js +11 -1
  39. package/lib/ecosystems/gradleutils.js +498 -17
  40. package/lib/ecosystems/kosi.js +1256 -0
  41. package/lib/ecosystems/parsers-crystal.js +179 -0
  42. package/lib/ecosystems/parsers-dotnet.js +69 -39
  43. package/lib/ecosystems/parsers-elm.js +816 -0
  44. package/lib/ecosystems/parsers-gleam.js +1 -1
  45. package/lib/ecosystems/parsers-js.js +26 -0
  46. package/lib/ecosystems/parsers-julia.js +268 -0
  47. package/lib/ecosystems/parsers-jvm.js +9 -0
  48. package/lib/ecosystems/parsers-lua.js +218 -0
  49. package/lib/ecosystems/parsers-misc.js +188 -2
  50. package/lib/ecosystems/parsers-nim.js +221 -0
  51. package/lib/ecosystems/parsers-opam.js +180 -0
  52. package/lib/ecosystems/parsers-perl.js +277 -0
  53. package/lib/ecosystems/parsers-python.js +172 -36
  54. package/lib/ecosystems/parsers-r.js +148 -0
  55. package/lib/ecosystems/parsers-rebar.js +411 -0
  56. package/lib/ecosystems/parsers-rust.js +944 -19
  57. package/lib/ecosystems/parsers-spack.js +204 -0
  58. package/lib/ecosystems/parsers-stack.js +264 -0
  59. package/lib/ecosystems/parsers-terraform.js +194 -0
  60. package/lib/ecosystems/pypiClassifiers.js +326 -0
  61. package/lib/ecosystems/pythonutils.js +49 -1
  62. package/lib/ecosystems/rubyutils.js +13 -0
  63. package/lib/ecosystems/rusi.js +126 -6
  64. package/lib/evinser/evinser.js +141 -13
  65. package/lib/helpers/agenticToolutils.js +925 -0
  66. package/lib/helpers/remote/dependency-track.js +81 -14
  67. package/lib/inventory/agentFormulationParser.js +57 -4
  68. package/lib/inventory/aiCollector.js +504 -10
  69. package/lib/inventory/aiInventory.js +12 -0
  70. package/lib/inventory/analyzer.js +488 -30
  71. package/lib/inventory/astWorker.js +6 -0
  72. package/lib/inventory/auditCategories.js +17 -0
  73. package/lib/inventory/cbomutils.js +30 -0
  74. package/lib/inventory/cdxrs.js +7 -2
  75. package/lib/inventory/communityAiConfigParser.js +215 -2
  76. package/lib/inventory/depsUtils.js +72 -0
  77. package/lib/inventory/dosai.js +508 -22
  78. package/lib/inventory/envcontext.js +215 -24
  79. package/lib/inventory/evidenceUtils.js +13 -0
  80. package/lib/inventory/fetchBatch.js +131 -2
  81. package/lib/inventory/fetchRate.js +74 -11
  82. package/lib/inventory/forgeEnricher.js +3 -2
  83. package/lib/inventory/jvmToolEnv.js +594 -0
  84. package/lib/inventory/mcpConfigParser.js +166 -2
  85. package/lib/inventory/mcpDiscovery.js +11 -0
  86. package/lib/inventory/mcpServerManifest.js +389 -0
  87. package/lib/inventory/mcpbBundle.js +419 -0
  88. package/lib/inventory/osinfo.js +1 -0
  89. package/lib/inventory/plugins.js +3 -0
  90. package/lib/inventory/protobom.js +54 -88
  91. package/lib/inventory/protobomLoader.js +4 -0
  92. package/lib/inventory/rustFormulationParser.js +1 -1
  93. package/lib/inventory/source.js +10 -4
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/toolRequirements.js +903 -0
  97. package/lib/managers/binary.js +24 -0
  98. package/lib/managers/docker.js +26 -5
  99. package/lib/parsers/huggingfaceUtils.js +15 -7
  100. package/lib/parsers/onnx.js +294 -0
  101. package/lib/parsers/safetensors.js +114 -0
  102. package/lib/server/server.js +1 -1
  103. package/lib/stages/postgen/annotationBuilder.js +97 -0
  104. package/lib/stages/postgen/auditBom.js +76 -79
  105. package/lib/stages/postgen/introspection/annotate.js +320 -0
  106. package/lib/stages/postgen/introspection/commandFacts.js +293 -0
  107. package/lib/stages/postgen/introspection/emit.js +192 -0
  108. package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
  109. package/lib/stages/postgen/introspection/markers.js +250 -0
  110. package/lib/stages/postgen/introspection/reflect.js +1214 -0
  111. package/lib/stages/postgen/introspection/report.js +1208 -0
  112. package/lib/stages/postgen/introspection/score.js +1067 -0
  113. package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
  114. package/lib/stages/postgen/parentEdge.js +156 -0
  115. package/lib/stages/postgen/postgen.js +284 -3
  116. package/lib/stages/postgen/ruleEngine.js +17 -0
  117. package/lib/stages/pregen/envAudit.js +42 -2
  118. package/lib/stages/pregen/pregen.js +1171 -6
  119. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  120. package/lib/validator/bomValidator.js +11 -2
  121. package/package.json +26 -26
  122. package/types/lib/audit/index.d.ts.map +1 -1
  123. package/types/lib/audit/scoring.d.ts +2 -8
  124. package/types/lib/audit/scoring.d.ts.map +1 -1
  125. package/types/lib/audit/targets.d.ts.map +1 -1
  126. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  127. package/types/lib/cli/cliOptions.d.ts +2 -1
  128. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  129. package/types/lib/cli/index.d.ts +10 -2
  130. package/types/lib/cli/index.d.ts.map +1 -1
  131. package/types/lib/cli/jsBom.d.ts +23 -0
  132. package/types/lib/cli/jsBom.d.ts.map +1 -1
  133. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  134. package/types/lib/cli/managedBom.d.ts +33 -0
  135. package/types/lib/cli/managedBom.d.ts.map +1 -1
  136. package/types/lib/cli/nativeBom.d.ts +88 -8
  137. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  138. package/types/lib/core/activity.d.ts.map +1 -1
  139. package/types/lib/core/buildLedger.d.ts +272 -0
  140. package/types/lib/core/buildLedger.d.ts.map +1 -0
  141. package/types/lib/core/deferredExit.d.ts +84 -0
  142. package/types/lib/core/deferredExit.d.ts.map +1 -0
  143. package/types/lib/core/env.d.ts +43 -1
  144. package/types/lib/core/env.d.ts.map +1 -1
  145. package/types/lib/core/fs.d.ts +21 -0
  146. package/types/lib/core/fs.d.ts.map +1 -1
  147. package/types/lib/core/logger.d.ts +2 -11
  148. package/types/lib/core/logger.d.ts.map +1 -1
  149. package/types/lib/core/severity.d.ts +11 -0
  150. package/types/lib/core/severity.d.ts.map +1 -0
  151. package/types/lib/core/syncFileWriter.d.ts +33 -0
  152. package/types/lib/core/syncFileWriter.d.ts.map +1 -0
  153. package/types/lib/core/userAgent.d.ts +56 -0
  154. package/types/lib/core/userAgent.d.ts.map +1 -0
  155. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  156. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  157. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  158. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  159. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  160. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  161. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  162. package/types/lib/ecosystems/gradleutils.d.ts +53 -0
  163. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  164. package/types/lib/ecosystems/kosi.d.ts +73 -0
  165. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  166. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  167. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  168. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  169. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  170. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  171. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  172. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  173. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  174. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  176. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  177. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  178. package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
  179. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  180. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  181. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  182. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  183. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  184. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  185. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  187. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  188. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  189. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  190. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  191. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  192. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  193. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  194. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  195. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  196. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  197. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  198. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  199. package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
  200. package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
  201. package/types/lib/ecosystems/pythonutils.d.ts +21 -1
  202. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  203. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  205. package/types/lib/evinser/evinser.d.ts +34 -1
  206. package/types/lib/evinser/evinser.d.ts.map +1 -1
  207. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  208. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  209. package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
  210. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  211. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  212. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  213. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  214. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +12 -0
  216. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  217. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  218. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  219. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  220. package/types/lib/inventory/dosai.d.ts +43 -0
  221. package/types/lib/inventory/dosai.d.ts.map +1 -1
  222. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  223. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  224. package/types/lib/inventory/fetchBatch.d.ts +43 -0
  225. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  226. package/types/lib/inventory/fetchRate.d.ts +9 -6
  227. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  228. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  229. package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
  230. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
  231. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  232. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  233. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  234. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  235. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  236. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  237. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  238. package/types/lib/inventory/plugins.d.ts.map +1 -1
  239. package/types/lib/inventory/protobom.d.ts +18 -13
  240. package/types/lib/inventory/protobom.d.ts.map +1 -1
  241. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  242. package/types/lib/inventory/source.d.ts.map +1 -1
  243. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  244. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  245. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  246. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  247. package/types/lib/inventory/toolRequirements.d.ts +216 -0
  248. package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
  249. package/types/lib/managers/binary.d.ts.map +1 -1
  250. package/types/lib/managers/docker.d.ts.map +1 -1
  251. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  252. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  253. package/types/lib/parsers/onnx.d.ts +28 -0
  254. package/types/lib/parsers/onnx.d.ts.map +1 -0
  255. package/types/lib/parsers/safetensors.d.ts +22 -0
  256. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  257. package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
  258. package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
  259. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  260. package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
  261. package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
  262. package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
  263. package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
  264. package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
  265. package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
  266. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
  267. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
  268. package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
  269. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
  270. package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
  271. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
  272. package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
  273. package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
  275. package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
  276. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
  277. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
  278. package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
  279. package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
  280. package/types/lib/stages/postgen/postgen.d.ts +35 -2
  281. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  282. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  283. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  284. package/types/lib/stages/pregen/pregen.d.ts +131 -0
  285. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  286. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -66,11 +66,13 @@ import {
66
66
  prefetchEnabled,
67
67
  prefetchedResponse,
68
68
  prefetchJson,
69
+ withHostRateLimit,
69
70
  } from "../inventory/fetchBatch.js";
70
71
  import {
71
72
  applyPurl,
72
73
  encodeForPurl,
73
74
  genericPurl,
75
+ nugetPurl,
74
76
  pypiPurl,
75
77
  } from "../inventory/purl.js";
76
78
  import {
@@ -80,6 +82,14 @@ import {
80
82
  } from "../inventory/spdx.js";
81
83
  import { extractLicenseText, extractRepoUrl } from "../parsers/htmlExtract.js";
82
84
  import { xml2js } from "../parsers/xml.js";
85
+ import {
86
+ localCargoMetadataEnabled,
87
+ readCargoCacheMetadata,
88
+ } from "./cargoCache.js";
89
+ import {
90
+ applyPypiClassifierMetadata,
91
+ applyPypiModuleNames,
92
+ } from "./pypiClassifiers.js";
83
93
  import {
84
94
  collectCargoRegistryProvenanceProperties,
85
95
  collectNpmRegistryProvenanceProperties,
@@ -810,11 +820,13 @@ export async function fetchPomXml({ urlPrefix, group, name, version }) {
810
820
  // A direct POM may have been prefetched by getMvnMetadata's batch round.
811
821
  // Parent POMs (fetched from inside fetchPomXmlAsJson) are not prefetched
812
822
  // because their URL depends on the direct POM's content, so they fall
813
- // through to the serial request unchanged.
823
+ // through to the serial request. That fallback still reaches Maven Central
824
+ // and still counts against the same budget as the batch round, so it queues
825
+ // behind the same per-host gate rather than a second, ungoverned one.
814
826
  try {
815
827
  const res =
816
828
  prefetchedResponse(prefetchedPoms, fullUrl) ||
817
- (await cdxgenAgent.get(fullUrl));
829
+ (await withHostRateLimit(fullUrl, () => cdxgenAgent.get(fullUrl)));
818
830
  return res.body;
819
831
  } catch (_err) {
820
832
  return undefined;
@@ -950,6 +962,10 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
950
962
  continue;
951
963
  }
952
964
  try {
965
+ // Module names are derived from the distribution name and the curated alias
966
+ // map, so they are available without a registry round trip and are attached
967
+ // before the skip below rather than after it.
968
+ applyPypiModuleNames(p);
953
969
  // If the package name has a url or already includes license and version skip it
954
970
  if (p.name.includes("https") || (p.license && p.version)) {
955
971
  cdepList.push(p);
@@ -1011,6 +1027,7 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
1011
1027
  }
1012
1028
  }
1013
1029
  }
1030
+ applyPypiClassifierMetadata(p, body.info.classifiers);
1014
1031
  }
1015
1032
  if (body.info.license) {
1016
1033
  const licenseId = findLicenseId(body.info.license);
@@ -1783,24 +1800,61 @@ function getGoPkgFullName(group, name) {
1783
1800
  /**
1784
1801
  * Method to retrieve metadata for rust packages by querying crates
1785
1802
  *
1803
+ * The local Cargo registry is consulted first, because crates.io's crawler
1804
+ * policy allows one request per second and a populated `~/.cargo` already
1805
+ * holds the license, description, repository, checksum and yanked flag for
1806
+ * every crate the build compiled.
1807
+ *
1808
+ * What it does not hold is publisher identity, so a caller that needs the
1809
+ * publisher-drift and release-cadence signals — the predictive audit does —
1810
+ * passes `preferLocalCache: false` and takes the slower registry path.
1811
+ *
1786
1812
  * @param {Array} pkgList Package list
1813
+ * @param {Object} [options] Options
1814
+ * @param {boolean} [options.preferLocalCache=true] Answer from the local Cargo
1815
+ * registry where it can, and query crates.io only for the rest.
1787
1816
  */
1788
- export async function getCratesMetadata(pkgList) {
1817
+ export async function getCratesMetadata(pkgList, options = {}) {
1789
1818
  const CRATES_URL =
1790
1819
  readEnvironmentVariable("RUST_CRATES_URL") ||
1791
1820
  "https://crates.io/api/v1/crates/";
1792
1821
  const cdepList = [];
1822
+ // The local Cargo registry answers first. crates.io allows one request per
1823
+ // second, so a populated ~/.cargo turns minutes of waiting into a few file
1824
+ // reads; only the crates it cannot account for reach the network.
1825
+ const servedLocally = new Set();
1826
+ const preferLocalCache = options.preferLocalCache !== false;
1827
+ if (preferLocalCache && localCargoMetadataEnabled()) {
1828
+ for (const p of pkgList) {
1829
+ if (!p?.name || !p?.version || p.version === "workspace") {
1830
+ continue;
1831
+ }
1832
+ if (applyCargoCacheMetadata(p)) {
1833
+ servedLocally.add(p);
1834
+ }
1835
+ }
1836
+ if (DEBUG_MODE && servedLocally.size) {
1837
+ console.log(
1838
+ `Resolved ${servedLocally.size} of ${pkgList.length} crate(s) from the local Cargo registry.`,
1839
+ );
1840
+ }
1841
+ }
1842
+ const needsRegistry = pkgList.filter(
1843
+ (p) =>
1844
+ p?.name &&
1845
+ p?.version &&
1846
+ p.version !== "workspace" &&
1847
+ !servedLocally.has(p),
1848
+ );
1793
1849
  // Two URLs per crate — the crate document and its owners — fetched for the
1794
1850
  // whole list at once. The `workspace` guard below is mirrored here so the
1795
1851
  // batch does not request anything the loop would have skipped.
1796
1852
  const prefetched = await prefetchJson(
1797
1853
  prefetchEnabled()
1798
- ? pkgList
1799
- .filter((p) => p?.name && p?.version && p.version !== "workspace")
1800
- .flatMap((p) => [
1801
- { url: CRATES_URL + p.name },
1802
- { url: `${CRATES_URL + p.name}/owners` },
1803
- ])
1854
+ ? needsRegistry.flatMap((p) => [
1855
+ { url: CRATES_URL + p.name },
1856
+ { url: `${CRATES_URL + p.name}/owners` },
1857
+ ])
1804
1858
  : [],
1805
1859
  );
1806
1860
  for (const p of pkgList) {
@@ -1809,23 +1863,35 @@ export async function getCratesMetadata(pkgList) {
1809
1863
  cdepList.push(p);
1810
1864
  continue;
1811
1865
  }
1866
+ if (servedLocally.has(p)) {
1867
+ cdepList.push(p);
1868
+ continue;
1869
+ }
1812
1870
  if (DEBUG_MODE) {
1813
1871
  console.log(`Querying crates.io for ${p.name}@${p.version}`);
1814
1872
  }
1815
1873
  const crateUrl = CRATES_URL + p.name;
1874
+ // A prefetch miss falls back to a direct request, which must queue
1875
+ // behind the same crates.io gate the batch pool uses: the registry
1876
+ // allows one request per second and does not care which code path in
1877
+ // this process issued it.
1816
1878
  const res =
1817
1879
  prefetchedResponse(prefetched, crateUrl) ||
1818
- (await cdxgenAgent.get(crateUrl, {
1819
- responseType: "json",
1820
- }));
1880
+ (await withHostRateLimit(crateUrl, () =>
1881
+ cdxgenAgent.get(crateUrl, {
1882
+ responseType: "json",
1883
+ }),
1884
+ ));
1821
1885
  let ownersRes;
1822
1886
  try {
1823
1887
  const ownersUrl = `${crateUrl}/owners`;
1824
1888
  ownersRes =
1825
1889
  prefetchedResponse(prefetched, ownersUrl) ||
1826
- (await cdxgenAgent.get(ownersUrl, {
1827
- responseType: "json",
1828
- }));
1890
+ (await withHostRateLimit(ownersUrl, () =>
1891
+ cdxgenAgent.get(ownersUrl, {
1892
+ responseType: "json",
1893
+ }),
1894
+ ));
1829
1895
  } catch (_err) {
1830
1896
  ownersRes = undefined;
1831
1897
  }
@@ -1895,6 +1961,86 @@ export async function getCratesMetadata(pkgList) {
1895
1961
  return cdepList;
1896
1962
  }
1897
1963
 
1964
+ /**
1965
+ * Apply the local Cargo registry's view of a crate to its component.
1966
+ *
1967
+ * Returns whether the component is complete enough to skip crates.io. The bar
1968
+ * is the license: `getCratesMetadata` runs only under `FETCH_LICENSE`, so a
1969
+ * crate whose license the local registry cannot name has not answered the
1970
+ * question that was asked and still needs the network. That happens when the
1971
+ * index knows a version but Cargo never extracted its sources — a crate the
1972
+ * lockfile pins but the build never compiled.
1973
+ *
1974
+ * Publisher identity has no local equivalent, so a component served from here
1975
+ * carries the publish time and yanked flag but none of the publisher-drift
1976
+ * properties; `cdx:cargo:metadataSource` records which it was.
1977
+ *
1978
+ * @param {Object} p Package component to enrich in place.
1979
+ * @returns {boolean} true when crates.io can be skipped for this crate.
1980
+ */
1981
+ function applyCargoCacheMetadata(p) {
1982
+ const local = readCargoCacheMetadata(p.name, p.version);
1983
+ if (!local?.license) {
1984
+ return false;
1985
+ }
1986
+ p.license = local.license;
1987
+ if (local.description) {
1988
+ p.description = local.description;
1989
+ }
1990
+ if (local.repository) {
1991
+ p.repository = { url: local.repository };
1992
+ }
1993
+ if (local.homepage && local.homepage !== local.repository) {
1994
+ p.homepage = { url: local.homepage };
1995
+ }
1996
+ if (!p._integrity && local.checksum) {
1997
+ p._integrity = normalizeCargoIntegrity(local.checksum);
1998
+ }
1999
+ if (!p.properties) {
2000
+ p.properties = [];
2001
+ }
2002
+ p.properties.push({
2003
+ name: "cdx:cargo:metadataSource",
2004
+ value: "local-registry",
2005
+ });
2006
+ if (local.rustVersion) {
2007
+ p.properties.push({
2008
+ name: "cdx:cargo:rust_version",
2009
+ value: local.rustVersion,
2010
+ });
2011
+ }
2012
+ if (local.latestVersion) {
2013
+ p.properties.push({
2014
+ name: "cdx:cargo:latest_version",
2015
+ value: local.latestVersion,
2016
+ });
2017
+ }
2018
+ if (typeof local.yanked === "boolean") {
2019
+ p.properties.push({
2020
+ name: "cdx:cargo:yanked",
2021
+ value: String(local.yanked),
2022
+ });
2023
+ }
2024
+ if (local.publishTime) {
2025
+ p.properties.push({
2026
+ name: "cdx:cargo:publishTime",
2027
+ value: local.publishTime,
2028
+ });
2029
+ }
2030
+ if (local.features && Object.keys(local.features).length) {
2031
+ p.properties.push({
2032
+ name: "cdx:cargo:features",
2033
+ value: JSON.stringify(local.features),
2034
+ });
2035
+ }
2036
+ // The download path is a documented, stable URL shape rather than something
2037
+ // the API had to tell us, so it costs no request to state it.
2038
+ p.distribution = {
2039
+ url: `https://crates.io/api/v1/crates/${p.name}/${p.version}/download`,
2040
+ };
2041
+ return true;
2042
+ }
2043
+
1898
2044
  /**
1899
2045
  * Method to retrieve metadata for dart packages by querying pub.dev
1900
2046
  *
@@ -2159,17 +2305,23 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
2159
2305
  try {
2160
2306
  const sha = await checksumFile("sha1", jf);
2161
2307
  const searchurl = `https://central.sonatype.com/solrsearch/select?q=1:%22${sha}%22&rows=20&wt=json`;
2162
- const res = await cdxgenAgent.get(searchurl, {
2163
- responseType: "json",
2164
- timeout: {
2165
- lookup: 1000,
2166
- connect: 5000,
2167
- secureConnect: 5000,
2168
- socket: 1000,
2169
- send: 10000,
2170
- response: 1000,
2171
- },
2172
- });
2308
+ // One Solr query per unidentified jar, against Central's origin
2309
+ // rather than its CDN. A directory of jars issues these back to
2310
+ // back, so they go through the same per-host gate the batch pool
2311
+ // uses (500 ms, serial) instead of a second, ungoverned path.
2312
+ const res = await withHostRateLimit(searchurl, () =>
2313
+ cdxgenAgent.get(searchurl, {
2314
+ responseType: "json",
2315
+ timeout: {
2316
+ lookup: 1000,
2317
+ connect: 5000,
2318
+ secureConnect: 5000,
2319
+ socket: 1000,
2320
+ send: 10000,
2321
+ response: 1000,
2322
+ },
2323
+ }),
2324
+ );
2173
2325
  const data = res?.body ? res.body["response"] : undefined;
2174
2326
  if (data && data["numFound"] === 1) {
2175
2327
  const jarInfo = data["docs"][0];
@@ -2471,6 +2623,12 @@ async function unresolvedJarComponents(unresolvedJars, jarFile, tempDir) {
2471
2623
  return components;
2472
2624
  }
2473
2625
 
2626
+ /**
2627
+ * Property recording the NuGet release a versionless component's metadata was
2628
+ * read from, so the description and licence in the output can be traced.
2629
+ */
2630
+ const METADATA_VERSION_PROP = "cdx:nuget:metadata_version";
2631
+
2474
2632
  async function getNugetUrl() {
2475
2633
  const req = "https://api.nuget.org/v3/index.json";
2476
2634
  const res = await cdxgenAgent.get(req, {
@@ -2546,60 +2704,46 @@ async function queryNuget(p, NUGET_URL) {
2546
2704
  if (!items?.[0]) {
2547
2705
  return [np, newBody, body];
2548
2706
  }
2549
- if (items[0] && !items[0].items) {
2550
- if (!p.version || p.version === "0.0.0" || p.version === "latest") {
2551
- const upper = items[items.length - 1].upper;
2552
- np.version = setLatestVersion(upper);
2553
- }
2707
+ // A component that states no version at all is not the same as one carrying a
2708
+ // `latest` or `0.0.0` placeholder. The placeholder asks to be replaced, so the
2709
+ // registry's newest release becomes the component's version. A missing version
2710
+ // means the manifest never said which release is in use - a `<Reference>` to an
2711
+ // assembly is the usual source - and the newest release is then only a key for
2712
+ // looking metadata up, never a version to write back. See #4359.
2713
+ const versionStated = p.version && !["0.0.0", "latest"].includes(p.version);
2714
+ let lookupVersion = p.version;
2715
+ if (!versionStated) {
2716
+ lookupVersion = setLatestVersion(items[items.length - 1].upper);
2717
+ np.version = p.version ? lookupVersion : undefined;
2718
+ np.metadataVersion = lookupVersion;
2719
+ }
2720
+ if (lookupVersion) {
2554
2721
  for (const item of items) {
2555
- if (np.version) {
2556
- const lower = compare(
2557
- coerce(item.lower, { loose: true }),
2558
- coerce(np.version, { loose: true }),
2559
- );
2560
- const upper = compare(
2561
- coerce(item.upper, { loose: true }),
2562
- coerce(np.version, { loose: true }),
2563
- );
2564
- if (lower !== 1 && upper !== -1) {
2565
- res = await cdxgenAgent.get(item["@id"], { responseType: "json" });
2566
- for (const i of res.body.items.reverse()) {
2567
- if (
2568
- i.catalogEntry &&
2569
- i.catalogEntry.version === coerceUp(np.version)
2570
- ) {
2571
- newBody.push(i);
2572
- return [np, newBody];
2573
- }
2574
- }
2575
- }
2722
+ const lower = compare(
2723
+ coerce(item.lower, { loose: true }),
2724
+ coerce(lookupVersion, { loose: true }),
2725
+ );
2726
+ const upper = compare(
2727
+ coerce(item.upper, { loose: true }),
2728
+ coerce(lookupVersion, { loose: true }),
2729
+ );
2730
+ if (lower === 1 || upper === -1) {
2731
+ continue;
2576
2732
  }
2577
- }
2578
- } else {
2579
- if (!p.version || p.version === "0.0.0" || p.version === "latest") {
2580
- const upper = items[items.length - 1].upper;
2581
- np.version = setLatestVersion(upper);
2582
- }
2583
- if (np.version) {
2584
- for (const item of items) {
2585
- const lower = compare(
2586
- coerce(item.lower, { loose: true }),
2587
- coerce(np.version, { loose: true }),
2588
- );
2589
- const upper = compare(
2590
- coerce(item.upper, { loose: true }),
2591
- coerce(np.version, { loose: true }),
2592
- );
2593
- if (lower !== 1 && upper !== -1) {
2594
- for (const i of item.items.reverse()) {
2595
- if (
2596
- i.catalogEntry &&
2597
- i.catalogEntry.version === coerceUp(np.version)
2598
- ) {
2599
- newBody.push(i);
2600
- return [np, newBody];
2601
- }
2602
- }
2733
+ // The registration index either inlines the leaves of each page or points
2734
+ // at a page that must be fetched.
2735
+ let leaves = item.items;
2736
+ if (!leaves) {
2737
+ res = await cdxgenAgent.get(item["@id"], { responseType: "json" });
2738
+ leaves = res.body.items;
2739
+ }
2740
+ for (const i of leaves.reverse()) {
2741
+ if (
2742
+ i.catalogEntry &&
2743
+ i.catalogEntry.version === coerceUp(lookupVersion)
2744
+ ) {
2745
+ newBody.push(i);
2746
+ return [np, newBody];
2603
2747
  }
2604
2748
  }
2605
2749
  }
@@ -2664,25 +2808,36 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
2664
2808
  [np, newBody] = await queryNuget(p, NUGET_URL);
2665
2809
  if (p.version !== np.version) {
2666
2810
  const oldRef = p["bom-ref"];
2667
- p["bom-ref"] = decodeURIComponent(
2668
- build({
2669
- type: "nuget",
2670
- namespace: "" || null,
2671
- name: np.name,
2672
- version: np.version || null,
2673
- }),
2674
- );
2675
- depRepList[oldRef] = p["bom-ref"];
2811
+ // The purl is rebuilt alongside the bom-ref. Leaving it behind let a
2812
+ // component state one version in `version` and another - or none - in
2813
+ // its purl, which is what a scanner range matches on. See #4359.
2676
2814
  p.version = np.version;
2815
+ applyPurl(p, nugetPurl(np.name, np.version));
2816
+ if (oldRef && p["bom-ref"] !== oldRef) {
2817
+ depRepList[oldRef] = p["bom-ref"];
2818
+ }
2677
2819
  }
2678
2820
  if (newBody && newBody.length > 0) {
2679
2821
  body = newBody[0];
2680
2822
  }
2681
2823
  if (body) {
2682
2824
  metadata_cache[cacheKey] = body;
2683
- // Set the latest version in case it is missing
2684
- if (!p.version && body.catalogEntry.version) {
2685
- p.version = body.catalogEntry.version;
2825
+ // The component states no version, so the description and licence
2826
+ // below were read from whichever release the lookup fell back to.
2827
+ // That release is recorded rather than adopted as the version: a
2828
+ // version cdxgen chose is not a version the project declared.
2829
+ if (!p.version && np.metadataVersion) {
2830
+ p.properties = p.properties || [];
2831
+ if (
2832
+ !p.properties.some(
2833
+ (prop) => prop.name === METADATA_VERSION_PROP,
2834
+ )
2835
+ ) {
2836
+ p.properties.push({
2837
+ name: METADATA_VERSION_PROP,
2838
+ value: np.metadataVersion,
2839
+ });
2840
+ }
2686
2841
  }
2687
2842
  p.description = body.catalogEntry.description;
2688
2843
  if (body.catalogEntry.authors) {
@@ -2711,8 +2866,12 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
2711
2866
  }
2712
2867
  if (body.catalogEntry.projectUrl) {
2713
2868
  p.repository = { url: body.catalogEntry.projectUrl };
2869
+ // A versionless component gets the package's landing page. Pasting
2870
+ // an undefined version into the path produced a 404 link.
2714
2871
  p.homepage = {
2715
- url: `https://www.nuget.org/packages/${p.name}/${p.version}/`,
2872
+ url: p.version
2873
+ ? `https://www.nuget.org/packages/${p.name}/${p.version}/`
2874
+ : `https://www.nuget.org/packages/${p.name}/`,
2716
2875
  };
2717
2876
  if (
2718
2877
  (!p.license || typeof p.license === "string") &&
@@ -1360,11 +1360,21 @@ function normalizedGolemDataFlowCallGraphMode(options = {}) {
1360
1360
  return callgraphMode;
1361
1361
  }
1362
1362
 
1363
+ function availableCpuCount() {
1364
+ try {
1365
+ return cpus()?.length || 1;
1366
+ } catch {
1367
+ // Deno denies the probe without --allow-sys=cpus; a worker-count hint
1368
+ // must not abort the analysis, so fall back to a single worker.
1369
+ return 1;
1370
+ }
1371
+ }
1372
+
1363
1373
  function appendGolemDataFlowArgs(args, dataFlowMode, options = {}) {
1364
1374
  if (dataFlowMode === "none") {
1365
1375
  return;
1366
1376
  }
1367
- const cpuCount = cpus()?.length || 1;
1377
+ const cpuCount = availableCpuCount();
1368
1378
  const performanceWorkerCount = Math.max(1, Math.min(cpuCount, 4));
1369
1379
  const dataFlowCallgraph = normalizedGolemDataFlowCallGraphMode(options);
1370
1380
  const dataFlowPacks =