@cdxgen/cdxgen 13.1.0 → 13.3.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 (283) hide show
  1. package/README.md +32 -15
  2. package/bin/cdxgen.js +243 -122
  3. package/bin/evinse.js +7 -0
  4. package/bin/sign.js +88 -14
  5. package/bin/tracebom.js +64 -4
  6. package/bin/validate.js +29 -7
  7. package/bin/verify.js +79 -39
  8. package/data/crypto-oid.json +88 -12
  9. package/data/remediations.json +47 -0
  10. package/data/rules/ai-bom.yaml +78 -0
  11. package/data/rules/cargo-rust.yaml +132 -0
  12. package/data/rules/mcp-servers.yaml +122 -0
  13. package/lib/audit/targets.js +6 -1
  14. package/lib/cli/bomAssembly.js +117 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +235 -5
  17. package/lib/cli/jsBom.js +108 -26
  18. package/lib/cli/jvmBom.js +68 -12
  19. package/lib/cli/managedBom.js +264 -41
  20. package/lib/cli/nativeBom.js +923 -15
  21. package/lib/core/activity.js +120 -2
  22. package/lib/core/env.js +44 -7
  23. package/lib/core/fs.js +174 -6
  24. package/lib/core/metadataProperties.js +97 -0
  25. package/lib/core/projectConfig.js +31 -4
  26. package/lib/core/userAgent.js +151 -0
  27. package/lib/ecosystems/bunutils.js +269 -101
  28. package/lib/ecosystems/cargoCache.js +364 -0
  29. package/lib/ecosystems/caxa.js +447 -7
  30. package/lib/ecosystems/cmakeResolver.js +66 -0
  31. package/lib/ecosystems/cppEvidence.js +104 -33
  32. package/lib/ecosystems/ecosystems.js +260 -99
  33. package/lib/ecosystems/golem.js +11 -1
  34. package/lib/ecosystems/gradleutils.js +185 -9
  35. package/lib/ecosystems/jsEvidence.js +19 -20
  36. package/lib/ecosystems/kosi.js +1263 -0
  37. package/lib/ecosystems/npmutils.js +110 -9
  38. package/lib/ecosystems/parsers-crystal.js +179 -0
  39. package/lib/ecosystems/parsers-dotnet.js +120 -49
  40. package/lib/ecosystems/parsers-elm.js +816 -0
  41. package/lib/ecosystems/parsers-gleam.js +1 -1
  42. package/lib/ecosystems/parsers-go.js +47 -9
  43. package/lib/ecosystems/parsers-js.js +171 -50
  44. package/lib/ecosystems/parsers-julia.js +268 -0
  45. package/lib/ecosystems/parsers-lua.js +218 -0
  46. package/lib/ecosystems/parsers-misc.js +919 -161
  47. package/lib/ecosystems/parsers-nim.js +221 -0
  48. package/lib/ecosystems/parsers-opam.js +180 -0
  49. package/lib/ecosystems/parsers-perl.js +277 -0
  50. package/lib/ecosystems/parsers-python.js +27 -15
  51. package/lib/ecosystems/parsers-r.js +148 -0
  52. package/lib/ecosystems/parsers-rebar.js +411 -0
  53. package/lib/ecosystems/parsers-rust.js +954 -33
  54. package/lib/ecosystems/parsers-spack.js +204 -0
  55. package/lib/ecosystems/parsers-stack.js +264 -0
  56. package/lib/ecosystems/parsers-terraform.js +194 -0
  57. package/lib/ecosystems/rubyutils.js +1426 -166
  58. package/lib/ecosystems/rusi.js +158 -7
  59. package/lib/evinser/evinser.js +547 -129
  60. package/lib/evinser/swiftsem.js +1914 -302
  61. package/lib/helpers/agenticToolutils.js +925 -0
  62. package/lib/helpers/bomSigner.js +652 -221
  63. package/lib/inventory/agentFormulationParser.js +57 -4
  64. package/lib/inventory/aiCollector.js +504 -10
  65. package/lib/inventory/aiInventory.js +12 -0
  66. package/lib/inventory/analyzer.js +488 -30
  67. package/lib/inventory/astWorker.js +6 -0
  68. package/lib/inventory/atomUtils.js +568 -31
  69. package/lib/inventory/auditCategories.js +8 -0
  70. package/lib/inventory/cbomutils.js +30 -0
  71. package/lib/inventory/cdxrs.js +110 -14
  72. package/lib/inventory/communityAiConfigParser.js +215 -2
  73. package/lib/inventory/depsUtils.js +183 -11
  74. package/lib/inventory/display.js +4 -5
  75. package/lib/inventory/dosai.js +627 -13
  76. package/lib/inventory/dynamic.js +39 -1
  77. package/lib/inventory/envcontext.js +9 -6
  78. package/lib/inventory/evidenceUtils.js +26 -5
  79. package/lib/inventory/fetchBatch.js +190 -19
  80. package/lib/inventory/fetchRate.js +74 -11
  81. package/lib/inventory/forgeEnricher.js +3 -2
  82. package/lib/inventory/jvmToolEnv.js +6 -5
  83. package/lib/inventory/mcpConfigParser.js +166 -2
  84. package/lib/inventory/mcpDiscovery.js +11 -0
  85. package/lib/inventory/mcpServerManifest.js +389 -0
  86. package/lib/inventory/mcpbBundle.js +419 -0
  87. package/lib/inventory/plugins.js +3 -0
  88. package/lib/inventory/protobom.js +62 -89
  89. package/lib/inventory/protobomLoader.js +4 -0
  90. package/lib/inventory/purl.js +152 -3
  91. package/lib/inventory/rustFormulationParser.js +1 -1
  92. package/lib/inventory/sbomDocument.js +2 -70
  93. package/lib/inventory/source.js +47 -23
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/traceRunner.js +210 -21
  97. package/lib/managers/binary.js +115 -24
  98. package/lib/managers/docker.js +440 -20
  99. package/lib/managers/oci.js +39 -1
  100. package/lib/parsers/cmakeLists.js +452 -0
  101. package/lib/parsers/huggingfaceUtils.js +15 -7
  102. package/lib/parsers/largeJson.js +454 -0
  103. package/lib/parsers/npmrc.js +19 -1
  104. package/lib/parsers/onnx.js +294 -0
  105. package/lib/parsers/safetensors.js +114 -0
  106. package/lib/stages/postgen/introspection/markers.js +29 -6
  107. package/lib/stages/postgen/postgen.js +237 -16
  108. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  109. package/lib/validator/bomValidator.js +6 -16
  110. package/lib/validator/index.js +48 -10
  111. package/lib/validator/reporters/console.js +3 -0
  112. package/lib/validator/reporters/json.js +1 -0
  113. package/lib/validator/reporters/sarif.js +7 -0
  114. package/package.json +27 -27
  115. package/types/lib/audit/targets.d.ts.map +1 -1
  116. package/types/lib/cli/bomAssembly.d.ts +2 -1
  117. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  118. package/types/lib/cli/cliOptions.d.ts +33 -1
  119. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  120. package/types/lib/cli/index.d.ts +2 -2
  121. package/types/lib/cli/index.d.ts.map +1 -1
  122. package/types/lib/cli/jsBom.d.ts +23 -0
  123. package/types/lib/cli/jsBom.d.ts.map +1 -1
  124. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  125. package/types/lib/cli/managedBom.d.ts +33 -0
  126. package/types/lib/cli/managedBom.d.ts.map +1 -1
  127. package/types/lib/cli/nativeBom.d.ts +88 -8
  128. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  129. package/types/lib/core/activity.d.ts.map +1 -1
  130. package/types/lib/core/env.d.ts +21 -0
  131. package/types/lib/core/env.d.ts.map +1 -1
  132. package/types/lib/core/fs.d.ts +55 -0
  133. package/types/lib/core/fs.d.ts.map +1 -1
  134. package/types/lib/core/metadataProperties.d.ts +39 -0
  135. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  136. package/types/lib/core/projectConfig.d.ts +6 -2
  137. package/types/lib/core/projectConfig.d.ts.map +1 -1
  138. package/types/lib/core/userAgent.d.ts +56 -0
  139. package/types/lib/core/userAgent.d.ts.map +1 -0
  140. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  141. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  143. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  144. package/types/lib/ecosystems/caxa.d.ts +14 -0
  145. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  146. package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
  147. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  148. package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
  149. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  150. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  151. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  152. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  153. package/types/lib/ecosystems/gradleutils.d.ts +40 -4
  154. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  155. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  156. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  157. package/types/lib/ecosystems/kosi.d.ts +86 -0
  158. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  159. package/types/lib/ecosystems/npmutils.d.ts +42 -0
  160. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  161. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  162. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  163. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  164. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  165. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  166. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  167. package/types/lib/ecosystems/parsers-go.d.ts +10 -0
  168. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  169. package/types/lib/ecosystems/parsers-js.d.ts +10 -4
  170. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  171. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  172. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  173. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  174. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
  176. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  177. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  178. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  179. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  180. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  181. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  182. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  183. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  184. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  185. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  187. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  188. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  189. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  191. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  192. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  193. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  194. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  195. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  196. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  197. package/types/lib/ecosystems/rubyutils.d.ts +143 -2
  198. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  199. package/types/lib/ecosystems/rusi.d.ts +13 -0
  200. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  201. package/types/lib/evinser/evinser.d.ts +81 -2
  202. package/types/lib/evinser/evinser.d.ts.map +1 -1
  203. package/types/lib/evinser/swiftsem.d.ts +280 -15
  204. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  205. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  206. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  207. package/types/lib/helpers/bomSigner.d.ts +106 -13
  208. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  209. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  210. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  211. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  212. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  213. package/types/lib/inventory/atomUtils.d.ts +145 -3
  214. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +4 -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/cdxrs.d.ts +8 -3
  219. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  220. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  221. package/types/lib/inventory/depsUtils.d.ts +15 -0
  222. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/display.d.ts.map +1 -1
  224. package/types/lib/inventory/dosai.d.ts +87 -1
  225. package/types/lib/inventory/dosai.d.ts.map +1 -1
  226. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  227. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  228. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  229. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  230. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  231. package/types/lib/inventory/fetchRate.d.ts +9 -6
  232. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  233. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  234. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  235. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  236. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  237. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  238. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  239. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  240. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  241. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  242. package/types/lib/inventory/plugins.d.ts.map +1 -1
  243. package/types/lib/inventory/protobom.d.ts +18 -13
  244. package/types/lib/inventory/protobom.d.ts.map +1 -1
  245. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  246. package/types/lib/inventory/purl.d.ts +71 -2
  247. package/types/lib/inventory/purl.d.ts.map +1 -1
  248. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  249. package/types/lib/inventory/source.d.ts.map +1 -1
  250. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  251. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  252. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  253. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  254. package/types/lib/inventory/traceRunner.d.ts +47 -1
  255. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  256. package/types/lib/managers/binary.d.ts +30 -1
  257. package/types/lib/managers/binary.d.ts.map +1 -1
  258. package/types/lib/managers/docker.d.ts +29 -0
  259. package/types/lib/managers/docker.d.ts.map +1 -1
  260. package/types/lib/managers/oci.d.ts +14 -0
  261. package/types/lib/managers/oci.d.ts.map +1 -1
  262. package/types/lib/parsers/cmakeLists.d.ts +76 -0
  263. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  264. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  265. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  266. package/types/lib/parsers/largeJson.d.ts +52 -0
  267. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  268. package/types/lib/parsers/npmrc.d.ts +5 -0
  269. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  270. package/types/lib/parsers/onnx.d.ts +28 -0
  271. package/types/lib/parsers/onnx.d.ts.map +1 -0
  272. package/types/lib/parsers/safetensors.d.ts +22 -0
  273. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  275. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  276. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  277. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  278. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  279. package/types/lib/validator/index.d.ts +10 -1
  280. package/types/lib/validator/index.d.ts.map +1 -1
  281. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  282. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  283. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
@@ -66,11 +66,12 @@ 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
- encodeForPurl,
73
73
  genericPurl,
74
+ nugetPurl,
74
75
  pypiPurl,
75
76
  } from "../inventory/purl.js";
76
77
  import {
@@ -80,6 +81,10 @@ import {
80
81
  } from "../inventory/spdx.js";
81
82
  import { extractLicenseText, extractRepoUrl } from "../parsers/htmlExtract.js";
82
83
  import { xml2js } from "../parsers/xml.js";
84
+ import {
85
+ localCargoMetadataEnabled,
86
+ readCargoCacheMetadata,
87
+ } from "./cargoCache.js";
83
88
  import {
84
89
  applyPypiClassifierMetadata,
85
90
  applyPypiModuleNames,
@@ -814,11 +819,13 @@ export async function fetchPomXml({ urlPrefix, group, name, version }) {
814
819
  // A direct POM may have been prefetched by getMvnMetadata's batch round.
815
820
  // Parent POMs (fetched from inside fetchPomXmlAsJson) are not prefetched
816
821
  // because their URL depends on the direct POM's content, so they fall
817
- // through to the serial request unchanged.
822
+ // through to the serial request. That fallback still reaches Maven Central
823
+ // and still counts against the same budget as the batch round, so it queues
824
+ // behind the same per-host gate rather than a second, ungoverned one.
818
825
  try {
819
826
  const res =
820
827
  prefetchedResponse(prefetchedPoms, fullUrl) ||
821
- (await cdxgenAgent.get(fullUrl));
828
+ (await withHostRateLimit(fullUrl, () => cdxgenAgent.get(fullUrl)));
822
829
  return res.body;
823
830
  } catch (_err) {
824
831
  return undefined;
@@ -1792,24 +1799,61 @@ function getGoPkgFullName(group, name) {
1792
1799
  /**
1793
1800
  * Method to retrieve metadata for rust packages by querying crates
1794
1801
  *
1802
+ * The local Cargo registry is consulted first, because crates.io's crawler
1803
+ * policy allows one request per second and a populated `~/.cargo` already
1804
+ * holds the license, description, repository, checksum and yanked flag for
1805
+ * every crate the build compiled.
1806
+ *
1807
+ * What it does not hold is publisher identity, so a caller that needs the
1808
+ * publisher-drift and release-cadence signals — the predictive audit does —
1809
+ * passes `preferLocalCache: false` and takes the slower registry path.
1810
+ *
1795
1811
  * @param {Array} pkgList Package list
1812
+ * @param {Object} [options] Options
1813
+ * @param {boolean} [options.preferLocalCache=true] Answer from the local Cargo
1814
+ * registry where it can, and query crates.io only for the rest.
1796
1815
  */
1797
- export async function getCratesMetadata(pkgList) {
1816
+ export async function getCratesMetadata(pkgList, options = {}) {
1798
1817
  const CRATES_URL =
1799
1818
  readEnvironmentVariable("RUST_CRATES_URL") ||
1800
1819
  "https://crates.io/api/v1/crates/";
1801
1820
  const cdepList = [];
1821
+ // The local Cargo registry answers first. crates.io allows one request per
1822
+ // second, so a populated ~/.cargo turns minutes of waiting into a few file
1823
+ // reads; only the crates it cannot account for reach the network.
1824
+ const servedLocally = new Set();
1825
+ const preferLocalCache = options.preferLocalCache !== false;
1826
+ if (preferLocalCache && localCargoMetadataEnabled()) {
1827
+ for (const p of pkgList) {
1828
+ if (!p?.name || !p?.version || p.version === "workspace") {
1829
+ continue;
1830
+ }
1831
+ if (applyCargoCacheMetadata(p)) {
1832
+ servedLocally.add(p);
1833
+ }
1834
+ }
1835
+ if (DEBUG_MODE && servedLocally.size) {
1836
+ console.log(
1837
+ `Resolved ${servedLocally.size} of ${pkgList.length} crate(s) from the local Cargo registry.`,
1838
+ );
1839
+ }
1840
+ }
1841
+ const needsRegistry = pkgList.filter(
1842
+ (p) =>
1843
+ p?.name &&
1844
+ p?.version &&
1845
+ p.version !== "workspace" &&
1846
+ !servedLocally.has(p),
1847
+ );
1802
1848
  // Two URLs per crate — the crate document and its owners — fetched for the
1803
1849
  // whole list at once. The `workspace` guard below is mirrored here so the
1804
1850
  // batch does not request anything the loop would have skipped.
1805
1851
  const prefetched = await prefetchJson(
1806
1852
  prefetchEnabled()
1807
- ? pkgList
1808
- .filter((p) => p?.name && p?.version && p.version !== "workspace")
1809
- .flatMap((p) => [
1810
- { url: CRATES_URL + p.name },
1811
- { url: `${CRATES_URL + p.name}/owners` },
1812
- ])
1853
+ ? needsRegistry.flatMap((p) => [
1854
+ { url: CRATES_URL + p.name },
1855
+ { url: `${CRATES_URL + p.name}/owners` },
1856
+ ])
1813
1857
  : [],
1814
1858
  );
1815
1859
  for (const p of pkgList) {
@@ -1818,23 +1862,35 @@ export async function getCratesMetadata(pkgList) {
1818
1862
  cdepList.push(p);
1819
1863
  continue;
1820
1864
  }
1865
+ if (servedLocally.has(p)) {
1866
+ cdepList.push(p);
1867
+ continue;
1868
+ }
1821
1869
  if (DEBUG_MODE) {
1822
1870
  console.log(`Querying crates.io for ${p.name}@${p.version}`);
1823
1871
  }
1824
1872
  const crateUrl = CRATES_URL + p.name;
1873
+ // A prefetch miss falls back to a direct request, which must queue
1874
+ // behind the same crates.io gate the batch pool uses: the registry
1875
+ // allows one request per second and does not care which code path in
1876
+ // this process issued it.
1825
1877
  const res =
1826
1878
  prefetchedResponse(prefetched, crateUrl) ||
1827
- (await cdxgenAgent.get(crateUrl, {
1828
- responseType: "json",
1829
- }));
1879
+ (await withHostRateLimit(crateUrl, () =>
1880
+ cdxgenAgent.get(crateUrl, {
1881
+ responseType: "json",
1882
+ }),
1883
+ ));
1830
1884
  let ownersRes;
1831
1885
  try {
1832
1886
  const ownersUrl = `${crateUrl}/owners`;
1833
1887
  ownersRes =
1834
1888
  prefetchedResponse(prefetched, ownersUrl) ||
1835
- (await cdxgenAgent.get(ownersUrl, {
1836
- responseType: "json",
1837
- }));
1889
+ (await withHostRateLimit(ownersUrl, () =>
1890
+ cdxgenAgent.get(ownersUrl, {
1891
+ responseType: "json",
1892
+ }),
1893
+ ));
1838
1894
  } catch (_err) {
1839
1895
  ownersRes = undefined;
1840
1896
  }
@@ -1904,6 +1960,86 @@ export async function getCratesMetadata(pkgList) {
1904
1960
  return cdepList;
1905
1961
  }
1906
1962
 
1963
+ /**
1964
+ * Apply the local Cargo registry's view of a crate to its component.
1965
+ *
1966
+ * Returns whether the component is complete enough to skip crates.io. The bar
1967
+ * is the license: `getCratesMetadata` runs only under `FETCH_LICENSE`, so a
1968
+ * crate whose license the local registry cannot name has not answered the
1969
+ * question that was asked and still needs the network. That happens when the
1970
+ * index knows a version but Cargo never extracted its sources — a crate the
1971
+ * lockfile pins but the build never compiled.
1972
+ *
1973
+ * Publisher identity has no local equivalent, so a component served from here
1974
+ * carries the publish time and yanked flag but none of the publisher-drift
1975
+ * properties; `cdx:cargo:metadataSource` records which it was.
1976
+ *
1977
+ * @param {Object} p Package component to enrich in place.
1978
+ * @returns {boolean} true when crates.io can be skipped for this crate.
1979
+ */
1980
+ function applyCargoCacheMetadata(p) {
1981
+ const local = readCargoCacheMetadata(p.name, p.version);
1982
+ if (!local?.license) {
1983
+ return false;
1984
+ }
1985
+ p.license = local.license;
1986
+ if (local.description) {
1987
+ p.description = local.description;
1988
+ }
1989
+ if (local.repository) {
1990
+ p.repository = { url: local.repository };
1991
+ }
1992
+ if (local.homepage && local.homepage !== local.repository) {
1993
+ p.homepage = { url: local.homepage };
1994
+ }
1995
+ if (!p._integrity && local.checksum) {
1996
+ p._integrity = normalizeCargoIntegrity(local.checksum);
1997
+ }
1998
+ if (!p.properties) {
1999
+ p.properties = [];
2000
+ }
2001
+ p.properties.push({
2002
+ name: "cdx:cargo:metadataSource",
2003
+ value: "local-registry",
2004
+ });
2005
+ if (local.rustVersion) {
2006
+ p.properties.push({
2007
+ name: "cdx:cargo:rust_version",
2008
+ value: local.rustVersion,
2009
+ });
2010
+ }
2011
+ if (local.latestVersion) {
2012
+ p.properties.push({
2013
+ name: "cdx:cargo:latest_version",
2014
+ value: local.latestVersion,
2015
+ });
2016
+ }
2017
+ if (typeof local.yanked === "boolean") {
2018
+ p.properties.push({
2019
+ name: "cdx:cargo:yanked",
2020
+ value: String(local.yanked),
2021
+ });
2022
+ }
2023
+ if (local.publishTime) {
2024
+ p.properties.push({
2025
+ name: "cdx:cargo:publishTime",
2026
+ value: local.publishTime,
2027
+ });
2028
+ }
2029
+ if (local.features && Object.keys(local.features).length) {
2030
+ p.properties.push({
2031
+ name: "cdx:cargo:features",
2032
+ value: JSON.stringify(local.features),
2033
+ });
2034
+ }
2035
+ // The download path is a documented, stable URL shape rather than something
2036
+ // the API had to tell us, so it costs no request to state it.
2037
+ p.distribution = {
2038
+ url: `https://crates.io/api/v1/crates/${p.name}/${p.version}/download`,
2039
+ };
2040
+ return true;
2041
+ }
2042
+
1907
2043
  /**
1908
2044
  * Method to retrieve metadata for dart packages by querying pub.dev
1909
2045
  *
@@ -2168,17 +2304,23 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
2168
2304
  try {
2169
2305
  const sha = await checksumFile("sha1", jf);
2170
2306
  const searchurl = `https://central.sonatype.com/solrsearch/select?q=1:%22${sha}%22&rows=20&wt=json`;
2171
- const res = await cdxgenAgent.get(searchurl, {
2172
- responseType: "json",
2173
- timeout: {
2174
- lookup: 1000,
2175
- connect: 5000,
2176
- secureConnect: 5000,
2177
- socket: 1000,
2178
- send: 10000,
2179
- response: 1000,
2180
- },
2181
- });
2307
+ // One Solr query per unidentified jar, against Central's origin
2308
+ // rather than its CDN. A directory of jars issues these back to
2309
+ // back, so they go through the same per-host gate the batch pool
2310
+ // uses (500 ms, serial) instead of a second, ungoverned path.
2311
+ const res = await withHostRateLimit(searchurl, () =>
2312
+ cdxgenAgent.get(searchurl, {
2313
+ responseType: "json",
2314
+ timeout: {
2315
+ lookup: 1000,
2316
+ connect: 5000,
2317
+ secureConnect: 5000,
2318
+ socket: 1000,
2319
+ send: 10000,
2320
+ response: 1000,
2321
+ },
2322
+ }),
2323
+ );
2182
2324
  const data = res?.body ? res.body["response"] : undefined;
2183
2325
  if (data && data["numFound"] === 1) {
2184
2326
  const jarInfo = data["docs"][0];
@@ -2357,8 +2499,8 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
2357
2499
  }
2358
2500
  }
2359
2501
  const apkg = {
2360
- group: group ? encodeForPurl(group) : "",
2361
- name: name ? encodeForPurl(name) : "",
2502
+ group: group || "",
2503
+ name: name || "",
2362
2504
  version,
2363
2505
  purl,
2364
2506
  evidence: {
@@ -2480,18 +2622,36 @@ async function unresolvedJarComponents(unresolvedJars, jarFile, tempDir) {
2480
2622
  return components;
2481
2623
  }
2482
2624
 
2625
+ /**
2626
+ * Property recording the NuGet release a versionless component's metadata was
2627
+ * read from, so the description and licence in the output can be traced.
2628
+ */
2629
+ const METADATA_VERSION_PROP = "cdx:nuget:metadata_version";
2630
+
2483
2631
  async function getNugetUrl() {
2484
2632
  const req = "https://api.nuget.org/v3/index.json";
2485
- const res = await cdxgenAgent.get(req, {
2486
- responseType: "json",
2487
- });
2488
- const urls = res.body.resources;
2633
+ const fallbackUrl = "https://api.nuget.org/v3/registration3/";
2634
+ let res;
2635
+ try {
2636
+ res = await cdxgenAgent.get(req, {
2637
+ responseType: "json",
2638
+ });
2639
+ } catch (err) {
2640
+ // Package lookups already degrade to unenriched components on error. An
2641
+ // unreachable service index used to reject here instead and abort the
2642
+ // whole BOM, so fall back to the well-known registration base.
2643
+ thoughtLog(
2644
+ `Unable to read the NuGet service index (${err.code || err.message}). Using ${fallbackUrl}.`,
2645
+ );
2646
+ return fallbackUrl;
2647
+ }
2648
+ const urls = res?.body?.resources || [];
2489
2649
  for (const resource of urls) {
2490
2650
  if (resource["@type"] === "RegistrationsBaseUrl/3.6.0") {
2491
2651
  return resource["@id"];
2492
2652
  }
2493
2653
  }
2494
- return "https://api.nuget.org/v3/registration3/";
2654
+ return fallbackUrl;
2495
2655
  }
2496
2656
 
2497
2657
  /**
@@ -2555,60 +2715,46 @@ async function queryNuget(p, NUGET_URL) {
2555
2715
  if (!items?.[0]) {
2556
2716
  return [np, newBody, body];
2557
2717
  }
2558
- if (items[0] && !items[0].items) {
2559
- if (!p.version || p.version === "0.0.0" || p.version === "latest") {
2560
- const upper = items[items.length - 1].upper;
2561
- np.version = setLatestVersion(upper);
2562
- }
2718
+ // A component that states no version at all is not the same as one carrying a
2719
+ // `latest` or `0.0.0` placeholder. The placeholder asks to be replaced, so the
2720
+ // registry's newest release becomes the component's version. A missing version
2721
+ // means the manifest never said which release is in use - a `<Reference>` to an
2722
+ // assembly is the usual source - and the newest release is then only a key for
2723
+ // looking metadata up, never a version to write back. See #4359.
2724
+ const versionStated = p.version && !["0.0.0", "latest"].includes(p.version);
2725
+ let lookupVersion = p.version;
2726
+ if (!versionStated) {
2727
+ lookupVersion = setLatestVersion(items[items.length - 1].upper);
2728
+ np.version = p.version ? lookupVersion : undefined;
2729
+ np.metadataVersion = lookupVersion;
2730
+ }
2731
+ if (lookupVersion) {
2563
2732
  for (const item of items) {
2564
- if (np.version) {
2565
- const lower = compare(
2566
- coerce(item.lower, { loose: true }),
2567
- coerce(np.version, { loose: true }),
2568
- );
2569
- const upper = compare(
2570
- coerce(item.upper, { loose: true }),
2571
- coerce(np.version, { loose: true }),
2572
- );
2573
- if (lower !== 1 && upper !== -1) {
2574
- res = await cdxgenAgent.get(item["@id"], { responseType: "json" });
2575
- for (const i of res.body.items.reverse()) {
2576
- if (
2577
- i.catalogEntry &&
2578
- i.catalogEntry.version === coerceUp(np.version)
2579
- ) {
2580
- newBody.push(i);
2581
- return [np, newBody];
2582
- }
2583
- }
2584
- }
2733
+ const lower = compare(
2734
+ coerce(item.lower, { loose: true }),
2735
+ coerce(lookupVersion, { loose: true }),
2736
+ );
2737
+ const upper = compare(
2738
+ coerce(item.upper, { loose: true }),
2739
+ coerce(lookupVersion, { loose: true }),
2740
+ );
2741
+ if (lower === 1 || upper === -1) {
2742
+ continue;
2585
2743
  }
2586
- }
2587
- } else {
2588
- if (!p.version || p.version === "0.0.0" || p.version === "latest") {
2589
- const upper = items[items.length - 1].upper;
2590
- np.version = setLatestVersion(upper);
2591
- }
2592
- if (np.version) {
2593
- for (const item of items) {
2594
- const lower = compare(
2595
- coerce(item.lower, { loose: true }),
2596
- coerce(np.version, { loose: true }),
2597
- );
2598
- const upper = compare(
2599
- coerce(item.upper, { loose: true }),
2600
- coerce(np.version, { loose: true }),
2601
- );
2602
- if (lower !== 1 && upper !== -1) {
2603
- for (const i of item.items.reverse()) {
2604
- if (
2605
- i.catalogEntry &&
2606
- i.catalogEntry.version === coerceUp(np.version)
2607
- ) {
2608
- newBody.push(i);
2609
- return [np, newBody];
2610
- }
2611
- }
2744
+ // The registration index either inlines the leaves of each page or points
2745
+ // at a page that must be fetched.
2746
+ let leaves = item.items;
2747
+ if (!leaves) {
2748
+ res = await cdxgenAgent.get(item["@id"], { responseType: "json" });
2749
+ leaves = res.body.items;
2750
+ }
2751
+ for (const i of leaves.reverse()) {
2752
+ if (
2753
+ i.catalogEntry &&
2754
+ i.catalogEntry.version === coerceUp(lookupVersion)
2755
+ ) {
2756
+ newBody.push(i);
2757
+ return [np, newBody];
2612
2758
  }
2613
2759
  }
2614
2760
  }
@@ -2673,25 +2819,36 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
2673
2819
  [np, newBody] = await queryNuget(p, NUGET_URL);
2674
2820
  if (p.version !== np.version) {
2675
2821
  const oldRef = p["bom-ref"];
2676
- p["bom-ref"] = decodeURIComponent(
2677
- build({
2678
- type: "nuget",
2679
- namespace: "" || null,
2680
- name: np.name,
2681
- version: np.version || null,
2682
- }),
2683
- );
2684
- depRepList[oldRef] = p["bom-ref"];
2822
+ // The purl is rebuilt alongside the bom-ref. Leaving it behind let a
2823
+ // component state one version in `version` and another - or none - in
2824
+ // its purl, which is what a scanner range matches on. See #4359.
2685
2825
  p.version = np.version;
2826
+ applyPurl(p, nugetPurl(np.name, np.version));
2827
+ if (oldRef && p["bom-ref"] !== oldRef) {
2828
+ depRepList[oldRef] = p["bom-ref"];
2829
+ }
2686
2830
  }
2687
2831
  if (newBody && newBody.length > 0) {
2688
2832
  body = newBody[0];
2689
2833
  }
2690
2834
  if (body) {
2691
2835
  metadata_cache[cacheKey] = body;
2692
- // Set the latest version in case it is missing
2693
- if (!p.version && body.catalogEntry.version) {
2694
- p.version = body.catalogEntry.version;
2836
+ // The component states no version, so the description and licence
2837
+ // below were read from whichever release the lookup fell back to.
2838
+ // That release is recorded rather than adopted as the version: a
2839
+ // version cdxgen chose is not a version the project declared.
2840
+ if (!p.version && np.metadataVersion) {
2841
+ p.properties = p.properties || [];
2842
+ if (
2843
+ !p.properties.some(
2844
+ (prop) => prop.name === METADATA_VERSION_PROP,
2845
+ )
2846
+ ) {
2847
+ p.properties.push({
2848
+ name: METADATA_VERSION_PROP,
2849
+ value: np.metadataVersion,
2850
+ });
2851
+ }
2695
2852
  }
2696
2853
  p.description = body.catalogEntry.description;
2697
2854
  if (body.catalogEntry.authors) {
@@ -2720,8 +2877,12 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
2720
2877
  }
2721
2878
  if (body.catalogEntry.projectUrl) {
2722
2879
  p.repository = { url: body.catalogEntry.projectUrl };
2880
+ // A versionless component gets the package's landing page. Pasting
2881
+ // an undefined version into the path produced a 404 link.
2723
2882
  p.homepage = {
2724
- url: `https://www.nuget.org/packages/${p.name}/${p.version}/`,
2883
+ url: p.version
2884
+ ? `https://www.nuget.org/packages/${p.name}/${p.version}/`
2885
+ : `https://www.nuget.org/packages/${p.name}/`,
2725
2886
  };
2726
2887
  if (
2727
2888
  (!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 =