@cdxgen/cdxgen 0.0.1 → 13.0.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 (722) hide show
  1. package/README.md +921 -87
  2. package/bin/audit.js +239 -0
  3. package/bin/cdxgen.js +2324 -0
  4. package/bin/convert.js +213 -0
  5. package/bin/evinse.js +353 -0
  6. package/bin/hbom.js +495 -0
  7. package/bin/repl.js +2224 -0
  8. package/bin/sign.js +195 -0
  9. package/bin/tracebom.js +295 -0
  10. package/bin/validate.js +268 -0
  11. package/bin/verify.js +189 -0
  12. package/data/README.md +307 -0
  13. package/data/bom-1.4.schema.json +1605 -0
  14. package/data/bom-1.5.schema.json +3660 -0
  15. package/data/bom-1.6.schema.json +5511 -0
  16. package/data/bom-1.7.schema.json +6700 -0
  17. package/data/cbomosdb-queries.json +68 -0
  18. package/data/component-tags.json +339 -0
  19. package/data/container-knowledge-index.json +125 -0
  20. package/data/cosdb-queries.json +17 -0
  21. package/data/crypto-oid.json +1998 -0
  22. package/data/cryptography-defs.json +3793 -0
  23. package/data/cryptography-defs.schema.json +576 -0
  24. package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
  25. package/data/frameworks-list.json +219 -0
  26. package/data/glibc-stdlib.json +117 -0
  27. package/data/gtfobins-index.json +6296 -0
  28. package/data/helpers/init.gradle +71 -0
  29. package/data/jsf-0.82.schema.json +211 -0
  30. package/data/known-licenses.json +82 -0
  31. package/data/lic-mapping.json +338 -0
  32. package/data/license-aliases.json +6624 -0
  33. package/data/license-db.json +39059 -0
  34. package/data/license-deprecations.json +302 -0
  35. package/data/lolbas-index.json +150 -0
  36. package/data/predictive-audit-allowlist.json +11 -0
  37. package/data/pypi-pkg-aliases.json +1199 -0
  38. package/data/python-stdlib.json +308 -0
  39. package/data/queries-darwin.json +193 -0
  40. package/data/queries-win.json +257 -0
  41. package/data/queries.json +268 -0
  42. package/data/ruby-known-modules.json +281 -0
  43. package/data/rules/ai-agent-governance.yaml +323 -0
  44. package/data/rules/ai-bom.yaml +268 -0
  45. package/data/rules/ai-oversight.yaml +186 -0
  46. package/data/rules/ai-provenance.yaml +92 -0
  47. package/data/rules/asar-archives.yaml +150 -0
  48. package/data/rules/cbom-security.yaml +165 -0
  49. package/data/rules/chrome-extensions.yaml +248 -0
  50. package/data/rules/ci-permissions.yaml +802 -0
  51. package/data/rules/container-risk.yaml +353 -0
  52. package/data/rules/dependency-sources.yaml +236 -0
  53. package/data/rules/golem-go.yaml +229 -0
  54. package/data/rules/hbom-compliance.yaml +325 -0
  55. package/data/rules/hbom-performance.yaml +307 -0
  56. package/data/rules/hbom-security.yaml +248 -0
  57. package/data/rules/host-topology.yaml +165 -0
  58. package/data/rules/mcp-servers.yaml +388 -0
  59. package/data/rules/obom-runtime.yaml +1776 -0
  60. package/data/rules/package-integrity.yaml +364 -0
  61. package/data/rules/rootfs-hardening.yaml +179 -0
  62. package/data/rules/vscode-extensions.yaml +237 -0
  63. package/data/spdx-export.schema.json +6794 -0
  64. package/data/spdx-license-list.json +7449 -0
  65. package/data/spdx-licenses.json +759 -0
  66. package/data/spdx.schema.json +819 -0
  67. package/data/templates/README.md +3 -0
  68. package/data/templates/asvs-4.0.3.cdx.json +2943 -0
  69. package/data/templates/asvs-5.0.cdx.json +3046 -0
  70. package/data/templates/bsimm-v13.cdx.json +1008 -0
  71. package/data/templates/masvs-2.0.0.cdx.json +225 -0
  72. package/data/templates/nist-ssdf-1.1.cdx.json +828 -0
  73. package/data/templates/pcissc-secure-slc-1.1.cdx.json +1027 -0
  74. package/data/templates/scvs-1.0.0.cdx.json +813 -0
  75. package/data/templates/ssaf-DRAFT-2023-11.cdx.json +127 -0
  76. package/data/vendor-alias.json +41967 -0
  77. package/data/wrapdb-releases.json +4227 -0
  78. package/index.cjs +15 -0
  79. package/lib/audit/index.js +2800 -0
  80. package/lib/audit/progress.js +114 -0
  81. package/lib/audit/reporters.js +937 -0
  82. package/lib/audit/scoring.js +560 -0
  83. package/lib/audit/targets.js +798 -0
  84. package/lib/cli/bomAssembly.js +1833 -0
  85. package/lib/cli/cliOptions.js +513 -0
  86. package/lib/cli/index.js +2759 -0
  87. package/lib/cli/jsBom.js +2087 -0
  88. package/lib/cli/jvmBom.js +1858 -0
  89. package/lib/cli/managedBom.js +2083 -0
  90. package/lib/cli/nativeBom.js +2125 -0
  91. package/lib/core/activity.js +1087 -0
  92. package/lib/core/env.js +719 -0
  93. package/lib/core/fs.js +1058 -0
  94. package/lib/core/httpClient.js +730 -0
  95. package/lib/core/logger.js +142 -0
  96. package/lib/core/parallel.js +323 -0
  97. package/lib/core/paths.js +70 -0
  98. package/lib/core/projectConfig.js +122 -0
  99. package/lib/core/propertySanitizer.js +174 -0
  100. package/lib/core/spdx.js +5 -0
  101. package/lib/core/state.js +59 -0
  102. package/lib/core/text.js +207 -0
  103. package/lib/core/ui.js +876 -0
  104. package/lib/core/urls.js +84 -0
  105. package/lib/ecosystems/asarutils.js +1636 -0
  106. package/lib/ecosystems/bunutils.js +402 -0
  107. package/lib/ecosystems/caxa.js +77 -0
  108. package/lib/ecosystems/chromextutils.js +1174 -0
  109. package/lib/ecosystems/cmakeResolver.js +587 -0
  110. package/lib/ecosystems/cppEvidence.js +305 -0
  111. package/lib/ecosystems/denoutils.js +1094 -0
  112. package/lib/ecosystems/dotnetutils.js +132 -0
  113. package/lib/ecosystems/ecosystems.js +2772 -0
  114. package/lib/ecosystems/golem.js +1807 -0
  115. package/lib/ecosystems/gradleutils.js +1048 -0
  116. package/lib/ecosystems/jsEvidence.js +317 -0
  117. package/lib/ecosystems/npmutils.js +586 -0
  118. package/lib/ecosystems/parsers-bazel.js +875 -0
  119. package/lib/ecosystems/parsers-dotnet.js +1430 -0
  120. package/lib/ecosystems/parsers-gleam.js +302 -0
  121. package/lib/ecosystems/parsers-go.js +818 -0
  122. package/lib/ecosystems/parsers-js.js +4222 -0
  123. package/lib/ecosystems/parsers-jvm.js +948 -0
  124. package/lib/ecosystems/parsers-misc.js +3191 -0
  125. package/lib/ecosystems/parsers-python.js +2070 -0
  126. package/lib/ecosystems/parsers-rust.js +1294 -0
  127. package/lib/ecosystems/parsers-zig.js +861 -0
  128. package/lib/ecosystems/pep770.js +341 -0
  129. package/lib/ecosystems/pylockutils.js +427 -0
  130. package/lib/ecosystems/pythonutils.js +1281 -0
  131. package/lib/ecosystems/registryProvenance.js +1003 -0
  132. package/lib/ecosystems/remote/huggingface.js +1343 -0
  133. package/lib/ecosystems/rubyutils.js +1629 -0
  134. package/lib/ecosystems/rusi.js +820 -0
  135. package/lib/ecosystems/sbtutils.js +972 -0
  136. package/lib/ecosystems/tea.js +607 -0
  137. package/lib/ecosystems/utils.js +575 -0
  138. package/lib/ecosystems/zigResolver.js +410 -0
  139. package/lib/evinser/db.js +145 -0
  140. package/lib/evinser/evinser.js +2362 -0
  141. package/lib/evinser/scalasem.js +65 -0
  142. package/lib/evinser/swiftsem.js +767 -0
  143. package/lib/helpers/annotationFormatter.js +84 -0
  144. package/lib/helpers/bomSigner.js +312 -0
  145. package/lib/helpers/exportUtils.js +160 -0
  146. package/lib/helpers/remote/dependency-track.js +117 -0
  147. package/lib/helpers/versutils.js +202 -0
  148. package/lib/helpers/vsixutils.js +1069 -0
  149. package/lib/inventory/agentFormulationParser.js +322 -0
  150. package/lib/inventory/aiCollector.js +2140 -0
  151. package/lib/inventory/aiInventory.js +414 -0
  152. package/lib/inventory/aiModelVariants.js +121 -0
  153. package/lib/inventory/aiOversightCollector.js +661 -0
  154. package/lib/inventory/aiProvenanceCollector.js +822 -0
  155. package/lib/inventory/analyzer.js +6089 -0
  156. package/lib/inventory/analyzerScope.js +807 -0
  157. package/lib/inventory/astWorker.js +20 -0
  158. package/lib/inventory/atomUtils.js +1092 -0
  159. package/lib/inventory/auditCategories.js +192 -0
  160. package/lib/inventory/bomUtils.js +369 -0
  161. package/lib/inventory/cacheCommands.js +182 -0
  162. package/lib/inventory/cacheDir.js +104 -0
  163. package/lib/inventory/cbomutils.js +590 -0
  164. package/lib/inventory/cdxrs.js +434 -0
  165. package/lib/inventory/ciParsers/azurePipelines.js +295 -0
  166. package/lib/inventory/ciParsers/circleCi.js +286 -0
  167. package/lib/inventory/ciParsers/common.js +24 -0
  168. package/lib/inventory/ciParsers/githubActions.js +2434 -0
  169. package/lib/inventory/ciParsers/gitlabCi.js +213 -0
  170. package/lib/inventory/ciParsers/jenkins.js +180 -0
  171. package/lib/inventory/citations.js +234 -0
  172. package/lib/inventory/communityAiConfigParser.js +756 -0
  173. package/lib/inventory/containerRisk.js +204 -0
  174. package/lib/inventory/cryptoAlgorithmFamily.js +258 -0
  175. package/lib/inventory/deps.js +807 -0
  176. package/lib/inventory/depsUtils.js +588 -0
  177. package/lib/inventory/display.js +2128 -0
  178. package/lib/inventory/dosai.js +601 -0
  179. package/lib/inventory/dosaiParsers.js +154 -0
  180. package/lib/inventory/dynamic.js +145 -0
  181. package/lib/inventory/envcontext.js +1789 -0
  182. package/lib/inventory/evidenceUtils.js +774 -0
  183. package/lib/inventory/fetchBatch.js +779 -0
  184. package/lib/inventory/fetchRate.js +255 -0
  185. package/lib/inventory/forgeEnricher.js +300 -0
  186. package/lib/inventory/formulationParsers.js +422 -0
  187. package/lib/inventory/gtfobins.js +339 -0
  188. package/lib/inventory/hbom.js +719 -0
  189. package/lib/inventory/hbomAnalysis.js +310 -0
  190. package/lib/inventory/hbomLoader.js +35 -0
  191. package/lib/inventory/hostTopology.js +835 -0
  192. package/lib/inventory/inventoryStats.js +100 -0
  193. package/lib/inventory/jsonLike.js +121 -0
  194. package/lib/inventory/licenseEnhancer.js +918 -0
  195. package/lib/inventory/lolbas.js +285 -0
  196. package/lib/inventory/mcp.js +253 -0
  197. package/lib/inventory/mcpConfigParser.js +683 -0
  198. package/lib/inventory/mcpDiscovery.js +112 -0
  199. package/lib/inventory/mcpPinning.js +209 -0
  200. package/lib/inventory/osPackageResolver.js +493 -0
  201. package/lib/inventory/osinfo.js +277 -0
  202. package/lib/inventory/osqueryTransform.js +274 -0
  203. package/lib/inventory/plugins.js +466 -0
  204. package/lib/inventory/protobom.js +231 -0
  205. package/lib/inventory/protobomLoader.js +43 -0
  206. package/lib/inventory/provenanceUtils.js +224 -0
  207. package/lib/inventory/purl.js +749 -0
  208. package/lib/inventory/rustFormulationParser.js +339 -0
  209. package/lib/inventory/sbomDocument.js +340 -0
  210. package/lib/inventory/source.js +1488 -0
  211. package/lib/inventory/spdx.js +361 -0
  212. package/lib/inventory/spdxExpression.js +512 -0
  213. package/lib/inventory/spdxUtils.js +107 -0
  214. package/lib/inventory/table.js +263 -0
  215. package/lib/inventory/traceRunner.js +439 -0
  216. package/lib/inventory/unicodeScan.js +147 -0
  217. package/lib/managers/binary.js +3307 -0
  218. package/lib/managers/containerutils.js +125 -0
  219. package/lib/managers/docker.js +2145 -0
  220. package/lib/managers/dockerConnection.js +162 -0
  221. package/lib/managers/oci.js +704 -0
  222. package/lib/managers/piptree.js +267 -0
  223. package/lib/parsers/cmakeCache.js +199 -0
  224. package/lib/parsers/gguf.js +492 -0
  225. package/lib/parsers/gitAiNotes.js +233 -0
  226. package/lib/parsers/gitmodules.js +239 -0
  227. package/lib/parsers/htmlExtract.js +610 -0
  228. package/lib/parsers/huggingfaceManifest.js +551 -0
  229. package/lib/parsers/huggingfaceUtils.js +506 -0
  230. package/lib/parsers/iri.js +618 -0
  231. package/lib/parsers/npmrc.js +99 -0
  232. package/lib/parsers/ollama.js +101 -0
  233. package/lib/parsers/xml.js +841 -0
  234. package/lib/server/openapi.yaml +433 -0
  235. package/lib/server/server.js +616 -0
  236. package/lib/stages/postgen/annotator.js +977 -0
  237. package/lib/stages/postgen/auditBom.js +468 -0
  238. package/lib/stages/postgen/postgen.js +1239 -0
  239. package/lib/stages/postgen/ruleEngine.js +731 -0
  240. package/lib/stages/postgen/sortBom.js +195 -0
  241. package/lib/stages/postgen/spdxConverter.js +796 -0
  242. package/lib/stages/postgen/specVersionCompat.js +703 -0
  243. package/lib/stages/pregen/envAudit.js +388 -0
  244. package/lib/stages/pregen/pregen.js +637 -0
  245. package/lib/third-party/README.md +9 -0
  246. package/lib/third-party/arborist/CHANGELOG.md +1109 -0
  247. package/lib/third-party/arborist/LICENSE.md +20 -0
  248. package/lib/third-party/arborist/README.md +315 -0
  249. package/lib/third-party/arborist/lib/arborist/index.js +258 -0
  250. package/lib/third-party/arborist/lib/arborist/load-actual.js +605 -0
  251. package/lib/third-party/arborist/lib/arborist/load-virtual.js +300 -0
  252. package/lib/third-party/arborist/lib/bin-links.js +120 -0
  253. package/lib/third-party/arborist/lib/calc-dep-flags.js +130 -0
  254. package/lib/third-party/arborist/lib/case-insensitive-map.js +51 -0
  255. package/lib/third-party/arborist/lib/consistent-resolve.js +42 -0
  256. package/lib/third-party/arborist/lib/debug.js +50 -0
  257. package/lib/third-party/arborist/lib/dep-valid.js +190 -0
  258. package/lib/third-party/arborist/lib/edge.js +417 -0
  259. package/lib/third-party/arborist/lib/from-path.js +36 -0
  260. package/lib/third-party/arborist/lib/gather-dep-set.js +43 -0
  261. package/lib/third-party/arborist/lib/index.js +22 -0
  262. package/lib/third-party/arborist/lib/inventory.js +138 -0
  263. package/lib/third-party/arborist/lib/link.js +169 -0
  264. package/lib/third-party/arborist/lib/node.js +1673 -0
  265. package/lib/third-party/arborist/lib/npm-extension.js +45 -0
  266. package/lib/third-party/arborist/lib/override-resolves.js +11 -0
  267. package/lib/third-party/arborist/lib/override-set.js +319 -0
  268. package/lib/third-party/arborist/lib/package-extensions.js +289 -0
  269. package/lib/third-party/arborist/lib/printable.js +210 -0
  270. package/lib/third-party/arborist/lib/proc-log.js +30 -0
  271. package/lib/third-party/arborist/lib/realpath.js +100 -0
  272. package/lib/third-party/arborist/lib/relpath.js +4 -0
  273. package/lib/third-party/arborist/lib/shrinkwrap.js +1291 -0
  274. package/lib/third-party/arborist/lib/spec-from-lock.js +34 -0
  275. package/lib/third-party/arborist/lib/tree-check.js +165 -0
  276. package/lib/third-party/arborist/lib/validate-npm-package-name.js +107 -0
  277. package/lib/third-party/arborist/lib/version-from-tgz.js +51 -0
  278. package/lib/third-party/arborist/lib/yarn-lock.js +397 -0
  279. package/lib/validator/bomValidator.js +985 -0
  280. package/lib/validator/complianceEngine.js +241 -0
  281. package/lib/validator/complianceRules.js +1680 -0
  282. package/lib/validator/generated/runtime.mjs +17 -0
  283. package/lib/validator/generated/validate-1.6.mjs +21258 -0
  284. package/lib/validator/generated/validate-1.7.mjs +24803 -0
  285. package/lib/validator/generated/validate-2.0.mjs +22931 -0
  286. package/lib/validator/index.js +246 -0
  287. package/lib/validator/reporters/annotations.js +142 -0
  288. package/lib/validator/reporters/console.js +160 -0
  289. package/lib/validator/reporters/index.js +41 -0
  290. package/lib/validator/reporters/json.js +37 -0
  291. package/lib/validator/reporters/sarif.js +184 -0
  292. package/package.json +209 -26
  293. package/types/bin/audit.d.ts +3 -0
  294. package/types/bin/audit.d.ts.map +1 -0
  295. package/types/bin/cdxgen.d.ts +3 -0
  296. package/types/bin/cdxgen.d.ts.map +1 -0
  297. package/types/bin/convert.d.ts +3 -0
  298. package/types/bin/convert.d.ts.map +1 -0
  299. package/types/bin/evinse.d.ts +3 -0
  300. package/types/bin/evinse.d.ts.map +1 -0
  301. package/types/bin/hbom.d.ts +3 -0
  302. package/types/bin/hbom.d.ts.map +1 -0
  303. package/types/bin/repl.d.ts +3 -0
  304. package/types/bin/repl.d.ts.map +1 -0
  305. package/types/bin/sign.d.ts +3 -0
  306. package/types/bin/sign.d.ts.map +1 -0
  307. package/types/bin/tracebom.d.ts +3 -0
  308. package/types/bin/tracebom.d.ts.map +1 -0
  309. package/types/bin/validate.d.ts +14 -0
  310. package/types/bin/validate.d.ts.map +1 -0
  311. package/types/bin/verify.d.ts +3 -0
  312. package/types/bin/verify.d.ts.map +1 -0
  313. package/types/lib/audit/index.d.ts +153 -0
  314. package/types/lib/audit/index.d.ts.map +1 -0
  315. package/types/lib/audit/progress.d.ts +31 -0
  316. package/types/lib/audit/progress.d.ts.map +1 -0
  317. package/types/lib/audit/reporters.d.ts +62 -0
  318. package/types/lib/audit/reporters.d.ts.map +1 -0
  319. package/types/lib/audit/scoring.d.ts +36 -0
  320. package/types/lib/audit/scoring.d.ts.map +1 -0
  321. package/types/lib/audit/targets.d.ts +77 -0
  322. package/types/lib/audit/targets.d.ts.map +1 -0
  323. package/types/lib/cli/bomAssembly.d.ts +315 -0
  324. package/types/lib/cli/bomAssembly.d.ts.map +1 -0
  325. package/types/lib/cli/cliOptions.d.ts +156 -0
  326. package/types/lib/cli/cliOptions.d.ts.map +1 -0
  327. package/types/lib/cli/index.d.ts +107 -0
  328. package/types/lib/cli/index.d.ts.map +1 -0
  329. package/types/lib/cli/jsBom.d.ts +94 -0
  330. package/types/lib/cli/jsBom.d.ts.map +1 -0
  331. package/types/lib/cli/jvmBom.d.ts +54 -0
  332. package/types/lib/cli/jvmBom.d.ts.map +1 -0
  333. package/types/lib/cli/managedBom.d.ts +105 -0
  334. package/types/lib/cli/managedBom.d.ts.map +1 -0
  335. package/types/lib/cli/nativeBom.d.ts +143 -0
  336. package/types/lib/cli/nativeBom.d.ts.map +1 -0
  337. package/types/lib/core/activity.d.ts +178 -0
  338. package/types/lib/core/activity.d.ts.map +1 -0
  339. package/types/lib/core/env.d.ts +192 -0
  340. package/types/lib/core/env.d.ts.map +1 -0
  341. package/types/lib/core/fs.d.ts +158 -0
  342. package/types/lib/core/fs.d.ts.map +1 -0
  343. package/types/lib/core/httpClient.d.ts +149 -0
  344. package/types/lib/core/httpClient.d.ts.map +1 -0
  345. package/types/lib/core/logger.d.ts +28 -0
  346. package/types/lib/core/logger.d.ts.map +1 -0
  347. package/types/lib/core/parallel.d.ts +95 -0
  348. package/types/lib/core/parallel.d.ts.map +1 -0
  349. package/types/lib/core/paths.d.ts +36 -0
  350. package/types/lib/core/paths.d.ts.map +1 -0
  351. package/types/lib/core/projectConfig.d.ts +23 -0
  352. package/types/lib/core/projectConfig.d.ts.map +1 -0
  353. package/types/lib/core/propertySanitizer.d.ts +23 -0
  354. package/types/lib/core/propertySanitizer.d.ts.map +1 -0
  355. package/types/lib/core/spdx.d.ts +5 -0
  356. package/types/lib/core/spdx.d.ts.map +1 -0
  357. package/types/lib/core/state.d.ts +20 -0
  358. package/types/lib/core/state.d.ts.map +1 -0
  359. package/types/lib/core/text.d.ts +78 -0
  360. package/types/lib/core/text.d.ts.map +1 -0
  361. package/types/lib/core/ui.d.ts +87 -0
  362. package/types/lib/core/ui.d.ts.map +1 -0
  363. package/types/lib/core/urls.d.ts +43 -0
  364. package/types/lib/core/urls.d.ts.map +1 -0
  365. package/types/lib/ecosystems/asarutils.d.ts +71 -0
  366. package/types/lib/ecosystems/asarutils.d.ts.map +1 -0
  367. package/types/lib/ecosystems/bunutils.d.ts +38 -0
  368. package/types/lib/ecosystems/bunutils.d.ts.map +1 -0
  369. package/types/lib/ecosystems/caxa.d.ts +8 -0
  370. package/types/lib/ecosystems/caxa.d.ts.map +1 -0
  371. package/types/lib/ecosystems/chromextutils.d.ts +97 -0
  372. package/types/lib/ecosystems/chromextutils.d.ts.map +1 -0
  373. package/types/lib/ecosystems/cmakeResolver.d.ts +176 -0
  374. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -0
  375. package/types/lib/ecosystems/cppEvidence.d.ts +35 -0
  376. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -0
  377. package/types/lib/ecosystems/denoutils.d.ts +76 -0
  378. package/types/lib/ecosystems/denoutils.d.ts.map +1 -0
  379. package/types/lib/ecosystems/dotnetutils.d.ts +45 -0
  380. package/types/lib/ecosystems/dotnetutils.d.ts.map +1 -0
  381. package/types/lib/ecosystems/ecosystems.d.ts +279 -0
  382. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -0
  383. package/types/lib/ecosystems/golem.d.ts +60 -0
  384. package/types/lib/ecosystems/golem.d.ts.map +1 -0
  385. package/types/lib/ecosystems/gradleutils.d.ts +139 -0
  386. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -0
  387. package/types/lib/ecosystems/jsEvidence.d.ts +17 -0
  388. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -0
  389. package/types/lib/ecosystems/npmutils.d.ts +183 -0
  390. package/types/lib/ecosystems/npmutils.d.ts.map +1 -0
  391. package/types/lib/ecosystems/parsers-bazel.d.ts +36 -0
  392. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -0
  393. package/types/lib/ecosystems/parsers-dotnet.d.ts +116 -0
  394. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -0
  395. package/types/lib/ecosystems/parsers-gleam.d.ts +33 -0
  396. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -0
  397. package/types/lib/ecosystems/parsers-go.d.ts +92 -0
  398. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -0
  399. package/types/lib/ecosystems/parsers-js.d.ts +242 -0
  400. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -0
  401. package/types/lib/ecosystems/parsers-jvm.d.ts +100 -0
  402. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -0
  403. package/types/lib/ecosystems/parsers-misc.d.ts +450 -0
  404. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -0
  405. package/types/lib/ecosystems/parsers-python.d.ts +145 -0
  406. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -0
  407. package/types/lib/ecosystems/parsers-rust.d.ts +56 -0
  408. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -0
  409. package/types/lib/ecosystems/parsers-zig.d.ts +83 -0
  410. package/types/lib/ecosystems/parsers-zig.d.ts.map +1 -0
  411. package/types/lib/ecosystems/pep770.d.ts +38 -0
  412. package/types/lib/ecosystems/pep770.d.ts.map +1 -0
  413. package/types/lib/ecosystems/pylockutils.d.ts +68 -0
  414. package/types/lib/ecosystems/pylockutils.d.ts.map +1 -0
  415. package/types/lib/ecosystems/pythonutils.d.ts +70 -0
  416. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -0
  417. package/types/lib/ecosystems/registryProvenance.d.ts +26 -0
  418. package/types/lib/ecosystems/registryProvenance.d.ts.map +1 -0
  419. package/types/lib/ecosystems/remote/huggingface.d.ts +31 -0
  420. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -0
  421. package/types/lib/ecosystems/rubyutils.d.ts +182 -0
  422. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -0
  423. package/types/lib/ecosystems/rusi.d.ts +40 -0
  424. package/types/lib/ecosystems/rusi.d.ts.map +1 -0
  425. package/types/lib/ecosystems/sbtutils.d.ts +179 -0
  426. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -0
  427. package/types/lib/ecosystems/tea.d.ts +180 -0
  428. package/types/lib/ecosystems/tea.d.ts.map +1 -0
  429. package/types/lib/ecosystems/utils.d.ts +41 -0
  430. package/types/lib/ecosystems/utils.d.ts.map +1 -0
  431. package/types/lib/ecosystems/zigResolver.d.ts +70 -0
  432. package/types/lib/ecosystems/zigResolver.d.ts.map +1 -0
  433. package/types/lib/evinser/db.d.ts +17 -0
  434. package/types/lib/evinser/db.d.ts.map +1 -0
  435. package/types/lib/evinser/evinser.d.ts +354 -0
  436. package/types/lib/evinser/evinser.d.ts.map +1 -0
  437. package/types/lib/evinser/scalasem.d.ts +15 -0
  438. package/types/lib/evinser/scalasem.d.ts.map +1 -0
  439. package/types/lib/evinser/swiftsem.d.ts +103 -0
  440. package/types/lib/evinser/swiftsem.d.ts.map +1 -0
  441. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  442. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  443. package/types/lib/helpers/bomSigner.d.ts +28 -0
  444. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  445. package/types/lib/helpers/exportUtils.d.ts +52 -0
  446. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  447. package/types/lib/helpers/remote/dependency-track.d.ts +23 -0
  448. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  449. package/types/lib/helpers/versutils.d.ts +8 -0
  450. package/types/lib/helpers/versutils.d.ts.map +1 -0
  451. package/types/lib/helpers/vsixutils.d.ts +130 -0
  452. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  453. package/types/lib/inventory/agentFormulationParser.d.ts +21 -0
  454. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -0
  455. package/types/lib/inventory/aiCollector.d.ts +61 -0
  456. package/types/lib/inventory/aiCollector.d.ts.map +1 -0
  457. package/types/lib/inventory/aiInventory.d.ts +103 -0
  458. package/types/lib/inventory/aiInventory.d.ts.map +1 -0
  459. package/types/lib/inventory/aiModelVariants.d.ts +31 -0
  460. package/types/lib/inventory/aiModelVariants.d.ts.map +1 -0
  461. package/types/lib/inventory/aiOversightCollector.d.ts +28 -0
  462. package/types/lib/inventory/aiOversightCollector.d.ts.map +1 -0
  463. package/types/lib/inventory/aiProvenanceCollector.d.ts +13 -0
  464. package/types/lib/inventory/aiProvenanceCollector.d.ts.map +1 -0
  465. package/types/lib/inventory/analyzer.d.ts +157 -0
  466. package/types/lib/inventory/analyzer.d.ts.map +1 -0
  467. package/types/lib/inventory/analyzerScope.d.ts +106 -0
  468. package/types/lib/inventory/analyzerScope.d.ts.map +1 -0
  469. package/types/lib/inventory/astWorker.d.ts +9 -0
  470. package/types/lib/inventory/astWorker.d.ts.map +1 -0
  471. package/types/lib/inventory/atomUtils.d.ts +159 -0
  472. package/types/lib/inventory/atomUtils.d.ts.map +1 -0
  473. package/types/lib/inventory/auditCategories.d.ts +80 -0
  474. package/types/lib/inventory/auditCategories.d.ts.map +1 -0
  475. package/types/lib/inventory/bomUtils.d.ts +145 -0
  476. package/types/lib/inventory/bomUtils.d.ts.map +1 -0
  477. package/types/lib/inventory/cacheCommands.d.ts +28 -0
  478. package/types/lib/inventory/cacheCommands.d.ts.map +1 -0
  479. package/types/lib/inventory/cacheDir.d.ts +55 -0
  480. package/types/lib/inventory/cacheDir.d.ts.map +1 -0
  481. package/types/lib/inventory/cbomutils.d.ts +37 -0
  482. package/types/lib/inventory/cbomutils.d.ts.map +1 -0
  483. package/types/lib/inventory/cdxrs.d.ts +90 -0
  484. package/types/lib/inventory/cdxrs.d.ts.map +1 -0
  485. package/types/lib/inventory/ciParsers/azurePipelines.d.ts +27 -0
  486. package/types/lib/inventory/ciParsers/azurePipelines.d.ts.map +1 -0
  487. package/types/lib/inventory/ciParsers/circleCi.d.ts +27 -0
  488. package/types/lib/inventory/ciParsers/circleCi.d.ts.map +1 -0
  489. package/types/lib/inventory/ciParsers/common.d.ts +11 -0
  490. package/types/lib/inventory/ciParsers/common.d.ts.map +1 -0
  491. package/types/lib/inventory/ciParsers/githubActions.d.ts +38 -0
  492. package/types/lib/inventory/ciParsers/githubActions.d.ts.map +1 -0
  493. package/types/lib/inventory/ciParsers/gitlabCi.d.ts +26 -0
  494. package/types/lib/inventory/ciParsers/gitlabCi.d.ts.map +1 -0
  495. package/types/lib/inventory/ciParsers/jenkins.d.ts +26 -0
  496. package/types/lib/inventory/ciParsers/jenkins.d.ts.map +1 -0
  497. package/types/lib/inventory/citations.d.ts +90 -0
  498. package/types/lib/inventory/citations.d.ts.map +1 -0
  499. package/types/lib/inventory/communityAiConfigParser.d.ts +14 -0
  500. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -0
  501. package/types/lib/inventory/containerRisk.d.ts +20 -0
  502. package/types/lib/inventory/containerRisk.d.ts.map +1 -0
  503. package/types/lib/inventory/cryptoAlgorithmFamily.d.ts +37 -0
  504. package/types/lib/inventory/cryptoAlgorithmFamily.d.ts.map +1 -0
  505. package/types/lib/inventory/deps.d.ts +140 -0
  506. package/types/lib/inventory/deps.d.ts.map +1 -0
  507. package/types/lib/inventory/depsUtils.d.ts +74 -0
  508. package/types/lib/inventory/depsUtils.d.ts.map +1 -0
  509. package/types/lib/inventory/display.d.ts +227 -0
  510. package/types/lib/inventory/display.d.ts.map +1 -0
  511. package/types/lib/inventory/dosai.d.ts +130 -0
  512. package/types/lib/inventory/dosai.d.ts.map +1 -0
  513. package/types/lib/inventory/dosaiParsers.d.ts +59 -0
  514. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -0
  515. package/types/lib/inventory/dynamic.d.ts +25 -0
  516. package/types/lib/inventory/dynamic.d.ts.map +1 -0
  517. package/types/lib/inventory/envcontext.d.ts +377 -0
  518. package/types/lib/inventory/envcontext.d.ts.map +1 -0
  519. package/types/lib/inventory/evidenceUtils.d.ts +74 -0
  520. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -0
  521. package/types/lib/inventory/fetchBatch.d.ts +127 -0
  522. package/types/lib/inventory/fetchBatch.d.ts.map +1 -0
  523. package/types/lib/inventory/fetchRate.d.ts +118 -0
  524. package/types/lib/inventory/fetchRate.d.ts.map +1 -0
  525. package/types/lib/inventory/forgeEnricher.d.ts +31 -0
  526. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -0
  527. package/types/lib/inventory/formulationParsers.d.ts +50 -0
  528. package/types/lib/inventory/formulationParsers.d.ts.map +1 -0
  529. package/types/lib/inventory/gtfobins.d.ts +27 -0
  530. package/types/lib/inventory/gtfobins.d.ts.map +1 -0
  531. package/types/lib/inventory/hbom.d.ts +58 -0
  532. package/types/lib/inventory/hbom.d.ts.map +1 -0
  533. package/types/lib/inventory/hbomAnalysis.d.ts +56 -0
  534. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -0
  535. package/types/lib/inventory/hbomLoader.d.ts +7 -0
  536. package/types/lib/inventory/hbomLoader.d.ts.map +1 -0
  537. package/types/lib/inventory/hostTopology.d.ts +47 -0
  538. package/types/lib/inventory/hostTopology.d.ts.map +1 -0
  539. package/types/lib/inventory/inventoryStats.d.ts +42 -0
  540. package/types/lib/inventory/inventoryStats.d.ts.map +1 -0
  541. package/types/lib/inventory/jsonLike.d.ts +23 -0
  542. package/types/lib/inventory/jsonLike.d.ts.map +1 -0
  543. package/types/lib/inventory/licenseEnhancer.d.ts +70 -0
  544. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -0
  545. package/types/lib/inventory/lolbas.d.ts +16 -0
  546. package/types/lib/inventory/lolbas.d.ts.map +1 -0
  547. package/types/lib/inventory/mcp.d.ts +29 -0
  548. package/types/lib/inventory/mcp.d.ts.map +1 -0
  549. package/types/lib/inventory/mcpConfigParser.d.ts +20 -0
  550. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -0
  551. package/types/lib/inventory/mcpDiscovery.d.ts +33 -0
  552. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -0
  553. package/types/lib/inventory/mcpPinning.d.ts +11 -0
  554. package/types/lib/inventory/mcpPinning.d.ts.map +1 -0
  555. package/types/lib/inventory/osPackageResolver.d.ts +49 -0
  556. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -0
  557. package/types/lib/inventory/osinfo.d.ts +120 -0
  558. package/types/lib/inventory/osinfo.d.ts.map +1 -0
  559. package/types/lib/inventory/osqueryTransform.d.ts +84 -0
  560. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -0
  561. package/types/lib/inventory/plugins.d.ts +58 -0
  562. package/types/lib/inventory/plugins.d.ts.map +1 -0
  563. package/types/lib/inventory/protobom.d.ts +71 -0
  564. package/types/lib/inventory/protobom.d.ts.map +1 -0
  565. package/types/lib/inventory/protobomLoader.d.ts +17 -0
  566. package/types/lib/inventory/protobomLoader.d.ts.map +1 -0
  567. package/types/lib/inventory/provenanceUtils.d.ts +98 -0
  568. package/types/lib/inventory/provenanceUtils.d.ts.map +1 -0
  569. package/types/lib/inventory/purl.d.ts +313 -0
  570. package/types/lib/inventory/purl.d.ts.map +1 -0
  571. package/types/lib/inventory/rustFormulationParser.d.ts +17 -0
  572. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -0
  573. package/types/lib/inventory/sbomDocument.d.ts +59 -0
  574. package/types/lib/inventory/sbomDocument.d.ts.map +1 -0
  575. package/types/lib/inventory/source.d.ts +163 -0
  576. package/types/lib/inventory/source.d.ts.map +1 -0
  577. package/types/lib/inventory/spdx.d.ts +66 -0
  578. package/types/lib/inventory/spdx.d.ts.map +1 -0
  579. package/types/lib/inventory/spdxExpression.d.ts +58 -0
  580. package/types/lib/inventory/spdxExpression.d.ts.map +1 -0
  581. package/types/lib/inventory/spdxUtils.d.ts +12 -0
  582. package/types/lib/inventory/spdxUtils.d.ts.map +1 -0
  583. package/types/lib/inventory/table.d.ts +19 -0
  584. package/types/lib/inventory/table.d.ts.map +1 -0
  585. package/types/lib/inventory/traceRunner.d.ts +80 -0
  586. package/types/lib/inventory/traceRunner.d.ts.map +1 -0
  587. package/types/lib/inventory/unicodeScan.d.ts +46 -0
  588. package/types/lib/inventory/unicodeScan.d.ts.map +1 -0
  589. package/types/lib/managers/binary.d.ts +76 -0
  590. package/types/lib/managers/binary.d.ts.map +1 -0
  591. package/types/lib/managers/containerutils.d.ts +30 -0
  592. package/types/lib/managers/containerutils.d.ts.map +1 -0
  593. package/types/lib/managers/docker.d.ts +157 -0
  594. package/types/lib/managers/docker.d.ts.map +1 -0
  595. package/types/lib/managers/dockerConnection.d.ts +42 -0
  596. package/types/lib/managers/dockerConnection.d.ts.map +1 -0
  597. package/types/lib/managers/oci.d.ts +21 -0
  598. package/types/lib/managers/oci.d.ts.map +1 -0
  599. package/types/lib/managers/piptree.d.ts +13 -0
  600. package/types/lib/managers/piptree.d.ts.map +1 -0
  601. package/types/lib/parsers/cmakeCache.d.ts +70 -0
  602. package/types/lib/parsers/cmakeCache.d.ts.map +1 -0
  603. package/types/lib/parsers/gguf.d.ts +39 -0
  604. package/types/lib/parsers/gguf.d.ts.map +1 -0
  605. package/types/lib/parsers/gitAiNotes.d.ts +15 -0
  606. package/types/lib/parsers/gitAiNotes.d.ts.map +1 -0
  607. package/types/lib/parsers/gitmodules.d.ts +88 -0
  608. package/types/lib/parsers/gitmodules.d.ts.map +1 -0
  609. package/types/lib/parsers/htmlExtract.d.ts +34 -0
  610. package/types/lib/parsers/htmlExtract.d.ts.map +1 -0
  611. package/types/lib/parsers/huggingfaceManifest.d.ts +153 -0
  612. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -0
  613. package/types/lib/parsers/huggingfaceUtils.d.ts +155 -0
  614. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -0
  615. package/types/lib/parsers/iri.d.ts +68 -0
  616. package/types/lib/parsers/iri.d.ts.map +1 -0
  617. package/types/lib/parsers/npmrc.d.ts +33 -0
  618. package/types/lib/parsers/npmrc.d.ts.map +1 -0
  619. package/types/lib/parsers/ollama.d.ts +22 -0
  620. package/types/lib/parsers/ollama.d.ts.map +1 -0
  621. package/types/lib/parsers/xml.d.ts +47 -0
  622. package/types/lib/parsers/xml.d.ts.map +1 -0
  623. package/types/lib/server/server.d.ts +51 -0
  624. package/types/lib/server/server.d.ts.map +1 -0
  625. package/types/lib/stages/postgen/annotator.d.ts +36 -0
  626. package/types/lib/stages/postgen/annotator.d.ts.map +1 -0
  627. package/types/lib/stages/postgen/auditBom.d.ts +92 -0
  628. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  629. package/types/lib/stages/postgen/postgen.d.ts +59 -0
  630. package/types/lib/stages/postgen/postgen.d.ts.map +1 -0
  631. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  632. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  633. package/types/lib/stages/postgen/sortBom.d.ts +34 -0
  634. package/types/lib/stages/postgen/sortBom.d.ts.map +1 -0
  635. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  636. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  637. package/types/lib/stages/postgen/specVersionCompat.d.ts +71 -0
  638. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -0
  639. package/types/lib/stages/pregen/envAudit.d.ts +14 -0
  640. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  641. package/types/lib/stages/pregen/pregen.d.ts +59 -0
  642. package/types/lib/stages/pregen/pregen.d.ts.map +1 -0
  643. package/types/lib/third-party/arborist/lib/arborist/index.d.ts +47 -0
  644. package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -0
  645. package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +4 -0
  646. package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +1 -0
  647. package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +4 -0
  648. package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +1 -0
  649. package/types/lib/third-party/arborist/lib/bin-links.d.ts +18 -0
  650. package/types/lib/third-party/arborist/lib/bin-links.d.ts.map +1 -0
  651. package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts +3 -0
  652. package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts.map +1 -0
  653. package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +9 -0
  654. package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -0
  655. package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts +3 -0
  656. package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts.map +1 -0
  657. package/types/lib/third-party/arborist/lib/debug.d.ts +3 -0
  658. package/types/lib/third-party/arborist/lib/debug.d.ts.map +1 -0
  659. package/types/lib/third-party/arborist/lib/dep-valid.d.ts +3 -0
  660. package/types/lib/third-party/arborist/lib/dep-valid.d.ts.map +1 -0
  661. package/types/lib/third-party/arborist/lib/edge.d.ts +46 -0
  662. package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -0
  663. package/types/lib/third-party/arborist/lib/from-path.d.ts +3 -0
  664. package/types/lib/third-party/arborist/lib/from-path.d.ts.map +1 -0
  665. package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +3 -0
  666. package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -0
  667. package/types/lib/third-party/arborist/lib/index.d.ts +3 -0
  668. package/types/lib/third-party/arborist/lib/index.d.ts.map +1 -0
  669. package/types/lib/third-party/arborist/lib/inventory.d.ts +14 -0
  670. package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -0
  671. package/types/lib/third-party/arborist/lib/link.d.ts +18 -0
  672. package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -0
  673. package/types/lib/third-party/arborist/lib/node.d.ts +112 -0
  674. package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -0
  675. package/types/lib/third-party/arborist/lib/npm-extension.d.ts +18 -0
  676. package/types/lib/third-party/arborist/lib/npm-extension.d.ts.map +1 -0
  677. package/types/lib/third-party/arborist/lib/override-resolves.d.ts +3 -0
  678. package/types/lib/third-party/arborist/lib/override-resolves.d.ts.map +1 -0
  679. package/types/lib/third-party/arborist/lib/override-set.d.ts +27 -0
  680. package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -0
  681. package/types/lib/third-party/arborist/lib/package-extensions.d.ts +36 -0
  682. package/types/lib/third-party/arborist/lib/package-extensions.d.ts.map +1 -0
  683. package/types/lib/third-party/arborist/lib/printable.d.ts +3 -0
  684. package/types/lib/third-party/arborist/lib/printable.d.ts.map +1 -0
  685. package/types/lib/third-party/arborist/lib/proc-log.d.ts +20 -0
  686. package/types/lib/third-party/arborist/lib/proc-log.d.ts.map +1 -0
  687. package/types/lib/third-party/arborist/lib/realpath.d.ts +3 -0
  688. package/types/lib/third-party/arborist/lib/realpath.d.ts.map +1 -0
  689. package/types/lib/third-party/arborist/lib/relpath.d.ts +3 -0
  690. package/types/lib/third-party/arborist/lib/relpath.d.ts.map +1 -0
  691. package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +54 -0
  692. package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -0
  693. package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts +3 -0
  694. package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts.map +1 -0
  695. package/types/lib/third-party/arborist/lib/tree-check.d.ts +3 -0
  696. package/types/lib/third-party/arborist/lib/tree-check.d.ts.map +1 -0
  697. package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts +13 -0
  698. package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts.map +1 -0
  699. package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +6 -0
  700. package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -0
  701. package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +35 -0
  702. package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -0
  703. package/types/lib/validator/bomValidator.d.ts +40 -0
  704. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  705. package/types/lib/validator/complianceEngine.d.ts +72 -0
  706. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  707. package/types/lib/validator/complianceRules.d.ts +61 -0
  708. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  709. package/types/lib/validator/index.d.ts +101 -0
  710. package/types/lib/validator/index.d.ts.map +1 -0
  711. package/types/lib/validator/reporters/annotations.d.ts +37 -0
  712. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  713. package/types/lib/validator/reporters/console.d.ts +34 -0
  714. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  715. package/types/lib/validator/reporters/index.d.ts +30 -0
  716. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  717. package/types/lib/validator/reporters/json.d.ts +11 -0
  718. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  719. package/types/lib/validator/reporters/sarif.d.ts +22 -0
  720. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  721. package/mod.d.ts +0 -7
  722. package/mod.js +0 -22
@@ -0,0 +1,2772 @@
1
+ import { Buffer } from "node:buffer";
2
+ import {
3
+ constants,
4
+ existsSync,
5
+ lstatSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ statSync,
9
+ } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import {
12
+ delimiter as _delimiter,
13
+ sep as _sep,
14
+ basename,
15
+ dirname,
16
+ join,
17
+ relative,
18
+ } from "node:path";
19
+
20
+ import { build } from "@cdxgen/cdx-purl";
21
+ import { globSync } from "glob";
22
+ import StreamZip from "node-stream-zip";
23
+ import {
24
+ clean,
25
+ coerce,
26
+ compare,
27
+ maxSatisfying,
28
+ parse,
29
+ satisfies,
30
+ } from "semver";
31
+
32
+ import {
33
+ cdxgenAgent,
34
+ DEBUG_MODE,
35
+ readEnvironmentVariable,
36
+ } from "../core/activity.js";
37
+ import {
38
+ SEARCH_MAVEN_ORG,
39
+ shouldFetchLicense,
40
+ shouldFetchPackageMetadata,
41
+ shouldFetchVCS,
42
+ } from "../core/env.js";
43
+ import {
44
+ checksumFile,
45
+ getAllFiles,
46
+ multiChecksumFile,
47
+ safeCopyFileSync,
48
+ safeExistsSync,
49
+ safeExtractArchive,
50
+ safeRmSync,
51
+ } from "../core/fs.js";
52
+ import { thoughtLog } from "../core/logger.js";
53
+ import { mapWithConcurrency } from "../core/parallel.js";
54
+ import { isWin } from "../core/paths.js";
55
+ import { vendorAliases } from "../core/state.js";
56
+ import { isGitHubUrl, modulePathHost } from "../core/urls.js";
57
+ import {
58
+ collectJarNS,
59
+ getPomPropertiesFromMavenDir,
60
+ inferJarGroupFromManifest,
61
+ parseJarManifest,
62
+ parsePomXml,
63
+ trimJarGroupSuffix,
64
+ } from "../inventory/deps.js";
65
+ import {
66
+ prefetchEnabled,
67
+ prefetchedResponse,
68
+ prefetchJson,
69
+ } from "../inventory/fetchBatch.js";
70
+ import {
71
+ applyPurl,
72
+ encodeForPurl,
73
+ genericPurl,
74
+ pypiPurl,
75
+ } from "../inventory/purl.js";
76
+ import {
77
+ findLicenseId,
78
+ guessLicenseId,
79
+ spdxLicenses,
80
+ } from "../inventory/spdx.js";
81
+ import { extractLicenseText, extractRepoUrl } from "../parsers/htmlExtract.js";
82
+ import { xml2js } from "../parsers/xml.js";
83
+ import {
84
+ collectCargoRegistryProvenanceProperties,
85
+ collectNpmRegistryProvenanceProperties,
86
+ collectPypiRegistryProvenanceProperties,
87
+ } from "./registryProvenance.js";
88
+
89
+ // Metadata cache
90
+ export let metadata_cache = {};
91
+
92
+ // Speed up lookup namespaces for a given jar
93
+
94
+ // circuit breaker for search maven.org
95
+ let search_maven_org_errors = 0;
96
+ const MAX_SEARCH_MAVEN_ORG_ERRORS = 1;
97
+
98
+ // circuit breaker for get repo license
99
+ let get_repo_license_errors = 0;
100
+ const MAX_GET_REPO_LICENSE_ERRORS = 5;
101
+
102
+ /**
103
+ * Internal helper to reset metadata_cache. Used by parseGoModData (still in
104
+ * utils.js until batch 6) because ESM forbids reassigning an imported binding.
105
+ * NOT re-exported through the utils.js barrel.
106
+ */
107
+ export function _clearMetadataCache() {
108
+ metadata_cache = {};
109
+ }
110
+
111
+ /**
112
+ * Fetches license information for a list of Swift packages by querying the
113
+ * GitHub repository license API for packages hosted on github.com.
114
+ *
115
+ * @param {Object[]} pkgList List of Swift package objects with optional repository.url fields
116
+ * @returns {Promise<Object[]>} Resolved list of package objects, each augmented with a license field where available
117
+ */
118
+ export async function getSwiftPackageMetadata(pkgList) {
119
+ const cdepList = [];
120
+ // Swift resolves licences purely through repository lookups, so its whole
121
+ // network cost is one batched round.
122
+ await prefetchRepoLicenses(
123
+ pkgList
124
+ .filter((p) => isGitHubUrl(p.repository?.url))
125
+ .map((p) => p.repository.url),
126
+ );
127
+ for (const p of pkgList) {
128
+ if (p.repository?.url) {
129
+ if (isGitHubUrl(p.repository.url)) {
130
+ try {
131
+ p.license = await getRepoLicense(p.repository.url, undefined);
132
+ } catch (_e) {
133
+ console.error("error fetching repo license from", p.repository.url);
134
+ }
135
+ } else {
136
+ if (DEBUG_MODE) {
137
+ console.log(
138
+ p.repository.url,
139
+ "is currently not supported to fetch for licenses",
140
+ );
141
+ }
142
+ }
143
+ } else {
144
+ if (DEBUG_MODE) {
145
+ console.warn("no repository url found for", p.name);
146
+ }
147
+ }
148
+ cdepList.push(p);
149
+ }
150
+ return cdepList;
151
+ }
152
+
153
+ /** Accept header pub.dev requires for its v2 API. */
154
+ const PUB_ACCEPT = "application/vnd.pub.v2+json";
155
+
156
+ /**
157
+ * The registry path segment for a package: the name, scoped by its group when
158
+ * it has one.
159
+ *
160
+ * Extracted so the batch prefetch and the request inside the loop cannot drift
161
+ * apart — a prefetch that computed the URL even slightly differently would
162
+ * silently fetch documents nobody reads and leave the loop making its own
163
+ * serial requests.
164
+ *
165
+ * @param {Object} p Package object with `name` and optional `group`.
166
+ * @returns {string} Registry key, e.g. `@babel/core` or `left-pad`.
167
+ */
168
+ function npmRegistryKey(p) {
169
+ if (p.group && p.group !== "") {
170
+ const group = p.group.startsWith("@") ? p.group : `@${p.group}`;
171
+ return `${group}/${p.name}`;
172
+ }
173
+ return p.name;
174
+ }
175
+
176
+ /**
177
+ * Method to retrieve metadata for npm packages by querying npmjs
178
+ *
179
+ * @param {Array} pkgList Package list
180
+ */
181
+ export async function getNpmMetadata(pkgList, registryUrl) {
182
+ const NPM_URL =
183
+ registryUrl ||
184
+ readEnvironmentVariable("NPM_URL") ||
185
+ "https://registry.npmjs.org/";
186
+ const cdepList = [];
187
+ // Every URL this loop needs is known up front, so they are fetched
188
+ // concurrently before the loop rather than one at a time inside it. The
189
+ // derivation below is unchanged and still reads `body`; only where `body`
190
+ // comes from differs.
191
+ const prefetched = await prefetchJson(
192
+ prefetchEnabled()
193
+ ? pkgList.map((p) => ({ url: NPM_URL + npmRegistryKey(p) }))
194
+ : [],
195
+ );
196
+ // The licence fallback calls getRepoLicense with a URL that only exists once
197
+ // the registry document has arrived, so it needs a second batched round. The
198
+ // documents are already in hand here, which is why this can be done without a
199
+ // second pass over the network for the registry itself.
200
+ if (prefetched.size) {
201
+ const repoUrls = [];
202
+ for (const p of pkgList) {
203
+ const entry = prefetched.get(NPM_URL + npmRegistryKey(p));
204
+ if (!entry?.ok) {
205
+ continue;
206
+ }
207
+ const body = entry.body;
208
+ const versionLicense = body?.versions?.[p.version]?.license;
209
+ if (!versionLicense && !body?.license && body?.repository?.url) {
210
+ repoUrls.push(body.repository.url);
211
+ }
212
+ }
213
+ await prefetchRepoLicenses(repoUrls);
214
+ }
215
+ for (const p of pkgList) {
216
+ try {
217
+ const key = npmRegistryKey(p);
218
+ // Namespace the cache by registry so packages resolved from a
219
+ // non-default registry (e.g. jsr's npm mirror at npm.jsr.io) do not
220
+ // collide with identically-named packages on the default registry.
221
+ const cacheKey = `${NPM_URL}${key}`;
222
+ let body = {};
223
+ if (metadata_cache[cacheKey]) {
224
+ body = metadata_cache[cacheKey];
225
+ } else {
226
+ const url = NPM_URL + key;
227
+ const res =
228
+ prefetchedResponse(prefetched, url) ||
229
+ (await cdxgenAgent.get(url, {
230
+ responseType: "json",
231
+ }));
232
+ body = res.body;
233
+ metadata_cache[cacheKey] = body;
234
+ }
235
+ p.description =
236
+ body.versions?.[p.version]?.description || body.description;
237
+ p.license =
238
+ body.versions?.[p.version]?.license ||
239
+ body.license ||
240
+ (await getRepoLicense(body.repository?.url, undefined));
241
+ if (body.repository?.url) {
242
+ p.repository = { url: body.repository.url };
243
+ }
244
+ if (body.homepage) {
245
+ p.homepage = { url: body.homepage };
246
+ }
247
+ // Capture the resolved tarball as a distribution external reference when
248
+ // the component does not already carry one. This is the reliable source
249
+ // of a download URL for registries (such as jsr's npm mirror) whose
250
+ // tarball path is not deterministically constructible.
251
+ const distTarball = body.versions?.[p.version]?.dist?.tarball;
252
+ if (distTarball) {
253
+ p.externalReferences = p.externalReferences || [];
254
+ if (
255
+ !p.externalReferences.some(
256
+ (ref) => ref.type === "distribution" && ref.url === distTarball,
257
+ )
258
+ ) {
259
+ p.externalReferences.push({
260
+ type: "distribution",
261
+ url: distTarball,
262
+ });
263
+ }
264
+ }
265
+ p.properties = p.properties || [];
266
+ p.properties.push(
267
+ ...collectNpmRegistryProvenanceProperties(body, p.version),
268
+ );
269
+ cdepList.push(p);
270
+ } catch (_err) {
271
+ cdepList.push(p);
272
+ if (DEBUG_MODE) {
273
+ console.error(p, "was not found on npm");
274
+ }
275
+ }
276
+ }
277
+ return cdepList;
278
+ }
279
+
280
+ /**
281
+ * Method to locate local Gradle, Maven, or Coursier cache files for a given maven coordinate.
282
+ *
283
+ * @param {string} group Maven groupId
284
+ * @param {string} name Maven artifactId
285
+ * @param {string} version Package version
286
+ * @returns {Object|null} Object containing jarPath, sha1, and pomPath, or null
287
+ */
288
+ export function findLocalMvnArtifact(group, name, version) {
289
+ if (!group || !name || !version) {
290
+ return null;
291
+ }
292
+
293
+ // 1. Gradle cache
294
+ const gradleUserHome = readEnvironmentVariable("GRADLE_USER_HOME");
295
+ const gradleCacheDir =
296
+ readEnvironmentVariable("GRADLE_CACHE_DIR") ||
297
+ (gradleUserHome
298
+ ? join(gradleUserHome, "caches", "modules-2", "files-2.1")
299
+ : join(homedir(), ".gradle", "caches", "modules-2", "files-2.1"));
300
+
301
+ if (existsSync(gradleCacheDir)) {
302
+ const gradlePath = join(gradleCacheDir, group, name, version);
303
+ if (existsSync(gradlePath)) {
304
+ try {
305
+ const subdirs = readdirSync(gradlePath);
306
+ let jarPath = null;
307
+ let sha1 = null;
308
+ let pomPath = null;
309
+ for (const subdir of subdirs) {
310
+ const subdirPath = join(gradlePath, subdir);
311
+ if (statSync(subdirPath).isDirectory()) {
312
+ const files = readdirSync(subdirPath);
313
+ for (const file of files) {
314
+ if (
315
+ file.endsWith(".jar") &&
316
+ !file.endsWith("-sources.jar") &&
317
+ !file.endsWith("-javadoc.jar")
318
+ ) {
319
+ jarPath = join(subdirPath, file);
320
+ sha1 = subdir;
321
+ } else if (file.endsWith(".pom")) {
322
+ pomPath = join(subdirPath, file);
323
+ }
324
+ }
325
+ }
326
+ }
327
+ if (jarPath || pomPath) {
328
+ return { jarPath, sha1, pomPath };
329
+ }
330
+ } catch (_err) {
331
+ // ignore
332
+ }
333
+ }
334
+ }
335
+
336
+ // 2. Maven local repo
337
+ const mavenLocal = join(homedir(), ".m2", "repository");
338
+ const groupPath = group.replace(/\./g, "/");
339
+ const mavenPath = join(mavenLocal, groupPath, name, version);
340
+ if (existsSync(mavenPath)) {
341
+ const jarPath = join(mavenPath, `${name}-${version}.jar`);
342
+ const pomPath = join(mavenPath, `${name}-${version}.pom`);
343
+ let hasFiles = false;
344
+ const result = {};
345
+ if (existsSync(jarPath)) {
346
+ result.jarPath = jarPath;
347
+ hasFiles = true;
348
+ }
349
+ if (existsSync(pomPath)) {
350
+ result.pomPath = pomPath;
351
+ hasFiles = true;
352
+ }
353
+ if (hasFiles) {
354
+ return result;
355
+ }
356
+ }
357
+
358
+ // 3. Coursier cache (SBT)
359
+ let cacheRoot = readEnvironmentVariable("COURSIER_CACHE");
360
+ if (!cacheRoot) {
361
+ if (process.platform === "darwin") {
362
+ cacheRoot = join(homedir(), "Library", "Caches", "Coursier", "v1");
363
+ } else if (process.platform === "win32") {
364
+ const localAppData = readEnvironmentVariable("LOCALAPPDATA");
365
+ if (localAppData) {
366
+ cacheRoot = join(localAppData, "Coursier", "Cache", "v1");
367
+ } else {
368
+ cacheRoot = join(
369
+ homedir(),
370
+ "AppData",
371
+ "Local",
372
+ "Coursier",
373
+ "Cache",
374
+ "v1",
375
+ );
376
+ }
377
+ } else {
378
+ cacheRoot = join(homedir(), ".cache", "coursier", "v1");
379
+ }
380
+ }
381
+
382
+ if (existsSync(cacheRoot)) {
383
+ const pattern = `**/${groupPath}/${name}/${version}/*`;
384
+ try {
385
+ const matches = globSync(pattern, { cwd: cacheRoot });
386
+ if (matches && matches.length > 0) {
387
+ let jarPath = null;
388
+ let pomPath = null;
389
+ for (const m of matches) {
390
+ const match = m.replace(/\\/g, "/");
391
+ if (
392
+ match.endsWith(".jar") &&
393
+ !match.endsWith("-sources.jar") &&
394
+ !match.endsWith("-javadoc.jar")
395
+ ) {
396
+ const localPath = join(cacheRoot, match);
397
+ if (existsSync(localPath)) {
398
+ jarPath = localPath;
399
+ }
400
+ } else if (match.endsWith(".pom")) {
401
+ const localPath = join(cacheRoot, match);
402
+ if (existsSync(localPath)) {
403
+ pomPath = localPath;
404
+ }
405
+ }
406
+ }
407
+ if (jarPath || pomPath) {
408
+ return { jarPath, pomPath };
409
+ }
410
+ }
411
+ } catch (_err) {
412
+ // ignore
413
+ }
414
+ }
415
+
416
+ return null;
417
+ }
418
+
419
+ /**
420
+ * Method to retrieve metadata for maven packages by querying maven central
421
+ *
422
+ * @param {Array} pkgList Package list
423
+ * @param {Object} jarNSMapping Jar Namespace mapping object
424
+ * @param {Boolean} force Force fetching of license
425
+ *
426
+ * @returns {Array} Updated package list
427
+ */
428
+ export async function getMvnMetadata(
429
+ pkgList,
430
+ jarNSMapping = {},
431
+ force = false,
432
+ ) {
433
+ const MAVEN_CENTRAL_URL =
434
+ readEnvironmentVariable("MAVEN_CENTRAL_URL") ||
435
+ "https://repo1.maven.org/maven2/";
436
+ const ANDROID_MAVEN_URL =
437
+ readEnvironmentVariable("ANDROID_MAVEN_URL") || "https://maven.google.com/";
438
+ const cdepList = [];
439
+ if (!pkgList?.length) {
440
+ return pkgList;
441
+ }
442
+ if (DEBUG_MODE && shouldFetchLicense()) {
443
+ console.log(`About to query maven for ${pkgList.length} packages`);
444
+ }
445
+ // Collect the direct POM URLs the loop is going to need and fetch them in
446
+ // one batched round. Only the direct POMs are prefetched: parent POMs (walked
447
+ // inside fetchPomXmlAsJson) depend on the direct POM's content and stay
448
+ // serial. The conditions below mirror the loop's skip checks so the batch
449
+ // does not request anything the loop would skip.
450
+ const batchUrls = [];
451
+ const seenPomUrls = new Set();
452
+ for (const p of pkgList) {
453
+ const group = p.group || "";
454
+ if (
455
+ !p.version ||
456
+ (group && p.name && p.version && !shouldFetchLicense() && !force) ||
457
+ (group && p.name && p.version && p.license)
458
+ ) {
459
+ continue;
460
+ }
461
+ if (!group || group === "") {
462
+ continue;
463
+ }
464
+ const urlPrefix =
465
+ group.indexOf("android") !== -1 ? ANDROID_MAVEN_URL : MAVEN_CENTRAL_URL;
466
+ const pomUrl = composePomXmlUrl({
467
+ urlPrefix,
468
+ group,
469
+ name: p.name,
470
+ version: p.version,
471
+ });
472
+ if (!seenPomUrls.has(pomUrl)) {
473
+ seenPomUrls.add(pomUrl);
474
+ batchUrls.push({ url: pomUrl, responseType: "text" });
475
+ }
476
+ }
477
+ const ownPomUrls = [];
478
+ if (batchUrls.length) {
479
+ const fetched = await prefetchJson(batchUrls);
480
+ const repoUrls = [];
481
+ for (const [key, value] of fetched) {
482
+ prefetchedPoms.set(key, value);
483
+ ownPomUrls.push(key);
484
+ const scmUrl = pomScmUrlNeedingLicenseLookup(value);
485
+ if (scmUrl) {
486
+ repoUrls.push(scmUrl);
487
+ }
488
+ }
489
+ // The POMs are in hand, so the licence fallback's repository URLs are known
490
+ // without a second network round for the POMs themselves — the same second
491
+ // round getNpmMetadata does. Only a POM that declares no licence and no
492
+ // licence comment reaches getRepoLicense, and only its own scm element is
493
+ // read here, so this never fetches a licence the serial path would not.
494
+ await prefetchRepoLicenses(repoUrls);
495
+ }
496
+ try {
497
+ return await enrichMvnPackages(pkgList, cdepList, jarNSMapping, force, {
498
+ MAVEN_CENTRAL_URL,
499
+ ANDROID_MAVEN_URL,
500
+ });
501
+ } finally {
502
+ // Each body is read once, by the loop below. Holding them past that only
503
+ // grows the process, which matters in server mode where one process serves
504
+ // many scans. Only the documents this call fetched are dropped, so a scan
505
+ // running alongside this one keeps its own.
506
+ for (const pomUrl of ownPomUrls) {
507
+ prefetchedPoms.delete(pomUrl);
508
+ }
509
+ }
510
+ }
511
+
512
+ /**
513
+ * Enrich Maven packages from their POMs, reading any document already fetched
514
+ * by {@link getMvnMetadata}'s batch round.
515
+ *
516
+ * @param {Array} pkgList Packages to enrich.
517
+ * @param {Array} cdepList Accumulator for the enriched packages.
518
+ * @param {Object} jarNSMapping Namespace and hash data from jar analysis.
519
+ * @param {Boolean} force Whether to query even when metadata is present.
520
+ * @param {{MAVEN_CENTRAL_URL: string, ANDROID_MAVEN_URL: string}} repos Repository
521
+ * base URLs resolved by the caller from the environment.
522
+ * @returns {Promise<Array>} The enriched package list.
523
+ */
524
+ async function enrichMvnPackages(
525
+ pkgList,
526
+ cdepList,
527
+ jarNSMapping,
528
+ force,
529
+ { MAVEN_CENTRAL_URL, ANDROID_MAVEN_URL },
530
+ ) {
531
+ for (const p of pkgList) {
532
+ // Reuse any namespace and hashes data from jarNSMapping
533
+ if (jarNSMapping && p.purl && jarNSMapping[p.purl]) {
534
+ if (jarNSMapping[p.purl].jarFile) {
535
+ p.evidence = {
536
+ identity: {
537
+ field: "purl",
538
+ confidence: 0.8,
539
+ methods: [
540
+ {
541
+ technique: "binary-analysis",
542
+ confidence: 0.8,
543
+ value: jarNSMapping[p.purl].jarFile,
544
+ },
545
+ ],
546
+ },
547
+ };
548
+ }
549
+ if (jarNSMapping[p.purl].hashes && !p?.hashes?.length) {
550
+ p.hashes = jarNSMapping[p.purl].hashes;
551
+ }
552
+ if (
553
+ jarNSMapping[p.purl].namespaces &&
554
+ jarNSMapping[p.purl].namespaces.length
555
+ ) {
556
+ if (!p.properties) {
557
+ p.properties = [];
558
+ }
559
+ p.properties.push({
560
+ name: "internal:Namespaces",
561
+ value: jarNSMapping[p.purl].namespaces.join("\n"),
562
+ });
563
+ }
564
+ }
565
+ const localArtifact = findLocalMvnArtifact(p.group, p.name, p.version);
566
+ if (localArtifact) {
567
+ if (localArtifact.jarPath && (!p.hashes || p.hashes.length === 0)) {
568
+ try {
569
+ const hashValues = await multiChecksumFile(
570
+ ["md5", "sha1", "sha256", "sha512"],
571
+ localArtifact.jarPath,
572
+ );
573
+ p.hashes = [
574
+ { alg: "MD5", content: hashValues["md5"] },
575
+ { alg: "SHA-1", content: hashValues["sha1"] },
576
+ { alg: "SHA-256", content: hashValues["sha256"] },
577
+ { alg: "SHA-512", content: hashValues["sha512"] },
578
+ ];
579
+ } catch (_err) {
580
+ if (localArtifact.sha1) {
581
+ p.hashes = [{ alg: "SHA-1", content: localArtifact.sha1 }];
582
+ }
583
+ }
584
+ }
585
+ if (localArtifact.pomPath) {
586
+ try {
587
+ const xmlData = readFileSync(localArtifact.pomPath, "utf-8");
588
+ const pomData = parsePomXml(xmlData);
589
+ if (pomData) {
590
+ if (!p.publisher && pomData.organization?.name?._) {
591
+ p.publisher = pomData.organization.name._;
592
+ }
593
+ if (!p.description && pomData.description) {
594
+ p.description = pomData.description
595
+ .replace(/[ \t]+/g, " ")
596
+ .replace(/^[ \t]+|[ \t]+$/gm, "")
597
+ .replace(/\n\s*\n/g, "\n")
598
+ .trim();
599
+ }
600
+ if (!p.repository && pomData.scm) {
601
+ p.repository = { url: pomData.scm };
602
+ }
603
+ if (!p.license) {
604
+ const parsedLicense = parseLicenseEntryOrArrayFromPomXml(
605
+ pomData.licenses,
606
+ );
607
+ if (parsedLicense) {
608
+ p.license = parsedLicense;
609
+ } else {
610
+ const licenseRegex = /<!--([\s\S]*?)-->[\s\n]*<project/m;
611
+ const match = licenseRegex.exec(xmlData);
612
+ if (match?.[1]) {
613
+ const commentLicense = findLicenseId(match[1].trim());
614
+ if (commentLicense) {
615
+ p.license = commentLicense;
616
+ }
617
+ }
618
+ }
619
+ }
620
+ }
621
+ } catch (_err) {
622
+ // ignore
623
+ }
624
+ }
625
+ }
626
+ const group = p.group || "";
627
+ // If the package already has key metadata skip querying maven
628
+ if (
629
+ !p.version ||
630
+ (group && p.name && p.version && !shouldFetchLicense() && !force) ||
631
+ (group && p.name && p.version && p.license)
632
+ ) {
633
+ cdepList.push(p);
634
+ continue;
635
+ }
636
+ let urlPrefix = MAVEN_CENTRAL_URL;
637
+ // Ideally we should try one resolver after the other. But it increases the time taken
638
+ if (group.indexOf("android") !== -1) {
639
+ urlPrefix = ANDROID_MAVEN_URL;
640
+ }
641
+ // Querying maven requires a valid group name
642
+ if (!group || group === "") {
643
+ cdepList.push(p);
644
+ continue;
645
+ }
646
+ const pomMetadata = {
647
+ urlPrefix: urlPrefix,
648
+ group: group,
649
+ name: p.name,
650
+ version: p.version,
651
+ };
652
+ try {
653
+ if (DEBUG_MODE) {
654
+ console.log(
655
+ `Querying ${pomMetadata.urlPrefix} for '${group}/${p.name}@${p.version}' ${composePomXmlUrl(
656
+ pomMetadata,
657
+ )}`,
658
+ );
659
+ }
660
+ const bodyJson = await fetchPomXmlAsJson(pomMetadata);
661
+ if (bodyJson) {
662
+ p.publisher = bodyJson?.organization?.name
663
+ ? bodyJson?.organization.name._
664
+ : "";
665
+ p.description = bodyJson?.description
666
+ ? bodyJson.description._.replace(/[ \t]+/g, " ")
667
+ .replace(/^[ \t]+|[ \t]+$/gm, "")
668
+ .replace(/\n\s*\n/g, "\n")
669
+ .trim()
670
+ : "";
671
+ if (bodyJson?.scm?.url) {
672
+ p.repository = { url: bodyJson.scm.url._ };
673
+ }
674
+ p.license =
675
+ parseLicenseEntryOrArrayFromPomXml(bodyJson?.licenses?.license) ||
676
+ (await extractLicenseCommentFromPomXml(pomMetadata)) ||
677
+ (await getRepoLicense(p.repository?.url, undefined));
678
+ }
679
+ } catch (err) {
680
+ if (DEBUG_MODE) {
681
+ console.log(
682
+ `An error occurred when trying to fetch metadata ${pomMetadata}`,
683
+ err,
684
+ );
685
+ }
686
+ } finally {
687
+ cdepList.push(p);
688
+ }
689
+ }
690
+ return cdepList;
691
+ }
692
+
693
+ /**
694
+ * The repository URL a prefetched POM will need a GitHub licence lookup for.
695
+ *
696
+ * Mirrors the order `enrichMvnPackages` resolves a licence in: a `<licenses>`
697
+ * element wins, then a licence comment, and only a POM offering neither falls
698
+ * through to `getRepoLicense`. A POM whose `scm` element is inherited from a
699
+ * parent is not covered, since the parent document is not prefetched.
700
+ *
701
+ * @param {{ok?: boolean, body?: String}} entry A prefetched POM batch entry.
702
+ * @returns {String|undefined} The scm URL to prefetch a licence for, if any.
703
+ */
704
+ function pomScmUrlNeedingLicenseLookup(entry) {
705
+ if (!entry?.ok || !entry.body) {
706
+ return undefined;
707
+ }
708
+ let pomJson;
709
+ try {
710
+ pomJson = xml2js(entry.body, {
711
+ compact: true,
712
+ spaces: 4,
713
+ textKey: "_",
714
+ attributesKey: "$",
715
+ commentKey: "value",
716
+ }).project;
717
+ } catch (_err) {
718
+ return undefined;
719
+ }
720
+ if (parseLicenseEntryOrArrayFromPomXml(pomJson?.licenses?.license)) {
721
+ return undefined;
722
+ }
723
+ const commentMatch = /<!--([\s\S]*?)-->[\s\n]*<project/m.exec(entry.body);
724
+ if (commentMatch?.[1] && findLicenseId(commentMatch[1].trim())) {
725
+ return undefined;
726
+ }
727
+ return pomJson?.scm?.url?._;
728
+ }
729
+
730
+ /**
731
+ * Method to compose URL of pom.xml
732
+ *
733
+ * @param {String} urlPrefix
734
+ * @param {String} group
735
+ * @param {String} name
736
+ * @param {String} version
737
+ *
738
+ * @return {String} fullUrl
739
+ */
740
+ export function composePomXmlUrl({ urlPrefix, group, name, version }) {
741
+ const groupPart = group.replace(/\./g, "/");
742
+ return `${urlPrefix + groupPart}/${name}/${version}/${name}-${version}.pom`;
743
+ }
744
+
745
+ /**
746
+ * Method to fetch pom.xml data and parse it to JSON
747
+ *
748
+ * @param {String} urlPrefix
749
+ * @param {String} group
750
+ * @param {String} name
751
+ * @param {String} version
752
+ *
753
+ * @return {Object|undefined}
754
+ */
755
+ export async function fetchPomXmlAsJson({ urlPrefix, group, name, version }) {
756
+ const pomXml = await fetchPomXml({ urlPrefix, group, name, version });
757
+ if (!pomXml) {
758
+ return undefined;
759
+ }
760
+ const options = {
761
+ compact: true,
762
+ spaces: 4,
763
+ textKey: "_",
764
+ attributesKey: "$",
765
+ commentKey: "value",
766
+ };
767
+ const pomJson = xml2js(pomXml, options).project;
768
+ if (pomJson?.parent) {
769
+ const parentXml = await fetchPomXml({
770
+ urlPrefix,
771
+ group: pomJson.parent.groupId?._,
772
+ name: pomJson.parent.artifactId?._,
773
+ version: pomJson.parent.version?._,
774
+ });
775
+ if (!parentXml) {
776
+ return undefined;
777
+ }
778
+ const parentJson = xml2js(parentXml, options).project;
779
+ return { ...parentJson, ...pomJson };
780
+ }
781
+ return pomJson;
782
+ }
783
+
784
+ /**
785
+ * Prefetched POM documents, populated by `getMvnMetadata` before its loop and
786
+ * read by `fetchPomXml`. Module state rather than a parameter because
787
+ * `fetchPomXml` is called from `fetchPomXmlAsJson` (direct + parent) and
788
+ * `extractLicenseCommentFromPomXml`, and threading a map through all of them
789
+ * would be a wide change for no gain.
790
+ *
791
+ * Only the *direct* POMs — the ones whose URLs are known before the loop — are
792
+ * prefetched. The parent POM walk inside `fetchPomXmlAsJson` depends on
793
+ * parsing the direct POM's content, so those URLs are not known up front and
794
+ * remain serial.
795
+ */
796
+ const prefetchedPoms = new Map();
797
+
798
+ /**
799
+ * Method to fetch pom.xml data
800
+ *
801
+ * @param {String} urlPrefix
802
+ * @param {String} group
803
+ * @param {String} name
804
+ * @param {String} version
805
+ *
806
+ * @return {Promise<String>}
807
+ */
808
+ export async function fetchPomXml({ urlPrefix, group, name, version }) {
809
+ const fullUrl = composePomXmlUrl({ urlPrefix, group, name, version });
810
+ // A direct POM may have been prefetched by getMvnMetadata's batch round.
811
+ // Parent POMs (fetched from inside fetchPomXmlAsJson) are not prefetched
812
+ // because their URL depends on the direct POM's content, so they fall
813
+ // through to the serial request unchanged.
814
+ try {
815
+ const res =
816
+ prefetchedResponse(prefetchedPoms, fullUrl) ||
817
+ (await cdxgenAgent.get(fullUrl));
818
+ return res.body;
819
+ } catch (_err) {
820
+ return undefined;
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Method extract single or multiple license entries that might appear in pom.xml
826
+ *
827
+ * @param {Object|Array} license
828
+ */
829
+ export function parseLicenseEntryOrArrayFromPomXml(license) {
830
+ if (!license) return;
831
+ if (Array.isArray(license)) {
832
+ return license.map((l) => {
833
+ return findLicenseId(l.name?._);
834
+ });
835
+ }
836
+ if (Object.keys(license).length) {
837
+ return [findLicenseId(license.name?._)];
838
+ }
839
+ }
840
+
841
+ /**
842
+ * Method to parse pom.xml in search of a comment containing license text
843
+ *
844
+ * @param {String} urlPrefix
845
+ * @param {String} group
846
+ * @param {String} name
847
+ * @param {String} version
848
+ *
849
+ * @return {Promise<String>} License ID
850
+ */
851
+ export async function extractLicenseCommentFromPomXml({
852
+ urlPrefix,
853
+ group,
854
+ name,
855
+ version,
856
+ }) {
857
+ const pom_xml = await fetchPomXml({ urlPrefix, group, name, version });
858
+ const licenseRegex = /<!--([\s\S]*?)-->[\s\n]*<project/m;
859
+ const match = licenseRegex.exec(pom_xml);
860
+ if (match?.[1]) {
861
+ return findLicenseId(match[1].trim());
862
+ }
863
+ }
864
+
865
+ /**
866
+ * Method to mimic pip version solver using node-semver
867
+ *
868
+ * @param {Array} versionsList List of version numbers available
869
+ * @param {*} versionSpecifiers pip version specifier
870
+ */
871
+ export function guessPypiMatchingVersion(versionsList, versionSpecifiers) {
872
+ versionSpecifiers = versionSpecifiers.replace(/,/g, " ").split(";")[0];
873
+ const comparator = (a, b) => {
874
+ if (!a && !b) {
875
+ return 0;
876
+ }
877
+ if (!a || !coerce(a, { loose: true })) {
878
+ return -1;
879
+ }
880
+ let c = coerce(a, { loose: true }).compare(coerce(b, { loose: true }));
881
+ // if coerced versions are "equal", compare them as strings
882
+ if (c === 0) {
883
+ c = a < b ? -1 : 1;
884
+ }
885
+ return -c;
886
+ };
887
+ // Iterate in the "reverse" order
888
+ for (const rv of versionsList.sort(comparator)) {
889
+ if (satisfies(coerce(rv, { loose: true }), versionSpecifiers, true)) {
890
+ return rv;
891
+ }
892
+ }
893
+ // Let's try to clean and have another go
894
+ return maxSatisfying(versionsList, clean(versionSpecifiers, { loose: true }));
895
+ }
896
+
897
+ /**
898
+ * The PyPI path for a package, or `undefined` when the package will not be
899
+ * queried at all.
900
+ *
901
+ * Shared by the batch prefetch and the request inside the loop so the two
902
+ * cannot construct different URLs. It deliberately reproduces the loop's skip
903
+ * conditions — a URL built for a package the loop skips would be a request the
904
+ * JS path never makes, which is both wasted work and a divergence.
905
+ *
906
+ * @param {Object} p Package object.
907
+ * @returns {string|undefined} Path to append to PYPI_URL.
908
+ */
909
+ function pyUrlAddition(p) {
910
+ if (!p?.name) {
911
+ return undefined;
912
+ }
913
+ // A URL as a name, or a package that already has both fields, is not queried.
914
+ if (p.name.includes("https") || (p.license && p.version)) {
915
+ return undefined;
916
+ }
917
+ // Extras (`requests[security]`) are not part of the PyPI path.
918
+ const name = p.name.includes("[") ? p.name.split("[")[0] : p.name;
919
+ return p.version?.trim().length
920
+ ? `${name}/${p.version.trim()}/json`
921
+ : `${name}/json`;
922
+ }
923
+
924
+ /**
925
+ * Method to retrieve metadata for python packages by querying pypi
926
+ *
927
+ * @param {Array} pkgList Package list
928
+ * @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
929
+ */
930
+ export async function getPyMetadata(pkgList, fetchDepsInfo) {
931
+ if (!shouldFetchPackageMetadata() && !fetchDepsInfo) {
932
+ return pkgList;
933
+ }
934
+ const PYPI_URL =
935
+ readEnvironmentVariable("PYPI_URL") || "https://pypi.org/pypi/";
936
+ const cdepList = [];
937
+ // One batched round for the primary lookups. The `django-` retry below is not
938
+ // prefetched: it only fires when the primary 404s, so batching it would issue
939
+ // a request the serial path never makes for every package that resolves.
940
+ const prefetched = await prefetchJson(
941
+ prefetchEnabled()
942
+ ? pkgList
943
+ .map((p) => pyUrlAddition(p))
944
+ .filter(Boolean)
945
+ .map((addition) => ({ url: PYPI_URL + addition }))
946
+ : [],
947
+ );
948
+ for (const p of pkgList) {
949
+ if (!p?.name) {
950
+ continue;
951
+ }
952
+ try {
953
+ // If the package name has a url or already includes license and version skip it
954
+ if (p.name.includes("https") || (p.license && p.version)) {
955
+ cdepList.push(p);
956
+ continue;
957
+ }
958
+ const origName = p.name;
959
+ // Some packages support extra modules
960
+ if (p.name.includes("[")) {
961
+ p.name = p.name.split("[")[0];
962
+ }
963
+ let res;
964
+ let url_addition;
965
+ if (p.version?.trim().length) {
966
+ url_addition = `${p.name}/${p.version.trim()}/json`;
967
+ } else {
968
+ url_addition = `${p.name}/json`;
969
+ }
970
+ try {
971
+ res =
972
+ prefetchedResponse(prefetched, PYPI_URL + url_addition) ||
973
+ (await cdxgenAgent.get(`${PYPI_URL + url_addition}`, {
974
+ responseType: "json",
975
+ }));
976
+ } catch (_err) {
977
+ // retry by prefixing django- to the package name
978
+ res = await cdxgenAgent.get(`${PYPI_URL}django-${url_addition}`, {
979
+ responseType: "json",
980
+ });
981
+ p.name = `django-${p.name}`;
982
+ }
983
+ const body = res.body;
984
+ if (body.info.author && body.info.author.trim() !== "") {
985
+ if (body.info.author_email && body.info.author_email.trim() !== "") {
986
+ p.author = `${body.info.author.trim()} <${body.info.author_email.trim()}>`;
987
+ } else {
988
+ p.author = body.info.author.trim();
989
+ }
990
+ } else if (
991
+ body.info.author_email &&
992
+ body.info.author_email.trim() !== ""
993
+ ) {
994
+ p.author = body.info.author_email.trim();
995
+ }
996
+ if (
997
+ p.name !== body.info?.name &&
998
+ p.name.toLowerCase() === body.info?.name.toLowerCase()
999
+ ) {
1000
+ p.name = body.info.name;
1001
+ }
1002
+ p.description = body.info.summary;
1003
+ p.license = [];
1004
+ if (body.info.classifiers) {
1005
+ for (const c of body.info.classifiers) {
1006
+ if (c.startsWith("License :: ")) {
1007
+ const licenseName = c.split("::").slice(-1)[0].trim();
1008
+ const licenseId = findLicenseId(licenseName);
1009
+ if (licenseId && !p.license.includes(licenseId)) {
1010
+ p.license.push(licenseId);
1011
+ }
1012
+ }
1013
+ }
1014
+ }
1015
+ if (body.info.license) {
1016
+ const licenseId = findLicenseId(body.info.license);
1017
+ if (licenseId && !p.license.includes(licenseId)) {
1018
+ p.license.push(licenseId);
1019
+ }
1020
+ }
1021
+ if (body.info.license_expression) {
1022
+ const licenseId = findLicenseId(body.info.license_expression);
1023
+ if (licenseId && !p.license.includes(licenseId)) {
1024
+ p.license.push(licenseId);
1025
+ }
1026
+ }
1027
+ if (body.info.home_page) {
1028
+ if (body.info.home_page.includes("git")) {
1029
+ p.repository = { url: body.info.home_page };
1030
+ } else {
1031
+ p.homepage = { url: body.info.home_page };
1032
+ }
1033
+ }
1034
+ // Use the latest version if none specified
1035
+ if (!p.version?.trim().length) {
1036
+ let versionSpecifiers;
1037
+ if (p.properties?.length) {
1038
+ for (const pprop of p.properties) {
1039
+ if (pprop.name === "cdx:pypi:versionSpecifiers") {
1040
+ versionSpecifiers = pprop.value;
1041
+ break;
1042
+ }
1043
+ }
1044
+ } else if (
1045
+ p.version &&
1046
+ (p.version.includes("*") ||
1047
+ p.version.includes("<") ||
1048
+ p.version.includes(">") ||
1049
+ p.version.includes("!"))
1050
+ ) {
1051
+ versionSpecifiers = p.version;
1052
+ }
1053
+ if (versionSpecifiers) {
1054
+ p.version = guessPypiMatchingVersion(
1055
+ Object.keys(body.releases || {}),
1056
+ versionSpecifiers,
1057
+ );
1058
+ // Indicate the confidence with our guess
1059
+ p.evidence = {
1060
+ identity: {
1061
+ field: "version",
1062
+ confidence: 0.6,
1063
+ methods: [
1064
+ {
1065
+ technique: "manifest-analysis",
1066
+ confidence: 0.6,
1067
+ value: `Version specifiers: ${versionSpecifiers}`,
1068
+ },
1069
+ ],
1070
+ },
1071
+ };
1072
+ }
1073
+ // If we have reached here, it means we have not solved the version
1074
+ // So assume latest
1075
+ if (!p.version) {
1076
+ p.version = body.info.version;
1077
+ // Indicate the low confidence
1078
+ p.evidence = {
1079
+ identity: {
1080
+ field: "version",
1081
+ confidence: 0.5,
1082
+ methods: [
1083
+ {
1084
+ technique: "source-code-analysis",
1085
+ confidence: 0.5,
1086
+ value: `PyPI package: ${p.name}`,
1087
+ },
1088
+ ],
1089
+ },
1090
+ };
1091
+ }
1092
+ } else if (p.version !== body.info.version) {
1093
+ if (!p.properties) {
1094
+ p.properties = [];
1095
+ }
1096
+ p.properties.push({
1097
+ name: "cdx:pypi:latest_version",
1098
+ value: body.info.version,
1099
+ });
1100
+ p.properties.push({
1101
+ name: "cdx:pypi:resolved_from",
1102
+ value: origName,
1103
+ });
1104
+ }
1105
+ const releaseEntries = body.releases?.[p.version]?.length
1106
+ ? body.releases[p.version]
1107
+ : Array.isArray(body.urls)
1108
+ ? body.urls
1109
+ : [];
1110
+ mergeExternalReferences(
1111
+ p,
1112
+ collectPypiReleaseExternalReferences(releaseEntries),
1113
+ );
1114
+ if (releaseEntries.length) {
1115
+ const digest = releaseEntries[0].digests;
1116
+ if (digest["sha256"]) {
1117
+ p._integrity = `sha256-${digest["sha256"]}`;
1118
+ } else if (digest["md5"]) {
1119
+ p._integrity = `md5-${digest["md5"]}`;
1120
+ }
1121
+ }
1122
+ const purlString = build({
1123
+ type: "pypi",
1124
+ namespace: "" || null,
1125
+ name: p.name.toLowerCase(),
1126
+ version: p.version || null,
1127
+ });
1128
+ p.properties = p.properties || [];
1129
+ p.properties.push(
1130
+ ...collectPypiRegistryProvenanceProperties(body, p.version),
1131
+ );
1132
+ p.purl = purlString;
1133
+ p["bom-ref"] = decodeURIComponent(purlString);
1134
+ cdepList.push(p);
1135
+ } catch (_err) {
1136
+ if (DEBUG_MODE) {
1137
+ console.error(p.name, "is not found on PyPI.");
1138
+ console.log(
1139
+ "If this package is available from PyPI or a registry, its name might be different from the module name. Raise a ticket at https://github.com/cdxgen/cdxgen/issues so that this can be added to the mapping file pypi-pkg-aliases.json",
1140
+ );
1141
+ console.log(
1142
+ "Alternatively, if this is a package that gets installed directly in your environment and offers a python binding, then track such packages manually.",
1143
+ );
1144
+ }
1145
+ if (!p.version) {
1146
+ if (DEBUG_MODE) {
1147
+ console.log(
1148
+ `Assuming the version as latest for the package ${p.name}`,
1149
+ );
1150
+ }
1151
+ p.version = "latest";
1152
+ // Indicate the low confidence
1153
+ p.evidence = {
1154
+ identity: {
1155
+ field: "version",
1156
+ confidence: 0,
1157
+ methods: [
1158
+ {
1159
+ technique: "source-code-analysis",
1160
+ confidence: 0,
1161
+ value: `Module ${p.name}`,
1162
+ },
1163
+ ],
1164
+ },
1165
+ };
1166
+ }
1167
+ const purlString = build({
1168
+ type: "pypi",
1169
+ namespace: "" || null,
1170
+ name: p.name.toLowerCase(),
1171
+ version: p.version || null,
1172
+ });
1173
+ p.purl = purlString;
1174
+ p["bom-ref"] = decodeURIComponent(purlString);
1175
+ cdepList.push(p);
1176
+ }
1177
+ }
1178
+ return cdepList;
1179
+ }
1180
+
1181
+ /**
1182
+ * Method to parse bdist_wheel metadata (dist-info/METADATA)
1183
+ *
1184
+ * @param {string} mDataFile bdist_wheel metadata file
1185
+ * @param {string} rawMetadata Raw metadata
1186
+ *
1187
+ */
1188
+ export function parseBdistMetadata(mDataFile, rawMetadata = undefined) {
1189
+ const mData = rawMetadata || readFileSync(mDataFile, { encoding: "utf-8" });
1190
+ const pkg = {
1191
+ name: "",
1192
+ version: "",
1193
+ description: "",
1194
+ author: "",
1195
+ licenses: [],
1196
+ externalReferences: [],
1197
+ properties: [],
1198
+ };
1199
+ if (mDataFile) {
1200
+ pkg.properties.push({ name: "internal:SrcFile", value: mDataFile });
1201
+ }
1202
+ const lines = mData.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1203
+ let isBody = false;
1204
+ for (const line of lines) {
1205
+ if (line.trim() === "") {
1206
+ isBody = true;
1207
+ continue;
1208
+ }
1209
+ if (isBody) break;
1210
+ const firstColon = line.indexOf(":");
1211
+ if (firstColon === -1) continue;
1212
+ const key = line.substring(0, firstColon).trim().toLowerCase();
1213
+ const value = line.substring(firstColon + 1).trim();
1214
+ switch (key) {
1215
+ case "name":
1216
+ pkg.name = value;
1217
+ break;
1218
+ case "version":
1219
+ pkg.version = value;
1220
+ break;
1221
+ case "summary":
1222
+ pkg.description = value;
1223
+ break;
1224
+ case "author":
1225
+ case "maintainer":
1226
+ pkg.publisher = value;
1227
+ pkg.author = value;
1228
+ break;
1229
+ case "license-expression":
1230
+ pkg.licenses.push({
1231
+ expression: value,
1232
+ });
1233
+ break;
1234
+ case "license":
1235
+ if (value !== "UNKNOWN" && pkg.licenses.length === 0) {
1236
+ pkg.licenses.push({
1237
+ license: {
1238
+ name: value,
1239
+ },
1240
+ });
1241
+ }
1242
+ break;
1243
+ case "home-page":
1244
+ pkg.homepage = {
1245
+ url: value,
1246
+ };
1247
+ pkg.externalReferences.push({
1248
+ type: "website",
1249
+ url: value,
1250
+ });
1251
+ break;
1252
+ case "project-url": {
1253
+ const commaIndex = value.indexOf(",");
1254
+ if (commaIndex > -1) {
1255
+ const label = value.substring(0, commaIndex).trim();
1256
+ const url = value.substring(commaIndex + 1).trim();
1257
+ const lowerLabel = label.toLowerCase();
1258
+ let type = "website";
1259
+ if (
1260
+ ["source", "source code", "repository", "git"].includes(lowerLabel)
1261
+ ) {
1262
+ type = "vcs";
1263
+ pkg.repository = {
1264
+ url: url,
1265
+ };
1266
+ } else if (
1267
+ ["tracker", "bug tracker", "issue tracker", "issues"].includes(
1268
+ lowerLabel,
1269
+ )
1270
+ ) {
1271
+ type = "issue-tracker";
1272
+ } else if (["changelog", "changes", "history"].includes(lowerLabel)) {
1273
+ type = "release-notes";
1274
+ } else if (["documentation", "docs"].includes(lowerLabel)) {
1275
+ type = "documentation";
1276
+ } else if (["funding", "sponsor", "donation"].includes(lowerLabel)) {
1277
+ type = "other";
1278
+ }
1279
+ pkg.externalReferences.push({
1280
+ type: type,
1281
+ url: url,
1282
+ comment: label,
1283
+ });
1284
+ }
1285
+ break;
1286
+ }
1287
+ case "keywords":
1288
+ if (value) {
1289
+ pkg.keywords = value.split(",").map((k) => k.trim());
1290
+ }
1291
+ break;
1292
+ case "requires-python":
1293
+ pkg.properties.push({
1294
+ name: "cdx:python:requires_python",
1295
+ value: value,
1296
+ });
1297
+ break;
1298
+ }
1299
+ }
1300
+ if (mDataFile) {
1301
+ pkg.evidence = {
1302
+ identity: {
1303
+ field: "purl",
1304
+ confidence: 0.5,
1305
+ methods: [
1306
+ {
1307
+ technique: "manifest-analysis",
1308
+ confidence: 0.5,
1309
+ value: mDataFile,
1310
+ },
1311
+ ],
1312
+ },
1313
+ };
1314
+ }
1315
+ applyPurl(pkg, pypiPurl(pkg.name, pkg.version));
1316
+ return [pkg];
1317
+ }
1318
+
1319
+ /**
1320
+ * Build a stable dedupe key for an external reference from its type/url/comment.
1321
+ *
1322
+ * @param {{type: string, url: string, comment?: string}} reference External reference.
1323
+ * @returns {string} JSON-stringified dedupe key.
1324
+ */
1325
+ export function createExternalReferenceKey(reference) {
1326
+ return JSON.stringify([
1327
+ reference.type,
1328
+ reference.url,
1329
+ reference.comment || "",
1330
+ ]);
1331
+ }
1332
+
1333
+ /**
1334
+ * Merge external references onto a component, skipping duplicates.
1335
+ *
1336
+ * @param {object} component Component to enrich with external references.
1337
+ * @param {Array<{type: string, url: string, comment?: string}>} references References to merge.
1338
+ * @returns {void}
1339
+ */
1340
+ export function mergeExternalReferences(component, references) {
1341
+ if (!references?.length) {
1342
+ return;
1343
+ }
1344
+ const existingReferences = component.externalReferences || [];
1345
+ const seen = new Set(
1346
+ existingReferences.map((reference) =>
1347
+ createExternalReferenceKey(reference),
1348
+ ),
1349
+ );
1350
+ for (const reference of references) {
1351
+ const dedupeKey = createExternalReferenceKey(reference);
1352
+ if (seen.has(dedupeKey)) {
1353
+ continue;
1354
+ }
1355
+ seen.add(dedupeKey);
1356
+ existingReferences.push(reference);
1357
+ }
1358
+ if (existingReferences.length) {
1359
+ component.externalReferences = existingReferences;
1360
+ }
1361
+ }
1362
+
1363
+ function collectPypiReleaseExternalReferences(releaseEntries) {
1364
+ const externalReferences = [];
1365
+ for (const releaseEntry of releaseEntries || []) {
1366
+ if (typeof releaseEntry?.url !== "string" || !releaseEntry.url.trim()) {
1367
+ continue;
1368
+ }
1369
+ externalReferences.push({
1370
+ type: "distribution",
1371
+ url: releaseEntry.url.trim(),
1372
+ comment: releaseEntry.filename || releaseEntry.packagetype,
1373
+ });
1374
+ }
1375
+ return externalReferences;
1376
+ }
1377
+
1378
+ /**
1379
+ * Method to construct a GitHub API url for the given repo metadata
1380
+ * @param {Object} repoMetadata Repo metadata with group and name
1381
+ * @return {String|undefined} github api url (or undefined - if not enough data)
1382
+ */
1383
+ export function repoMetadataToGitHubApiUrl(repoMetadata) {
1384
+ if (repoMetadata) {
1385
+ const group = repoMetadata.group;
1386
+ const name = repoMetadata.name;
1387
+ // GITHUB_API_URL is what GitHub Actions itself sets, and it is what a
1388
+ // GitHub Enterprise user needs. It also makes this path testable without
1389
+ // reaching the real api.github.com, which is why the licence lookups had no
1390
+ // offline coverage before.
1391
+ const apiBase = (
1392
+ readEnvironmentVariable("GITHUB_API_URL") || "https://api.github.com"
1393
+ ).replace(/\/$/, "");
1394
+ let ghUrl = `${apiBase}/repos`;
1395
+ if (group && group !== "." && group !== "") {
1396
+ ghUrl = `${ghUrl}/${group.replace("github.com/", "")}`;
1397
+ }
1398
+ ghUrl = `${ghUrl}/${name}`;
1399
+ return ghUrl;
1400
+ }
1401
+ return undefined;
1402
+ }
1403
+
1404
+ /**
1405
+ * Method to split GitHub url into its parts
1406
+ * @param {String} repoUrl Repository url
1407
+ * @return {[String]} parts from url
1408
+ */
1409
+ export function getGithubUrlParts(repoUrl) {
1410
+ if (repoUrl.toLowerCase().endsWith(".git")) {
1411
+ repoUrl = repoUrl.slice(0, -4);
1412
+ }
1413
+ repoUrl.replace(/\/$/, "");
1414
+ return repoUrl.split("/");
1415
+ }
1416
+
1417
+ /**
1418
+ * Method to construct GitHub api url from repo metadata or one of multiple formats of repo URLs
1419
+ * @param {String} repoUrl Repository url
1420
+ * @param {Object} repoMetadata Object containing group and package name strings
1421
+ * @return {String|undefined} github api url (or undefined - if not a GitHub repo)
1422
+ */
1423
+ export function toGitHubApiUrl(repoUrl, repoMetadata) {
1424
+ if (repoMetadata) {
1425
+ return repoMetadataToGitHubApiUrl(repoMetadata);
1426
+ }
1427
+ const parts = getGithubUrlParts(repoUrl);
1428
+ if (parts.length < 5 || parts[2] !== "github.com") {
1429
+ return undefined; // Not a valid GitHub repo URL
1430
+ }
1431
+ return repoMetadataToGitHubApiUrl({
1432
+ group: parts[3],
1433
+ name: parts[4],
1434
+ });
1435
+ }
1436
+
1437
+ /**
1438
+ * Method to retrieve repo license by querying github api
1439
+ *
1440
+ * @param {String} repoUrl Repository url
1441
+ * @param {Object} repoMetadata Object containing group and package name strings
1442
+ * @return {Promise<String>} SPDX license id
1443
+ */
1444
+ /**
1445
+ * URLs for repository licence lookups, prefetched by {@link prefetchRepoLicenses}.
1446
+ *
1447
+ * Module state rather than a parameter because `getRepoLicense` is called from
1448
+ * eight places, several of them deep inside other metadata functions, and
1449
+ * threading a map through all of them would be a wide change for no gain. A
1450
+ * miss simply means the lookup issues its own request.
1451
+ */
1452
+ let repoLicensePrefetch = new Map();
1453
+
1454
+ /**
1455
+ * Prefetch the GitHub licence endpoint for a list of repository URLs.
1456
+ *
1457
+ * The single biggest remaining serialisation: `getRepoLicense` is called once
1458
+ * per component from npm, Maven, Swift and Go, each call a full round trip to
1459
+ * api.github.com. Batching them also makes the authenticated concurrency
1460
+ * allowance worth having — with `GITHUB_TOKEN` set, cdxrs runs eight of these at
1461
+ * a time instead of one.
1462
+ *
1463
+ * @param {Array<string|undefined>} repoUrls Repository URLs (duplicates and
1464
+ * empties are fine).
1465
+ * @returns {Promise<void>}
1466
+ */
1467
+ export async function prefetchRepoLicenses(repoUrls) {
1468
+ if (!prefetchEnabled() || !Array.isArray(repoUrls)) {
1469
+ return;
1470
+ }
1471
+ const requests = [];
1472
+ const seen = new Set();
1473
+ for (const repoUrl of repoUrls) {
1474
+ if (!repoUrl) {
1475
+ continue;
1476
+ }
1477
+ const apiUrl = toGitHubApiUrl(repoUrl, undefined);
1478
+ if (!apiUrl) {
1479
+ continue;
1480
+ }
1481
+ const licenseUrl = `${apiUrl}/license`;
1482
+ if (seen.has(licenseUrl)) {
1483
+ continue;
1484
+ }
1485
+ seen.add(licenseUrl);
1486
+ requests.push({
1487
+ url: licenseUrl,
1488
+ // The realm keys the cache per repository, never on the token itself, so
1489
+ // an authenticated response cannot be served to an anonymous lookup.
1490
+ authRealm: readEnvironmentVariable("GITHUB_TOKEN")
1491
+ ? `github-auth:${apiUrl}`
1492
+ : undefined,
1493
+ });
1494
+ }
1495
+ if (!requests.length) {
1496
+ return;
1497
+ }
1498
+ const fetched = await prefetchJson(requests);
1499
+ for (const [key, value] of fetched) {
1500
+ repoLicensePrefetch.set(key, value);
1501
+ }
1502
+ }
1503
+
1504
+ /**
1505
+ * Discard prefetched repository licences. Tests only.
1506
+ */
1507
+ export function resetRepoLicensePrefetch() {
1508
+ repoLicensePrefetch = new Map();
1509
+ }
1510
+
1511
+ /**
1512
+ * Fetch the license for a repository, primarily via the GitHub license API.
1513
+ *
1514
+ * Resolves the GitHub API license endpoint for the repository URL, deriving an
1515
+ * SPDX id from the response (or by scanning the license file content when the
1516
+ * API reports `NOASSERTION`). Honours any prefetched response.
1517
+ *
1518
+ * @param {string} repoUrl Repository URL.
1519
+ * @param {object} [repoMetadata] Optional repository metadata.
1520
+ * @returns {Promise<{url: string, id?: string, name?: string}|undefined>}
1521
+ * Resolved license object, or undefined when no license can be determined.
1522
+ */
1523
+ export async function getRepoLicense(repoUrl, repoMetadata) {
1524
+ if (!repoUrl) {
1525
+ return undefined;
1526
+ }
1527
+ const apiUrl = toGitHubApiUrl(repoUrl, repoMetadata);
1528
+ // Perform github lookups
1529
+ if (apiUrl && get_repo_license_errors < MAX_GET_REPO_LICENSE_ERRORS) {
1530
+ const licenseUrl = `${apiUrl}/license`;
1531
+ const headers = {};
1532
+ if (readEnvironmentVariable("GITHUB_TOKEN")) {
1533
+ headers["Authorization"] =
1534
+ `Bearer ${readEnvironmentVariable("GITHUB_TOKEN")}`;
1535
+ }
1536
+ try {
1537
+ const res =
1538
+ prefetchedResponse(repoLicensePrefetch, licenseUrl) ||
1539
+ (await cdxgenAgent.get(licenseUrl, {
1540
+ responseType: "json",
1541
+ headers: headers,
1542
+ }));
1543
+ if (res?.body) {
1544
+ const license = res.body.license;
1545
+ let licenseId = license.spdx_id;
1546
+ const licObj = {
1547
+ url: res.body.html_url,
1548
+ };
1549
+ if (license.spdx_id === "NOASSERTION") {
1550
+ if (res.body.content) {
1551
+ const content = Buffer.from(res.body.content, "base64").toString(
1552
+ "ascii",
1553
+ );
1554
+ licenseId = guessLicenseId(content);
1555
+ }
1556
+ // If content match fails attempt to find by name
1557
+ if (!licenseId && license.name.toLowerCase() !== "other") {
1558
+ licenseId = findLicenseId(license.name);
1559
+ licObj["name"] = license.name;
1560
+ }
1561
+ }
1562
+ licObj["id"] = licenseId;
1563
+ if (licObj["id"] || licObj["name"]) {
1564
+ return licObj;
1565
+ }
1566
+ }
1567
+ } catch (err) {
1568
+ if (err?.message) {
1569
+ if (
1570
+ err.message.includes("rate limit exceeded") &&
1571
+ !readEnvironmentVariable("GITHUB_TOKEN")
1572
+ ) {
1573
+ console.log(
1574
+ "Rate limit exceeded for REST API of github.com. " +
1575
+ "Please ensure GITHUB_TOKEN is set as environment variable. " +
1576
+ "See: https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api",
1577
+ );
1578
+ get_repo_license_errors++;
1579
+ } else if (!err.message.includes("404")) {
1580
+ get_repo_license_errors++;
1581
+ }
1582
+ }
1583
+ }
1584
+ }
1585
+ return undefined;
1586
+ }
1587
+
1588
+ /**
1589
+ * Documents from pkg.go.dev, prefetched by {@link prefetchGoPkgMetadata}.
1590
+ *
1591
+ * Module state for the same reason as {@link prefetchRepoLicenses}: the two
1592
+ * readers are called once per module from six Go parsers, and threading a map
1593
+ * through all of them would be a wide change for no gain. A miss means the
1594
+ * lookup issues its own request, exactly as before.
1595
+ */
1596
+ let goPkgPrefetch = new Map();
1597
+
1598
+ /**
1599
+ * Prefetch the pkg.go.dev pages for a list of Go modules.
1600
+ *
1601
+ * Go was the last ecosystem making one round trip per module with the URL known
1602
+ * up front: `getGoPkgLicense` and `getGoPkgVCSUrl` are both called from inside
1603
+ * the parsers' loops, so a module list of any size was fetched strictly one at a
1604
+ * time. Both pages are HTML, which is why these requests carry
1605
+ * `responseType: "text"` and so run on the JS pool rather than through cdxrs.
1606
+ *
1607
+ * Callers must pass exactly the modules their loop will look up. A superset
1608
+ * issues requests the serial path never made; a subset only loses some of the
1609
+ * batching.
1610
+ *
1611
+ * @param {Array<{group?: string, name?: string}>} modules Modules about to be
1612
+ * resolved. Duplicates and entries without a name are fine.
1613
+ * @returns {Promise<void>}
1614
+ */
1615
+ export async function prefetchGoPkgMetadata(modules) {
1616
+ if (!prefetchEnabled() || !Array.isArray(modules)) {
1617
+ return;
1618
+ }
1619
+ const requests = [];
1620
+ const seen = new Set();
1621
+ for (const module of modules) {
1622
+ if (!module?.name) {
1623
+ continue;
1624
+ }
1625
+ const pkgUrl = getGoPkgUrl(module);
1626
+ // The licence tab and the module page are distinct documents, read by
1627
+ // getGoPkgLicense and getGoPkgVCSUrl respectively. Both are requested only
1628
+ // when the corresponding flag is on, so the batch mirrors that.
1629
+ const wanted = [];
1630
+ if (shouldFetchLicense()) {
1631
+ wanted.push(`${pkgUrl}?tab=licenses`);
1632
+ }
1633
+ // getGoPkgVCSUrl derives github.com and gitlab.com URLs without asking
1634
+ // pkg.go.dev, so those modules must not be requested here either.
1635
+ if (shouldFetchVCS() && !isDirectlyResolvableGoVCS(module)) {
1636
+ wanted.push(pkgUrl);
1637
+ }
1638
+ for (const url of wanted) {
1639
+ if (seen.has(url) || metadata_cache[url]) {
1640
+ continue;
1641
+ }
1642
+ seen.add(url);
1643
+ requests.push({ url, responseType: "text" });
1644
+ }
1645
+ }
1646
+ if (!requests.length) {
1647
+ return;
1648
+ }
1649
+ const fetched = await prefetchJson(requests);
1650
+ for (const [key, value] of fetched) {
1651
+ goPkgPrefetch.set(key, value);
1652
+ }
1653
+ }
1654
+
1655
+ /**
1656
+ * Discard prefetched pkg.go.dev documents. Tests only.
1657
+ */
1658
+ export function resetGoPkgPrefetch() {
1659
+ goPkgPrefetch = new Map();
1660
+ }
1661
+
1662
+ /**
1663
+ * Method to get go pkg license from go.dev site.
1664
+ *
1665
+ * @param {Object} repoMetadata Repo metadata
1666
+ */
1667
+ export async function getGoPkgLicense(repoMetadata) {
1668
+ const group = repoMetadata.group;
1669
+ const pkgUrl = `${getGoPkgUrl(repoMetadata)}?tab=licenses`;
1670
+ // Check the metadata cache first
1671
+ if (metadata_cache[pkgUrl]) {
1672
+ return metadata_cache[pkgUrl];
1673
+ }
1674
+ try {
1675
+ const res =
1676
+ prefetchedResponse(goPkgPrefetch, pkgUrl) ||
1677
+ (await cdxgenAgent.get(pkgUrl));
1678
+ if (res?.body) {
1679
+ const licenses = extractLicenseText(res.body);
1680
+ const licenseIds = licenses.split(", ");
1681
+ const licList = [];
1682
+ for (const id of licenseIds) {
1683
+ if (id.trim().length) {
1684
+ const alicense = {};
1685
+ if (id.includes(" ")) {
1686
+ // go.dev separates a package's licences by newline. The first break
1687
+ // ends the licence name, every later one joins another licence.
1688
+ let breakCount = 0;
1689
+ alicense.name = id
1690
+ .trim()
1691
+ .replaceAll(/ {2}/g, "")
1692
+ .replaceAll("\n", () => (breakCount++ === 0 ? " " : " OR "));
1693
+ } else {
1694
+ alicense.id = id.trim();
1695
+ }
1696
+ alicense["url"] = pkgUrl;
1697
+ licList.push(alicense);
1698
+ }
1699
+ }
1700
+ metadata_cache[pkgUrl] = licList;
1701
+ return licList;
1702
+ }
1703
+ } catch (_err) {
1704
+ return undefined;
1705
+ }
1706
+ if (modulePathHost(group, "github.com")) {
1707
+ return await getRepoLicense(undefined, repoMetadata);
1708
+ }
1709
+ return undefined;
1710
+ }
1711
+
1712
+ /**
1713
+ * Method to get go pkg vcs url from go.dev site.
1714
+ *
1715
+ * @param {String} group Package group
1716
+ * @param {String} name Package name
1717
+ */
1718
+ export async function getGoPkgVCSUrl(group, name) {
1719
+ const fullName = getGoPkgFullName(group, name);
1720
+ if (isDirectlyResolvableGoVCS({ group, name })) {
1721
+ return `https://${fullName}`;
1722
+ }
1723
+ const pkgUrl = getGoPkgUrl({ fullName });
1724
+ if (metadata_cache[pkgUrl]) {
1725
+ return metadata_cache[pkgUrl];
1726
+ }
1727
+ try {
1728
+ const res =
1729
+ prefetchedResponse(goPkgPrefetch, pkgUrl) ||
1730
+ (await cdxgenAgent.get(pkgUrl));
1731
+ if (res?.body) {
1732
+ const vcs = extractRepoUrl(res.body);
1733
+ metadata_cache[pkgUrl] = vcs;
1734
+ return vcs;
1735
+ }
1736
+ } catch (_err) {
1737
+ return undefined;
1738
+ }
1739
+ return undefined;
1740
+ }
1741
+
1742
+ /**
1743
+ * Whether a module's repository URL follows from its path alone, needing no
1744
+ * pkg.go.dev lookup.
1745
+ *
1746
+ * @param {Object} pkgMetadata pkg metadata with `group` and/or `name`
1747
+ * @returns {boolean} True for github.com and gitlab.com hosted modules
1748
+ */
1749
+ function isDirectlyResolvableGoVCS(pkgMetadata) {
1750
+ const fullName =
1751
+ pkgMetadata.fullName ||
1752
+ getGoPkgFullName(pkgMetadata.group, pkgMetadata.name);
1753
+ return (
1754
+ modulePathHost(fullName, "github.com") ||
1755
+ modulePathHost(fullName, "gitlab.com")
1756
+ );
1757
+ }
1758
+
1759
+ /**
1760
+ * Method to get go pkg url (go.dev site).
1761
+ *
1762
+ * @param {Object} pkgMetadata pkg metadata
1763
+ */
1764
+ function getGoPkgUrl(pkgMetadata) {
1765
+ const pkgUrlPrefix =
1766
+ readEnvironmentVariable("GO_PKG_URL") || "https://pkg.go.dev/";
1767
+ const fullName =
1768
+ pkgMetadata.fullName ||
1769
+ getGoPkgFullName(pkgMetadata.group, pkgMetadata.name);
1770
+ return pkgUrlPrefix + fullName;
1771
+ }
1772
+
1773
+ /**
1774
+ * Method to get go pkg full name.
1775
+ *
1776
+ * @param {String} group Package group
1777
+ * @param {String} name Package name
1778
+ */
1779
+ function getGoPkgFullName(group, name) {
1780
+ return group && group !== "." && group !== name ? `${group}/${name}` : name;
1781
+ }
1782
+
1783
+ /**
1784
+ * Method to retrieve metadata for rust packages by querying crates
1785
+ *
1786
+ * @param {Array} pkgList Package list
1787
+ */
1788
+ export async function getCratesMetadata(pkgList) {
1789
+ const CRATES_URL =
1790
+ readEnvironmentVariable("RUST_CRATES_URL") ||
1791
+ "https://crates.io/api/v1/crates/";
1792
+ const cdepList = [];
1793
+ // Two URLs per crate — the crate document and its owners — fetched for the
1794
+ // whole list at once. The `workspace` guard below is mirrored here so the
1795
+ // batch does not request anything the loop would have skipped.
1796
+ const prefetched = await prefetchJson(
1797
+ 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
+ ])
1804
+ : [],
1805
+ );
1806
+ for (const p of pkgList) {
1807
+ try {
1808
+ if (!p?.name || !p?.version || p.version === "workspace") {
1809
+ cdepList.push(p);
1810
+ continue;
1811
+ }
1812
+ if (DEBUG_MODE) {
1813
+ console.log(`Querying crates.io for ${p.name}@${p.version}`);
1814
+ }
1815
+ const crateUrl = CRATES_URL + p.name;
1816
+ const res =
1817
+ prefetchedResponse(prefetched, crateUrl) ||
1818
+ (await cdxgenAgent.get(crateUrl, {
1819
+ responseType: "json",
1820
+ }));
1821
+ let ownersRes;
1822
+ try {
1823
+ const ownersUrl = `${crateUrl}/owners`;
1824
+ ownersRes =
1825
+ prefetchedResponse(prefetched, ownersUrl) ||
1826
+ (await cdxgenAgent.get(ownersUrl, {
1827
+ responseType: "json",
1828
+ }));
1829
+ } catch (_err) {
1830
+ ownersRes = undefined;
1831
+ }
1832
+ let versionToUse = res?.body?.versions[0];
1833
+ if (p.version) {
1834
+ for (const aversion of res.body.versions) {
1835
+ if (aversion.num === p.version) {
1836
+ versionToUse = aversion;
1837
+ break;
1838
+ }
1839
+ }
1840
+ }
1841
+ const body = res.body.crate;
1842
+ p.description = body.description;
1843
+ if (versionToUse?.license) {
1844
+ p.license = versionToUse.license;
1845
+ }
1846
+ if (body.repository) {
1847
+ p.repository = { url: body.repository };
1848
+ }
1849
+ if (body.homepage && body.homepage !== body.repository) {
1850
+ p.homepage = { url: body.homepage };
1851
+ }
1852
+ // Use the latest version if none specified
1853
+ if (!p.version) {
1854
+ p.version = body.newest_version;
1855
+ }
1856
+ if (!p._integrity && versionToUse.checksum) {
1857
+ p._integrity = normalizeCargoIntegrity(versionToUse.checksum);
1858
+ }
1859
+ if (!p.properties) {
1860
+ p.properties = [];
1861
+ }
1862
+ p.properties.push({
1863
+ name: "cdx:cargo:crate_id",
1864
+ value: `${versionToUse.id}`,
1865
+ });
1866
+ if (versionToUse.rust_version) {
1867
+ p.properties.push({
1868
+ name: "cdx:cargo:rust_version",
1869
+ value: `${versionToUse.rust_version}`,
1870
+ });
1871
+ }
1872
+ p.properties.push({
1873
+ name: "cdx:cargo:latest_version",
1874
+ value: body.newest_version,
1875
+ });
1876
+ p.distribution = { url: `https://crates.io${versionToUse.dl_path}` };
1877
+ if (versionToUse.features && Object.keys(versionToUse.features).length) {
1878
+ p.properties.push({
1879
+ name: "cdx:cargo:features",
1880
+ value: JSON.stringify(versionToUse.features),
1881
+ });
1882
+ }
1883
+ p.properties = p.properties.concat(
1884
+ collectCargoRegistryProvenanceProperties(
1885
+ res?.body,
1886
+ versionToUse?.num || p.version,
1887
+ ownersRes?.body,
1888
+ ),
1889
+ );
1890
+ cdepList.push(p);
1891
+ } catch (_err) {
1892
+ cdepList.push(p);
1893
+ }
1894
+ }
1895
+ return cdepList;
1896
+ }
1897
+
1898
+ /**
1899
+ * Method to retrieve metadata for dart packages by querying pub.dev
1900
+ *
1901
+ * @param {Array} pkgList Package list
1902
+ */
1903
+ export async function getDartMetadata(pkgList) {
1904
+ const PUB_DEV_URL =
1905
+ readEnvironmentVariable("PUB_DEV_URL") || "https://pub.dev";
1906
+ const PUB_LICENSE_REGEX = /^license:/i;
1907
+ const OPTIONS = {
1908
+ responseType: "json",
1909
+ headers: {
1910
+ Accept: PUB_ACCEPT,
1911
+ },
1912
+ };
1913
+
1914
+ const cdepList = [];
1915
+ // pub.dev needs the package document and its score document; both carry the
1916
+ // vendor Accept header, which is part of the cache key on the Rust side.
1917
+ const prefetched = await prefetchJson(
1918
+ prefetchEnabled()
1919
+ ? pkgList.flatMap((p) => {
1920
+ const base = `${PUB_DEV_URL}/api/packages/${p.name}/versions/${p.version}`;
1921
+ return [
1922
+ { url: base, accept: PUB_ACCEPT },
1923
+ { url: `${base}/score`, accept: PUB_ACCEPT },
1924
+ ];
1925
+ })
1926
+ : [],
1927
+ );
1928
+ for (const p of pkgList) {
1929
+ try {
1930
+ if (DEBUG_MODE) {
1931
+ console.log(`Querying ${PUB_DEV_URL} for ${p.name}`);
1932
+ }
1933
+ const PUB_PACKAGE_URL = `${PUB_DEV_URL}/api/packages/${p.name}/versions/${p.version}`;
1934
+ const PUB_PACKAGE_SCORE_URL = `${PUB_PACKAGE_URL}/score`;
1935
+ const res =
1936
+ prefetchedResponse(prefetched, PUB_PACKAGE_URL) ||
1937
+ (await cdxgenAgent.get(PUB_PACKAGE_URL, OPTIONS));
1938
+ if (res?.body) {
1939
+ const pubspec = res.body.pubspec;
1940
+ p.description = pubspec.description;
1941
+ if (pubspec.repository) {
1942
+ p.repository = { url: pubspec.repository };
1943
+ }
1944
+ if (pubspec.homepage) {
1945
+ p.homepage = { url: pubspec.homepage };
1946
+ }
1947
+ const score =
1948
+ prefetchedResponse(prefetched, PUB_PACKAGE_SCORE_URL) ||
1949
+ (await cdxgenAgent.get(PUB_PACKAGE_SCORE_URL, OPTIONS));
1950
+ if (score?.body) {
1951
+ const tags = score.body.tags;
1952
+ const license = tags.find((tag) => PUB_LICENSE_REGEX.test(tag));
1953
+ if (license) {
1954
+ p.license = spdxLicenses.find(
1955
+ (spdxLicense) =>
1956
+ spdxLicense.toLowerCase() ===
1957
+ license.replace(PUB_LICENSE_REGEX, "").toLowerCase(),
1958
+ );
1959
+ }
1960
+ }
1961
+ cdepList.push(p);
1962
+ }
1963
+ } catch (_err) {
1964
+ cdepList.push(p);
1965
+ }
1966
+ }
1967
+ return cdepList;
1968
+ }
1969
+
1970
+ /**
1971
+ * Normalize a Cargo checksum/integrity string into canonical hex-prefixed form.
1972
+ *
1973
+ * Accepts an existing `sha256-`/`sha384-` prefixed digest (validating the hex
1974
+ * length) or a bare hex digest, returning `<algo>-<digest>`. Returns undefined
1975
+ * for non-string or unrecognized inputs.
1976
+ *
1977
+ * @param {string} integrity Raw checksum string from a Cargo.lock or registry.
1978
+ * @returns {string|undefined} Canonical `algo-digest` integrity, or undefined.
1979
+ */
1980
+ export function normalizeCargoIntegrity(integrity) {
1981
+ if (typeof integrity !== "string") {
1982
+ return undefined;
1983
+ }
1984
+ const normalizedIntegrity = integrity.trim().toLowerCase();
1985
+ const prefixedMatch = /^(sha256|sha384)-(?<digest>[a-f0-9]+)$/i.exec(
1986
+ normalizedIntegrity,
1987
+ );
1988
+ if (prefixedMatch?.groups?.digest) {
1989
+ const algorithm = prefixedMatch[1].toLowerCase();
1990
+ const digest = prefixedMatch.groups.digest;
1991
+ const expectedDigestLength = algorithm === "sha384" ? 96 : 64;
1992
+ if (digest.length === expectedDigestLength) {
1993
+ return `${algorithm}-${digest}`;
1994
+ }
1995
+ return undefined;
1996
+ }
1997
+ if (!/^[a-f0-9]+$/i.test(normalizedIntegrity)) {
1998
+ return undefined;
1999
+ }
2000
+ if (normalizedIntegrity.length === 64) {
2001
+ return `sha256-${normalizedIntegrity}`;
2002
+ }
2003
+ if (normalizedIntegrity.length === 96) {
2004
+ return `sha384-${normalizedIntegrity}`;
2005
+ }
2006
+ return undefined;
2007
+ }
2008
+
2009
+ /**
2010
+ * Method to extract a war or ear file
2011
+ *
2012
+ * @param {string} jarFile Path to jar file
2013
+ * @param {string} tempDir Temporary directory to use for extraction
2014
+ * @param {object} jarNSMapping Jar class names mapping object
2015
+ *
2016
+ * @return pkgList Package list
2017
+ */
2018
+ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
2019
+ const pkgList = [];
2020
+ let jarFiles = [];
2021
+ // Archives whose Maven coordinates could not be determined. They are still
2022
+ // reported, as file components with zero-confidence identity evidence.
2023
+ const unresolvedJars = [];
2024
+ const fname = basename(jarFile);
2025
+ let pomname;
2026
+ // If there is a pom file in the same directory, try to use it
2027
+ const manifestname = join(dirname(jarFile), "META-INF", "MANIFEST.MF");
2028
+ // Issue 439: Current implementation checks for existance of a .pom file, but .pom file is not used.
2029
+ // Instead code expects to find META-INF/MANIFEST.MF in the same folder as a .jar file.
2030
+ // For now check for presence of both .pom and MANIFEST.MF files.
2031
+ if (jarFile.endsWith(".jar")) {
2032
+ pomname = jarFile.replace(".jar", ".pom");
2033
+ }
2034
+ if (
2035
+ pomname &&
2036
+ safeExistsSync(pomname) &&
2037
+ manifestname &&
2038
+ safeExistsSync(manifestname)
2039
+ ) {
2040
+ tempDir = dirname(jarFile);
2041
+ } else if (
2042
+ !safeExistsSync(join(tempDir, fname)) &&
2043
+ safeExistsSync(jarFile) &&
2044
+ lstatSync(jarFile).isFile()
2045
+ ) {
2046
+ // Only copy if the file doesn't exist
2047
+ safeCopyFileSync(jarFile, join(tempDir, fname), constants.COPYFILE_FICLONE);
2048
+ }
2049
+ const env = {
2050
+ ...process.env,
2051
+ };
2052
+ // jar command usually would not be available in the PATH for windows
2053
+ if (isWin && env.JAVA_HOME) {
2054
+ env.PATH = `${env.PATH || env.Path}${_delimiter}${join(
2055
+ env.JAVA_HOME,
2056
+ "bin",
2057
+ )}`;
2058
+ }
2059
+ if (
2060
+ jarFile.endsWith(".war") ||
2061
+ jarFile.endsWith(".hpi") ||
2062
+ jarFile.endsWith(".jar")
2063
+ ) {
2064
+ if (safeExistsSync(join(tempDir, fname))) {
2065
+ try {
2066
+ const zip = new StreamZip.async({ file: join(tempDir, fname) });
2067
+ const extracted = await safeExtractArchive(
2068
+ join(tempDir, fname),
2069
+ tempDir,
2070
+ async () => {
2071
+ await zip.extract(null, tempDir);
2072
+ },
2073
+ );
2074
+ await zip.close();
2075
+ if (!extracted) {
2076
+ return pkgList;
2077
+ }
2078
+ } catch (e) {
2079
+ console.log("Unable to extract %s. Skipping.", join(tempDir, fname), e);
2080
+ return pkgList;
2081
+ }
2082
+ }
2083
+ jarFiles = getAllFiles(join(tempDir, "WEB-INF", "lib"), "**/*.jar");
2084
+ if (jarFile.endsWith(".hpi")) {
2085
+ jarFiles.push(jarFile);
2086
+ }
2087
+ // Some jar files could also have more jar files inside BOOT-INF directory
2088
+ const jarFiles2 = getAllFiles(join(tempDir, "BOOT-INF", "lib"), "**/*.jar");
2089
+ if (jarFiles && jarFiles2.length) {
2090
+ jarFiles = jarFiles.concat(jarFiles2);
2091
+ }
2092
+ // Fallback. If our jar file didn't include any jar
2093
+ if (jarFile.endsWith(".jar") && !jarFiles.length) {
2094
+ jarFiles = [join(tempDir, fname)];
2095
+ }
2096
+ } else {
2097
+ jarFiles = [join(tempDir, fname)];
2098
+ }
2099
+ if (jarFiles?.length) {
2100
+ for (const jf of jarFiles) {
2101
+ // If the jar file doesn't exist at the point of use, skip it
2102
+ if (!safeExistsSync(jf)) {
2103
+ if (DEBUG_MODE) {
2104
+ console.log("%s %s is not a readable file.", jf, jarFile);
2105
+ }
2106
+ continue;
2107
+ }
2108
+ pomname = jf.replace(".jar", ".pom");
2109
+ const jarname = basename(jf);
2110
+ // Ignore test jars
2111
+ if (
2112
+ jarname.endsWith("-tests.jar") ||
2113
+ jarname.endsWith("-test-sources.jar")
2114
+ ) {
2115
+ if (DEBUG_MODE) {
2116
+ console.log(`Skipping tests jar ${jarname}`);
2117
+ }
2118
+ continue;
2119
+ }
2120
+ const manifestDir = join(tempDir, "META-INF");
2121
+ const manifestFile = join(manifestDir, "MANIFEST.MF");
2122
+ const mavenDir = join(manifestDir, "maven");
2123
+ let jarResult = {
2124
+ status: 1,
2125
+ };
2126
+ if (safeExistsSync(pomname)) {
2127
+ jarResult = { status: 0 };
2128
+ } else {
2129
+ // Unzip natively
2130
+ try {
2131
+ const zip = new StreamZip.async({ file: jf });
2132
+ const extracted = await safeExtractArchive(jf, tempDir, async () => {
2133
+ await zip.extract(null, tempDir);
2134
+ });
2135
+ await zip.close();
2136
+ jarResult = { status: extracted ? 0 : 1 };
2137
+ } catch (_e) {
2138
+ if (DEBUG_MODE) {
2139
+ console.log(`Unable to extract ${jf}. Skipping.`);
2140
+ }
2141
+ jarResult = { status: 1 };
2142
+ }
2143
+ }
2144
+ if (jarResult.status === 0) {
2145
+ // When maven descriptor is available take group, name and version from pom.properties
2146
+ // META-INF/maven/${groupId}/${artifactId}/pom.properties
2147
+ // see https://maven.apache.org/shared/maven-archiver/index.html
2148
+ const pomProperties = getPomPropertiesFromMavenDir(mavenDir);
2149
+ let group = pomProperties["groupId"];
2150
+ let name = pomProperties["artifactId"];
2151
+ let version = pomProperties["version"];
2152
+ let confidence = 0.5;
2153
+ let technique = "manifest-analysis";
2154
+ if (
2155
+ (!group || !name || !version) &&
2156
+ SEARCH_MAVEN_ORG &&
2157
+ search_maven_org_errors < MAX_SEARCH_MAVEN_ORG_ERRORS
2158
+ ) {
2159
+ try {
2160
+ const sha = await checksumFile("sha1", jf);
2161
+ 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
+ });
2173
+ const data = res?.body ? res.body["response"] : undefined;
2174
+ if (data && data["numFound"] === 1) {
2175
+ const jarInfo = data["docs"][0];
2176
+ group = jarInfo["g"];
2177
+ name = jarInfo["a"];
2178
+ version = jarInfo["v"];
2179
+ technique = "hash-comparison";
2180
+ }
2181
+ } catch (err) {
2182
+ if (err?.message && !err.message.includes("404")) {
2183
+ if (
2184
+ err.message.includes("Timeout") ||
2185
+ err.message.includes("429")
2186
+ ) {
2187
+ console.log(
2188
+ "Maven search appears to be unavailable. Search will be skipped for all remaining packages.",
2189
+ );
2190
+ }
2191
+ search_maven_org_errors++;
2192
+ }
2193
+ }
2194
+ }
2195
+ let jarMetadata;
2196
+ if ((!group || !name || !version) && safeExistsSync(manifestFile)) {
2197
+ confidence = 0.3;
2198
+ jarMetadata = parseJarManifest(
2199
+ readFileSync(manifestFile, {
2200
+ encoding: "utf-8",
2201
+ }),
2202
+ );
2203
+ if (jarMetadata["Bundle-SymbolicName"]) {
2204
+ jarMetadata["Bundle-SymbolicName"] = jarMetadata[
2205
+ "Bundle-SymbolicName"
2206
+ ]
2207
+ .split(";")[0]
2208
+ .trim();
2209
+ }
2210
+ group = group || inferJarGroupFromManifest(jarMetadata);
2211
+ version =
2212
+ version ||
2213
+ jarMetadata["Bundle-Version"] ||
2214
+ jarMetadata["Implementation-Version"] ||
2215
+ jarMetadata["Specification-Version"];
2216
+ if (version?.includes(" ")) {
2217
+ version = version.split(" ")[0];
2218
+ }
2219
+ // Prefer jar filename to construct name and version
2220
+ const tmpA = jarname.split("-");
2221
+ let fileVersionCandidate;
2222
+ let nameCandidate;
2223
+ if (tmpA && tmpA.length > 1) {
2224
+ const lastPart = tmpA[tmpA.length - 1];
2225
+ // Bug #768. Check if we have any number before simplifying the name.
2226
+ if (/\d/.test(lastPart)) {
2227
+ fileVersionCandidate = lastPart.replace(".jar", "");
2228
+ nameCandidate = jarname.replace(`-${lastPart}`, "") || "";
2229
+ if (nameCandidate.includes(".")) {
2230
+ const gnArr = nameCandidate.split(".");
2231
+ if (gnArr?.length === 2 && gnArr[0] === gnArr[1]) {
2232
+ nameCandidate = gnArr[1];
2233
+ }
2234
+ }
2235
+ }
2236
+ }
2237
+ if (
2238
+ fileVersionCandidate &&
2239
+ (!version ||
2240
+ version === "" ||
2241
+ (version.includes(fileVersionCandidate) &&
2242
+ version.length > fileVersionCandidate.length))
2243
+ ) {
2244
+ version = fileVersionCandidate;
2245
+ confidence = 0.3;
2246
+ technique = "filename";
2247
+ }
2248
+ if (!name || name === "") {
2249
+ name = nameCandidate;
2250
+ }
2251
+ if (
2252
+ !name?.length &&
2253
+ jarMetadata["Bundle-Name"] &&
2254
+ !jarMetadata["Bundle-Name"].includes(" ")
2255
+ ) {
2256
+ name = jarMetadata["Bundle-Name"];
2257
+ } else if (
2258
+ !name?.length &&
2259
+ jarMetadata["Implementation-Title"] &&
2260
+ !jarMetadata["Implementation-Title"].includes(" ")
2261
+ ) {
2262
+ name = jarMetadata["Implementation-Title"];
2263
+ }
2264
+ // Sometimes the group might already contain the name
2265
+ // Eg: group: org.checkerframework.checker.qual name: checker-qual
2266
+ group = trimJarGroupSuffix(group, name);
2267
+ // Patch the group string
2268
+ if (vendorAliases[name]) {
2269
+ group = vendorAliases[name];
2270
+ } else {
2271
+ for (const aprefix in vendorAliases) {
2272
+ if (name?.startsWith(aprefix) || name?.endsWith(`.${aprefix}`)) {
2273
+ group = vendorAliases[aprefix];
2274
+ if (name?.startsWith(`${group}.`)) {
2275
+ name = name.replace(`${group}.`, "");
2276
+ }
2277
+ break;
2278
+ }
2279
+ }
2280
+ }
2281
+ // if group is empty use name as group
2282
+ group = group === "." ? name : group || name;
2283
+ }
2284
+ if (name) {
2285
+ if (!version) {
2286
+ confidence = 0;
2287
+ }
2288
+ const properties = [
2289
+ {
2290
+ name: "internal:SrcFile",
2291
+ value: jf,
2292
+ },
2293
+ ];
2294
+ const purl = build({
2295
+ type: "maven",
2296
+ namespace: group || null,
2297
+ name: name,
2298
+ version: version || null,
2299
+ qualifiers: { type: "jar" } || null,
2300
+ });
2301
+ let namespaceValues;
2302
+ let namespaceList;
2303
+ if (jarNSMapping?.[purl]?.namespaces) {
2304
+ namespaceList = jarNSMapping[purl].namespaces;
2305
+ namespaceValues = namespaceList.join("\n");
2306
+ properties.push({
2307
+ name: "internal:Namespaces",
2308
+ value: namespaceValues,
2309
+ });
2310
+ } else {
2311
+ const tmpJarNSMapping = await collectJarNS(jf);
2312
+ if (tmpJarNSMapping?.[jf]?.namespaces?.length) {
2313
+ namespaceList = tmpJarNSMapping[jf].namespaces;
2314
+ namespaceValues = namespaceList.join("\n");
2315
+ properties.push({
2316
+ name: "internal:Namespaces",
2317
+ value: namespaceValues,
2318
+ });
2319
+ }
2320
+ }
2321
+ // Are there any shaded classes
2322
+ if (
2323
+ namespaceValues?.includes(".shaded.") ||
2324
+ namespaceValues?.includes(".thirdparty.com.")
2325
+ ) {
2326
+ properties.push({
2327
+ name: "cdx:maven:shaded",
2328
+ value: "true",
2329
+ });
2330
+ confidence = 0;
2331
+ const unshadedNS = new Set();
2332
+ for (const ans of namespaceList) {
2333
+ let tmpns;
2334
+ if (ans.includes(".shaded.")) {
2335
+ tmpns = ans.split(".shaded.").pop();
2336
+ } else if (ans.includes(".thirdparty.")) {
2337
+ tmpns = ans.split(".thirdparty.").pop();
2338
+ }
2339
+ if (tmpns?.search("[.]") > 3) {
2340
+ unshadedNS.add(tmpns.split("$")[0]);
2341
+ }
2342
+ }
2343
+ if (unshadedNS.size) {
2344
+ properties.push({
2345
+ name: "cdx:maven:unshadedNamespaces",
2346
+ value: Array.from(unshadedNS).join("\n"),
2347
+ });
2348
+ }
2349
+ }
2350
+ const apkg = {
2351
+ group: group ? encodeForPurl(group) : "",
2352
+ name: name ? encodeForPurl(name) : "",
2353
+ version,
2354
+ purl,
2355
+ evidence: {
2356
+ identity: {
2357
+ field: "purl",
2358
+ confidence: confidence,
2359
+ methods: [
2360
+ {
2361
+ technique: technique,
2362
+ confidence: confidence,
2363
+ value: jarname,
2364
+ },
2365
+ ],
2366
+ },
2367
+ },
2368
+ properties,
2369
+ };
2370
+ pkgList.push(apkg);
2371
+ } else {
2372
+ unresolvedJars.push(jf);
2373
+ if (DEBUG_MODE) {
2374
+ console.log("Ignored jar %s", jarname, name, version);
2375
+ }
2376
+ }
2377
+ }
2378
+ try {
2379
+ if (safeExistsSync(join(tempDir, "META-INF"))) {
2380
+ // Clean up META-INF
2381
+ safeRmSync(join(tempDir, "META-INF"), {
2382
+ recursive: true,
2383
+ force: true,
2384
+ });
2385
+ }
2386
+ } catch (_err) {
2387
+ // ignore cleanup errors
2388
+ }
2389
+ } // for
2390
+ } // if
2391
+ if (unresolvedJars.length) {
2392
+ // An archive without readable Maven coordinates is still a real file in the
2393
+ // artifact. Reporting it as a file component keeps it in the inventory,
2394
+ // while zero-confidence identity evidence records that only its name and
2395
+ // hashes are known — the same treatment unpackaged executables and shared
2396
+ // libraries get.
2397
+ pkgList.push(
2398
+ ...(await unresolvedJarComponents(unresolvedJars, jarFile, tempDir)),
2399
+ );
2400
+ thoughtLog(
2401
+ `I couldn't read Maven coordinates for ${unresolvedJars.length} archive(s) under ${basename(jarFile)}, so I recorded them as files with their hashes instead of dropping them.`,
2402
+ );
2403
+ }
2404
+ return pkgList;
2405
+ }
2406
+
2407
+ /**
2408
+ * Build file components for archives whose Maven coordinates could not be
2409
+ * determined.
2410
+ *
2411
+ * @param {string[]} unresolvedJars Paths of the archives on disk
2412
+ * @param {string} jarFile The archive originally passed for extraction
2413
+ * @param {string} tempDir Directory the archive was expanded into
2414
+ * @returns {Promise<object[]>} File components with zero-confidence identity
2415
+ */
2416
+ async function unresolvedJarComponents(unresolvedJars, jarFile, tempDir) {
2417
+ const hashResults = await mapWithConcurrency(unresolvedJars, (jf) =>
2418
+ multiChecksumFile(["md5", "sha1", "sha256"], jf)
2419
+ .then((hashValues) => [
2420
+ { alg: "MD5", content: hashValues.md5 },
2421
+ { alg: "SHA-1", content: hashValues.sha1 },
2422
+ { alg: "SHA-256", content: hashValues.sha256 },
2423
+ ])
2424
+ .catch(() => undefined),
2425
+ );
2426
+ const components = [];
2427
+ for (let i = 0; i < unresolvedJars.length; i++) {
2428
+ const jf = unresolvedJars[i];
2429
+ const name = basename(jf);
2430
+ // A nested archive is identified by its entry path within the outer one,
2431
+ // which is what a reader can actually go and look at. The temp directory
2432
+ // the archive happened to be expanded into carries no information.
2433
+ const relativeEntry = relative(tempDir, jf);
2434
+ const srcFile =
2435
+ relativeEntry && !relativeEntry.startsWith("..") && relativeEntry !== name
2436
+ ? `${jarFile}!/${relativeEntry.split(_sep).join("/")}`
2437
+ : jarFile;
2438
+ // The purl carries the name only. The archive is reached through a
2439
+ // temporary extraction directory whose name changes every run, and postgen
2440
+ // rewrites `internal:SrcFile` and `concludedValue` to scan-relative paths
2441
+ // but never a purl, since purls are join keys. Putting the location in the
2442
+ // purl would make it differ between two runs over the same input.
2443
+ const purl = genericPurl(name);
2444
+ components.push({
2445
+ name,
2446
+ type: "file",
2447
+ ...(purl ? { purl, "bom-ref": purl } : {}),
2448
+ hashes: hashResults[i],
2449
+ properties: [
2450
+ { name: "internal:SrcFile", value: srcFile },
2451
+ { name: "internal:is_file", value: "true" },
2452
+ ],
2453
+ evidence: {
2454
+ identity: [
2455
+ {
2456
+ field: "purl",
2457
+ confidence: 0,
2458
+ methods: [
2459
+ {
2460
+ technique: "filename",
2461
+ confidence: 0,
2462
+ value: srcFile,
2463
+ },
2464
+ ],
2465
+ concludedValue: srcFile,
2466
+ },
2467
+ ],
2468
+ },
2469
+ });
2470
+ }
2471
+ return components;
2472
+ }
2473
+
2474
+ async function getNugetUrl() {
2475
+ const req = "https://api.nuget.org/v3/index.json";
2476
+ const res = await cdxgenAgent.get(req, {
2477
+ responseType: "json",
2478
+ });
2479
+ const urls = res.body.resources;
2480
+ for (const resource of urls) {
2481
+ if (resource["@type"] === "RegistrationsBaseUrl/3.6.0") {
2482
+ return resource["@id"];
2483
+ }
2484
+ }
2485
+ return "https://api.nuget.org/v3/registration3/";
2486
+ }
2487
+
2488
+ /**
2489
+ * Prefetched NuGet registration index documents, populated by
2490
+ * `getNugetMetadata`'s batch round. The index is the first request
2491
+ * `queryNuget` makes for each package; batching it removes the serial round
2492
+ * trip per package. The follow-up request to a specific registration page
2493
+ * (when the index does not inline items) depends on the index response and
2494
+ * stays serial.
2495
+ */
2496
+ const prefetchedNugetIndex = new Map();
2497
+
2498
+ async function queryNuget(p, NUGET_URL) {
2499
+ function setLatestVersion(upper) {
2500
+ // Handle special case for versions with more than 3 parts
2501
+ if (upper.split(".").length > 3) {
2502
+ const tmpVersionArray = upper.split("-")[0].split(".");
2503
+ // Compromise for versions such as 1.2.3.0-alpha
2504
+ // How to find latest proper release version?
2505
+ if (
2506
+ upper.split("-").length > 1 &&
2507
+ Number(tmpVersionArray.slice(-1)) === 0
2508
+ ) {
2509
+ return upper;
2510
+ }
2511
+ if (upper.split("-").length > 1) {
2512
+ tmpVersionArray[tmpVersionArray.length - 1] = (
2513
+ Number(tmpVersionArray.slice(-1)) - 1
2514
+ ).toString();
2515
+ }
2516
+ return tmpVersionArray.join(".");
2517
+ }
2518
+ const tmpVersion = parse(upper);
2519
+ let version = `${tmpVersion.major}.${tmpVersion.minor}.${tmpVersion.patch}`;
2520
+ if (compare(version, upper) === 1) {
2521
+ if (tmpVersion.patch > 0) {
2522
+ version = `${tmpVersion.major}.${tmpVersion.minor}.${(tmpVersion.patch - 1).toString()}`;
2523
+ }
2524
+ }
2525
+ return version;
2526
+ }
2527
+
2528
+ // Coerce only when missing patch/minor version
2529
+ function coerceUp(version) {
2530
+ return version.split(".").length < 3
2531
+ ? coerce(version, { loose: true }).version
2532
+ : version;
2533
+ }
2534
+
2535
+ if (DEBUG_MODE) {
2536
+ console.log(`Querying nuget for ${p.name}`);
2537
+ }
2538
+ const np = JSON.parse(JSON.stringify(p));
2539
+ const body = [];
2540
+ const newBody = [];
2541
+ const indexUrl = `${NUGET_URL + np.name.toLowerCase()}/index.json`;
2542
+ let res =
2543
+ prefetchedResponse(prefetchedNugetIndex, indexUrl) ||
2544
+ (await cdxgenAgent.get(indexUrl, { responseType: "json" }));
2545
+ const items = res.body.items;
2546
+ if (!items?.[0]) {
2547
+ return [np, newBody, body];
2548
+ }
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
+ }
2554
+ 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
+ }
2576
+ }
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
+ }
2603
+ }
2604
+ }
2605
+ }
2606
+ }
2607
+ return [np, newBody];
2608
+ }
2609
+
2610
+ /**
2611
+ * Method to retrieve metadata for nuget packages
2612
+ *
2613
+ * @param {Array} pkgList Package list
2614
+ * @param {Array} dependencies Dependencies
2615
+ */
2616
+ export async function getNugetMetadata(pkgList, dependencies = undefined) {
2617
+ const NUGET_URL =
2618
+ readEnvironmentVariable("NUGET_URL") || (await getNugetUrl());
2619
+ const cdepList = [];
2620
+ const depRepList = {};
2621
+ // Batch the per-package registration index requests. queryNuget's first
2622
+ // request for each package is the index; the follow-up to a specific
2623
+ // registration page depends on the index response and stays serial. Only
2624
+ // packages that would actually be queried (not in metadata_cache, no prior
2625
+ // error) are prefetched, matching the loop's skip conditions.
2626
+ const batchUrls = [];
2627
+ const seenNugetUrls = new Set();
2628
+ for (const p of pkgList) {
2629
+ // A cached entry — a body or a recorded error — means the loop will not
2630
+ // reach the network for this package, so the batch must not either.
2631
+ if (metadata_cache[`${p.name}|${p.version}`]) {
2632
+ continue;
2633
+ }
2634
+ const indexUrl = `${NUGET_URL + p.name.toLowerCase()}/index.json`;
2635
+ if (!seenNugetUrls.has(indexUrl)) {
2636
+ seenNugetUrls.add(indexUrl);
2637
+ batchUrls.push({ url: indexUrl });
2638
+ }
2639
+ }
2640
+ const ownIndexUrls = [];
2641
+ if (batchUrls.length) {
2642
+ const fetched = await prefetchJson(batchUrls);
2643
+ for (const [key, value] of fetched) {
2644
+ prefetchedNugetIndex.set(key, value);
2645
+ ownIndexUrls.push(key);
2646
+ }
2647
+ }
2648
+ try {
2649
+ for (const p of pkgList) {
2650
+ let cacheKey;
2651
+ try {
2652
+ // If there is a version, we can safely use the cache to retrieve the license
2653
+ // See: https://github.com/cdxgen/cdxgen/issues/352
2654
+ cacheKey = `${p.name}|${p.version}`;
2655
+ let body = metadata_cache[cacheKey];
2656
+
2657
+ if (body?.error) {
2658
+ cdepList.push(p);
2659
+ continue;
2660
+ }
2661
+ if (!body) {
2662
+ let newBody = {};
2663
+ let np = {};
2664
+ [np, newBody] = await queryNuget(p, NUGET_URL);
2665
+ if (p.version !== np.version) {
2666
+ 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"];
2676
+ p.version = np.version;
2677
+ }
2678
+ if (newBody && newBody.length > 0) {
2679
+ body = newBody[0];
2680
+ }
2681
+ if (body) {
2682
+ 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;
2686
+ }
2687
+ p.description = body.catalogEntry.description;
2688
+ if (body.catalogEntry.authors) {
2689
+ p.author = body.catalogEntry.authors.trim();
2690
+ }
2691
+ if (
2692
+ body.catalogEntry.licenseExpression &&
2693
+ body.catalogEntry.licenseExpression !== ""
2694
+ ) {
2695
+ p.license = findLicenseId(body.catalogEntry.licenseExpression);
2696
+ } else if (body.catalogEntry.licenseUrl) {
2697
+ p.license = findLicenseId(body.catalogEntry.licenseUrl);
2698
+ if (typeof p.license === "string" && isGitHubUrl(p.license)) {
2699
+ p.license =
2700
+ (await getRepoLicense(p.license, undefined)) || p.license;
2701
+ }
2702
+ }
2703
+ // Capture the tags
2704
+ if (
2705
+ body.catalogEntry?.tags?.length &&
2706
+ Array.isArray(body.catalogEntry.tags)
2707
+ ) {
2708
+ p.tags = body.catalogEntry.tags.map((t) =>
2709
+ t.toLowerCase().replaceAll(" ", "-"),
2710
+ );
2711
+ }
2712
+ if (body.catalogEntry.projectUrl) {
2713
+ p.repository = { url: body.catalogEntry.projectUrl };
2714
+ p.homepage = {
2715
+ url: `https://www.nuget.org/packages/${p.name}/${p.version}/`,
2716
+ };
2717
+ if (
2718
+ (!p.license || typeof p.license === "string") &&
2719
+ typeof p.repository.url === "string" &&
2720
+ isGitHubUrl(p.repository.url)
2721
+ ) {
2722
+ // license couldn't be properly identified and is still a url,
2723
+ // therefore trying to resolve license via repository
2724
+ p.license =
2725
+ (await getRepoLicense(p.repository.url, undefined)) ||
2726
+ p.license;
2727
+ }
2728
+ }
2729
+ cdepList.push(p);
2730
+ }
2731
+ }
2732
+ } catch (err) {
2733
+ if (cacheKey) {
2734
+ metadata_cache[cacheKey] = { error: err.code };
2735
+ }
2736
+ cdepList.push(p);
2737
+ }
2738
+ }
2739
+ } finally {
2740
+ // Each index document is read once, by the loop above. Holding them past
2741
+ // that only grows the process, which matters in server mode where one
2742
+ // process serves many scans. Only the documents this call fetched are
2743
+ // dropped, so a scan running alongside this one keeps its own.
2744
+ for (const indexUrl of ownIndexUrls) {
2745
+ prefetchedNugetIndex.delete(indexUrl);
2746
+ }
2747
+ }
2748
+ const newDependencies = [].concat(dependencies);
2749
+ if (depRepList && newDependencies.length) {
2750
+ const changed = Object.keys(depRepList);
2751
+ // if (!parentComponent.version || parentComponent.version === "latest" || parentComponent.version === "0.0.0"){
2752
+ // if (changed.includes(parentComponent["bom-ref"])) {
2753
+ // parentComponent["bom-ref"] = depRepList[parentComponent["bom-ref"]["ref"]];
2754
+ // }
2755
+ // }
2756
+ for (const d of newDependencies) {
2757
+ if (changed.length > 0 && changed.includes(d["ref"])) {
2758
+ d["ref"] = depRepList[d["ref"]];
2759
+ }
2760
+ for (const dd in d["dependsOn"]) {
2761
+ if (changed.includes(d["dependsOn"][dd])) {
2762
+ const replace = d["dependsOn"][dd];
2763
+ d["dependsOn"][dd] = depRepList[replace];
2764
+ }
2765
+ }
2766
+ }
2767
+ }
2768
+ return {
2769
+ pkgList: cdepList,
2770
+ dependencies: newDependencies,
2771
+ };
2772
+ }