@cdxgen/cdxgen 13.2.0 → 13.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/README.md +33 -14
  2. package/bin/cdxgen.js +310 -138
  3. package/bin/evinse.js +22 -0
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +119 -15
  6. package/bin/tracebom.js +69 -5
  7. package/bin/validate.js +107 -19
  8. package/bin/verify.js +172 -63
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +85 -0
  11. package/data/rules/dependency-sources.yaml +67 -0
  12. package/data/rules/scala-evidence.yaml +115 -0
  13. package/lib/audit/index.js +38 -6
  14. package/lib/cli/bomAssembly.js +105 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +156 -58
  17. package/lib/cli/jsBom.js +99 -53
  18. package/lib/cli/jvmBom.js +708 -147
  19. package/lib/cli/managedBom.js +415 -224
  20. package/lib/cli/nativeBom.js +264 -44
  21. package/lib/core/activity.js +12 -0
  22. package/lib/core/env.js +22 -15
  23. package/lib/core/fs.js +176 -7
  24. package/lib/core/httpClient.js +159 -24
  25. package/lib/core/metadataProperties.js +97 -0
  26. package/lib/core/pluginRun.js +184 -0
  27. package/lib/core/processTree.js +100 -0
  28. package/lib/core/projectConfig.js +31 -4
  29. package/lib/core/runState.js +39 -0
  30. package/lib/core/toml.js +72 -0
  31. package/lib/ecosystems/asarutils.js +4 -2
  32. package/lib/ecosystems/bunutils.js +274 -108
  33. package/lib/ecosystems/cargoCache.js +231 -8
  34. package/lib/ecosystems/caxa.js +434 -2
  35. package/lib/ecosystems/cmakeResolver.js +76 -21
  36. package/lib/ecosystems/cppBuildContext.js +631 -0
  37. package/lib/ecosystems/cppEvidence.js +399 -51
  38. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  39. package/lib/ecosystems/denoutils.js +93 -66
  40. package/lib/ecosystems/ecosystems.js +1983 -793
  41. package/lib/ecosystems/golem.js +28 -12
  42. package/lib/ecosystems/gradleutils.js +356 -37
  43. package/lib/ecosystems/jsEvidence.js +104 -44
  44. package/lib/ecosystems/kosi.js +31 -7
  45. package/lib/ecosystems/npmutils.js +174 -9
  46. package/lib/ecosystems/parsers-dotnet.js +616 -159
  47. package/lib/ecosystems/parsers-elm.js +46 -28
  48. package/lib/ecosystems/parsers-gleam.js +3 -4
  49. package/lib/ecosystems/parsers-go.js +371 -70
  50. package/lib/ecosystems/parsers-js.js +263 -76
  51. package/lib/ecosystems/parsers-julia.js +2 -3
  52. package/lib/ecosystems/parsers-jvm.js +204 -12
  53. package/lib/ecosystems/parsers-misc.js +929 -216
  54. package/lib/ecosystems/parsers-python.js +167 -24
  55. package/lib/ecosystems/parsers-rust.js +28 -22
  56. package/lib/ecosystems/parsers-terraform.js +1573 -19
  57. package/lib/ecosystems/pythonutils.js +102 -6
  58. package/lib/ecosystems/remote/huggingface.js +3 -0
  59. package/lib/ecosystems/rubyutils.js +1492 -182
  60. package/lib/ecosystems/rusi.js +54 -10
  61. package/lib/ecosystems/sbtutils.js +243 -187
  62. package/lib/ecosystems/scalacliutils.js +342 -0
  63. package/lib/ecosystems/scalasem.js +2618 -0
  64. package/lib/ecosystems/terraformRegistry.js +288 -0
  65. package/lib/ecosystems/utils.js +2 -0
  66. package/lib/ecosystems/vendoredCode.js +156 -0
  67. package/lib/evinser/evinser.js +1008 -191
  68. package/lib/evinser/swiftsem.js +1914 -302
  69. package/lib/helpers/agenticToolutils.js +2 -2
  70. package/lib/helpers/bomSigner.js +1101 -222
  71. package/lib/inventory/aiCollector.js +4 -1
  72. package/lib/inventory/analyzer.js +110 -127
  73. package/lib/inventory/atomUtils.js +703 -43
  74. package/lib/inventory/auditCategories.js +7 -0
  75. package/lib/inventory/buildToolRateLimit.js +144 -0
  76. package/lib/inventory/cacheCommands.js +2 -1
  77. package/lib/inventory/cdxrs.js +103 -12
  78. package/lib/inventory/cmakeBuildDirs.js +254 -0
  79. package/lib/inventory/deps.js +501 -124
  80. package/lib/inventory/depsUtils.js +183 -11
  81. package/lib/inventory/display.js +4 -5
  82. package/lib/inventory/dosai.js +798 -57
  83. package/lib/inventory/dosaiParsers.js +310 -15
  84. package/lib/inventory/dynamic.js +90 -6
  85. package/lib/inventory/evidenceUtils.js +166 -113
  86. package/lib/inventory/fetchBatch.js +690 -55
  87. package/lib/inventory/fetchDiskCache.js +597 -0
  88. package/lib/inventory/forgeEnricher.js +14 -5
  89. package/lib/inventory/jvmLocalRepos.js +917 -0
  90. package/lib/inventory/mcpConfigParser.js +2 -3
  91. package/lib/inventory/parentSelection.js +146 -0
  92. package/lib/inventory/protobom.js +8 -1
  93. package/lib/inventory/purl.js +152 -3
  94. package/lib/inventory/rustFormulationParser.js +3 -4
  95. package/lib/inventory/sbomDocument.js +2 -70
  96. package/lib/inventory/scalaCoords.js +153 -0
  97. package/lib/inventory/source.js +45 -27
  98. package/lib/inventory/spdx.js +101 -0
  99. package/lib/inventory/traceRunner.js +295 -25
  100. package/lib/inventory/tracedCrypto.js +294 -0
  101. package/lib/managers/binary.js +115 -24
  102. package/lib/managers/docker.js +467 -46
  103. package/lib/managers/oci.js +63 -3
  104. package/lib/parsers/cmakeCache.js +69 -0
  105. package/lib/parsers/cmakeLists.js +466 -0
  106. package/lib/parsers/cmakePresets.js +400 -0
  107. package/lib/parsers/compileCommands.js +557 -0
  108. package/lib/parsers/largeJson.js +454 -0
  109. package/lib/parsers/npmrc.js +19 -1
  110. package/lib/parsers/strictJson.js +322 -0
  111. package/lib/parsers/terraformConfig.js +1073 -0
  112. package/lib/parsers/terraformSource.js +652 -0
  113. package/lib/server/server.js +23 -0
  114. package/lib/stages/postgen/introspection/markers.js +1 -1
  115. package/lib/stages/postgen/postgen.js +83 -16
  116. package/lib/stages/postgen/specVersionCompat.js +32 -0
  117. package/lib/validator/bomValidator.js +6 -16
  118. package/lib/validator/index.js +64 -11
  119. package/lib/validator/reporters/console.js +3 -0
  120. package/lib/validator/reporters/json.js +2 -0
  121. package/lib/validator/reporters/sarif.js +7 -0
  122. package/package.json +23 -23
  123. package/types/lib/audit/index.d.ts.map +1 -1
  124. package/types/lib/cli/bomAssembly.d.ts +2 -1
  125. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  126. package/types/lib/cli/cliOptions.d.ts +33 -1
  127. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  128. package/types/lib/cli/index.d.ts.map +1 -1
  129. package/types/lib/cli/jsBom.d.ts.map +1 -1
  130. package/types/lib/cli/jvmBom.d.ts +24 -1
  131. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  132. package/types/lib/cli/managedBom.d.ts +7 -4
  133. package/types/lib/cli/managedBom.d.ts.map +1 -1
  134. package/types/lib/cli/nativeBom.d.ts +3 -1
  135. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  136. package/types/lib/core/activity.d.ts.map +1 -1
  137. package/types/lib/core/env.d.ts +9 -0
  138. package/types/lib/core/env.d.ts.map +1 -1
  139. package/types/lib/core/fs.d.ts +55 -0
  140. package/types/lib/core/fs.d.ts.map +1 -1
  141. package/types/lib/core/httpClient.d.ts +13 -8
  142. package/types/lib/core/httpClient.d.ts.map +1 -1
  143. package/types/lib/core/metadataProperties.d.ts +39 -0
  144. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  145. package/types/lib/core/pluginRun.d.ts +97 -0
  146. package/types/lib/core/pluginRun.d.ts.map +1 -0
  147. package/types/lib/core/processTree.d.ts +36 -0
  148. package/types/lib/core/processTree.d.ts.map +1 -0
  149. package/types/lib/core/projectConfig.d.ts +6 -2
  150. package/types/lib/core/projectConfig.d.ts.map +1 -1
  151. package/types/lib/core/runState.d.ts +29 -0
  152. package/types/lib/core/runState.d.ts.map +1 -0
  153. package/types/lib/core/toml.d.ts +20 -0
  154. package/types/lib/core/toml.d.ts.map +1 -0
  155. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  156. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  157. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  158. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  159. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  160. package/types/lib/ecosystems/caxa.d.ts +14 -0
  161. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  162. package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
  163. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  164. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  165. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  166. package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
  167. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  168. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  169. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  170. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  171. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  172. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  173. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  174. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  175. package/types/lib/ecosystems/gradleutils.d.ts +70 -4
  176. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  177. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  178. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  179. package/types/lib/ecosystems/kosi.d.ts +13 -0
  180. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  181. package/types/lib/ecosystems/npmutils.d.ts +57 -0
  182. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  183. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  184. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  185. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  186. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  187. package/types/lib/ecosystems/parsers-go.d.ts +54 -2
  188. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  189. package/types/lib/ecosystems/parsers-js.d.ts +16 -5
  190. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  191. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  192. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  193. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  194. package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
  195. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  196. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  197. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  198. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  199. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  200. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  201. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  202. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  203. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  204. package/types/lib/ecosystems/rubyutils.d.ts +164 -3
  205. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  206. package/types/lib/ecosystems/rusi.d.ts +13 -0
  207. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  208. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  209. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  210. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  211. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  212. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  213. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  214. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  215. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  216. package/types/lib/ecosystems/utils.d.ts +2 -2
  217. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  218. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  219. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  220. package/types/lib/evinser/evinser.d.ts +132 -15
  221. package/types/lib/evinser/evinser.d.ts.map +1 -1
  222. package/types/lib/evinser/swiftsem.d.ts +280 -15
  223. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  224. package/types/lib/helpers/bomSigner.d.ts +182 -13
  225. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  226. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  227. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  228. package/types/lib/inventory/atomUtils.d.ts +219 -5
  229. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  230. package/types/lib/inventory/auditCategories.d.ts +3 -0
  231. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  232. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  233. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  234. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  235. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  236. package/types/lib/inventory/cdxrs.d.ts +8 -3
  237. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  238. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  239. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  240. package/types/lib/inventory/deps.d.ts +59 -1
  241. package/types/lib/inventory/deps.d.ts.map +1 -1
  242. package/types/lib/inventory/depsUtils.d.ts +15 -0
  243. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  244. package/types/lib/inventory/display.d.ts.map +1 -1
  245. package/types/lib/inventory/dosai.d.ts +121 -9
  246. package/types/lib/inventory/dosai.d.ts.map +1 -1
  247. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  248. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  249. package/types/lib/inventory/dynamic.d.ts +10 -0
  250. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  251. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  252. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  253. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  254. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  255. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  256. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  257. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  258. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  259. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  260. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  261. package/types/lib/inventory/parentSelection.d.ts +34 -0
  262. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  263. package/types/lib/inventory/plugins.d.ts.map +1 -1
  264. package/types/lib/inventory/protobom.d.ts.map +1 -1
  265. package/types/lib/inventory/purl.d.ts +71 -2
  266. package/types/lib/inventory/purl.d.ts.map +1 -1
  267. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  268. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  269. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  270. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  271. package/types/lib/inventory/source.d.ts.map +1 -1
  272. package/types/lib/inventory/spdx.d.ts +9 -0
  273. package/types/lib/inventory/spdx.d.ts.map +1 -1
  274. package/types/lib/inventory/traceRunner.d.ts +59 -1
  275. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  276. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  277. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  278. package/types/lib/managers/binary.d.ts +30 -1
  279. package/types/lib/managers/binary.d.ts.map +1 -1
  280. package/types/lib/managers/docker.d.ts +29 -0
  281. package/types/lib/managers/docker.d.ts.map +1 -1
  282. package/types/lib/managers/oci.d.ts +17 -1
  283. package/types/lib/managers/oci.d.ts.map +1 -1
  284. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  285. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  286. package/types/lib/parsers/cmakeLists.d.ts +77 -0
  287. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  288. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  289. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  290. package/types/lib/parsers/compileCommands.d.ts +115 -0
  291. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  292. package/types/lib/parsers/largeJson.d.ts +52 -0
  293. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  294. package/types/lib/parsers/npmrc.d.ts +5 -0
  295. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  296. package/types/lib/parsers/strictJson.d.ts +46 -0
  297. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  298. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  299. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  300. package/types/lib/parsers/terraformSource.d.ts +66 -0
  301. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  302. package/types/lib/server/server.d.ts.map +1 -1
  303. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  304. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  305. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  306. package/types/lib/validator/index.d.ts +18 -1
  307. package/types/lib/validator/index.d.ts.map +1 -1
  308. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  309. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  310. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
  311. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -1,15 +1,20 @@
1
1
  import { Buffer } from "node:buffer";
2
- import { readFileSync } from "node:fs";
3
- import { join } from "node:path";
2
+ import { readdirSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { delimiter, dirname, join } from "node:path";
4
5
 
5
6
  import { Purl } from "@cdxgen/cdx-purl";
6
7
 
7
- import { DEBUG_MODE } from "../core/activity.js";
8
+ import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
8
9
  import { shouldFetchLicense, shouldFetchVCS } from "../core/env.js";
9
- import { safeExistsSync } from "../core/fs.js";
10
- import { applyPurl, tryBuildPurl, tryParsePurl } from "../inventory/purl.js";
11
10
  import {
12
- _clearMetadataCache,
11
+ applyPurl,
12
+ canonicalPurlFromLooseString,
13
+ tryBuildPurl,
14
+ tryParsePurl,
15
+ } from "../inventory/purl.js";
16
+ import { guessLicenseId } from "../inventory/spdx.js";
17
+ import {
13
18
  getGoPkgLicense,
14
19
  getGoPkgVCSUrl,
15
20
  prefetchGoPkgMetadata,
@@ -25,6 +30,163 @@ function toBase64(hexString) {
25
30
  return Buffer.from(hexString, "hex").toString("base64");
26
31
  }
27
32
 
33
+ /**
34
+ * The root of the Go module cache on this machine.
35
+ *
36
+ * @returns {string|undefined} `$GOMODCACHE`, or `$GOPATH/pkg/mod`, or the
37
+ * default `~/go/pkg/mod`.
38
+ */
39
+ function goModuleCacheDir() {
40
+ const gomodcache = readEnvironmentVariable("GOMODCACHE");
41
+ if (gomodcache) {
42
+ return gomodcache;
43
+ }
44
+ const gopath = readEnvironmentVariable("GOPATH");
45
+ if (gopath) {
46
+ return join(gopath.split(delimiter)[0], "pkg", "mod");
47
+ }
48
+ return join(homedir(), "go", "pkg", "mod");
49
+ }
50
+
51
+ /**
52
+ * Escape a module path the way Go's module cache directory names do: every
53
+ * upper-case letter becomes `!` followed by the lower-case one, so the cache
54
+ * stays case-preserving on case-insensitive file systems
55
+ * (`github.com/Azure` is stored as `github.com/!azure`).
56
+ *
57
+ * @param {string} modulePath Full module path
58
+ * @returns {string} The escaped path
59
+ */
60
+ function escapeGoModulePath(modulePath) {
61
+ let escaped = "";
62
+ for (const ch of modulePath) {
63
+ if (ch >= "A" && ch <= "Z") {
64
+ escaped += `!${ch.toLowerCase()}`;
65
+ } else {
66
+ escaped += ch;
67
+ }
68
+ }
69
+ return escaped;
70
+ }
71
+
72
+ /**
73
+ * Whether a file name is a licence or copying notice, whose text describes the
74
+ * module's licence without a registry round.
75
+ *
76
+ * @param {string} fileName Candidate file name
77
+ * @returns {boolean}
78
+ */
79
+ function isGoLicenseFileName(fileName) {
80
+ const lower = fileName.toLowerCase();
81
+ return lower.startsWith("license") || lower.startsWith("copying");
82
+ }
83
+
84
+ /**
85
+ * The licence or copying notice of a module that is already on this machine,
86
+ * from the directory `go list` reported, from the project's `vendor/` tree, or
87
+ * from the module cache. Reading it costs no network round trip, so it answers
88
+ * before pkg.go.dev is asked.
89
+ *
90
+ * The directory `go list` reported is always read. The vendor tree and the
91
+ * module cache stand in for pkg.go.dev, so they are read only when licences
92
+ * are being fetched.
93
+ *
94
+ * @param {string} name Full module path
95
+ * @param {string} [version] Module version, for the module cache layout
96
+ * @param {Object} [options] Options
97
+ * @param {string} [options.dir] The module's directory, as `go list` reports it
98
+ * @param {string} [options.projectRoot] Project root holding a `vendor/` tree
99
+ * @returns {string|undefined} The notice text, or undefined when none is held
100
+ * locally
101
+ */
102
+ export function readLocalGoLicense(name, version, options = {}) {
103
+ if (!name) {
104
+ return undefined;
105
+ }
106
+ const candidateDirs = [];
107
+ if (options.dir) {
108
+ candidateDirs.push(options.dir);
109
+ }
110
+ const fetchingLicenses = shouldFetchLicense();
111
+ if (fetchingLicenses && options.projectRoot) {
112
+ candidateDirs.push(join(options.projectRoot, "vendor", name));
113
+ }
114
+ if (fetchingLicenses && version) {
115
+ const cacheRoot = goModuleCacheDir();
116
+ if (cacheRoot) {
117
+ candidateDirs.push(
118
+ join(cacheRoot, `${escapeGoModulePath(name)}@${version}`),
119
+ );
120
+ }
121
+ }
122
+ for (const dir of candidateDirs) {
123
+ let entries;
124
+ try {
125
+ entries = readdirSync(dir);
126
+ } catch {
127
+ continue;
128
+ }
129
+ const names = entries
130
+ .filter((e) => isGoLicenseFileName(e))
131
+ .sort((a, b) => a.localeCompare(b));
132
+ // A directory can hold both a licence and a copying notice; the licence
133
+ // notice wins when both are present.
134
+ const chosen =
135
+ names.find((f) => f.toLowerCase().startsWith("license")) ||
136
+ names.find((f) => f.toLowerCase().startsWith("copying"));
137
+ if (!chosen) {
138
+ continue;
139
+ }
140
+ try {
141
+ const text = readFileSync(join(dir, chosen), {
142
+ encoding: "utf-8",
143
+ });
144
+ if (text?.length) {
145
+ return text;
146
+ }
147
+ } catch {
148
+ // An unreadable notice is the same as none: the registry path answers.
149
+ }
150
+ }
151
+ return undefined;
152
+ }
153
+
154
+ /**
155
+ * Mark the modules whose licence notice is held on this machine, so the
156
+ * pkg.go.dev batch skips their licence tab but still fetches their module page
157
+ * for the repository URL.
158
+ *
159
+ * @param {Object[]} modules Modules with `name`, `version` and the options
160
+ * {@link readLocalGoLicense} takes
161
+ * @returns {Object[]} Copies of the modules with `licenseKnown` set
162
+ */
163
+ function withLocalLicenseFlags(modules) {
164
+ return modules.map((m) => ({
165
+ ...m,
166
+ licenseKnown: !!readLocalGoLicense(m.name, m.version, m),
167
+ }));
168
+ }
169
+
170
+ /**
171
+ * The licence object for a notice read from disk: its SPDX id when the text
172
+ * names one, else a CUSTOM licence, with the notice as its text either way.
173
+ *
174
+ * @param {string} text Notice text
175
+ * @returns {Object} CycloneDX licence object
176
+ */
177
+ function localGoLicenseObject(text) {
178
+ const licenseId = guessLicenseId(text);
179
+ const identity = !licenseId
180
+ ? { name: "CUSTOM" }
181
+ : licenseId.includes(" ")
182
+ ? { name: licenseId }
183
+ : { id: licenseId };
184
+ return {
185
+ ...identity,
186
+ text: { contentType: "text/plain", content: text },
187
+ };
188
+ }
189
+
28
190
  /**
29
191
  * Builds a Go package component object containing purl, bom-ref, integrity hash,
30
192
  * and optionally license and VCS external reference information.
@@ -33,11 +195,31 @@ function toBase64(hexString) {
33
195
  * @param {string} name Package name (full module path when group is empty)
34
196
  * @param {string} version Package version string
35
197
  * @param {string} hash Integrity hash (e.g. "sha256-…"), used as _integrity
198
+ * @param {Object} [options] Options
199
+ * @param {boolean} [options.skipRegistryLookups] Leave the licence and VCS
200
+ * URL unset instead of asking pkg.go.dev. Used for the main module, which
201
+ * no registry holds.
202
+ * @param {string} [options.dir] The module's directory, as `go list` reports
203
+ * it, where a local licence notice is read first
204
+ * @param {string} [options.projectRoot] Project root holding a `vendor/` tree
205
+ * the module may be vendored in
36
206
  * @returns {Promise<Object>} Component object ready for inclusion in a BOM package list
37
207
  */
38
- export async function getGoPkgComponent(group, name, version, hash) {
208
+ export async function getGoPkgComponent(
209
+ group,
210
+ name,
211
+ version,
212
+ hash,
213
+ options = {},
214
+ ) {
39
215
  let license;
40
- if (shouldFetchLicense()) {
216
+ let licenses;
217
+ // A notice held on this machine answers before pkg.go.dev is asked, and the
218
+ // registry is consulted only when there is none.
219
+ const localLicense = readLocalGoLicense(name, version, options);
220
+ if (localLicense) {
221
+ licenses = [{ license: localGoLicenseObject(localLicense) }];
222
+ } else if (shouldFetchLicense() && !options.skipRegistryLookups) {
41
223
  if (DEBUG_MODE) {
42
224
  console.log(
43
225
  `About to fetch go package license information for ${group}:${name}`,
@@ -46,6 +228,7 @@ export async function getGoPkgComponent(group, name, version, hash) {
46
228
  license = await getGoPkgLicense({
47
229
  group: group,
48
230
  name: name,
231
+ version: version,
49
232
  });
50
233
  }
51
234
  // Split the full module path into namespace and name, since the caller
@@ -71,8 +254,8 @@ export async function getGoPkgComponent(group, name, version, hash) {
71
254
  version: version || null,
72
255
  })?.replace(/%2F/g, "/") || null;
73
256
  let vcs;
74
- if (shouldFetchVCS()) {
75
- vcs = await getGoPkgVCSUrl(group, name);
257
+ if (shouldFetchVCS() && !options.skipRegistryLookups) {
258
+ vcs = await getGoPkgVCSUrl(group, name, version);
76
259
  }
77
260
  const packageInfo = {
78
261
  group: group,
@@ -81,6 +264,9 @@ export async function getGoPkgComponent(group, name, version, hash) {
81
264
  _integrity: hash,
82
265
  license: license,
83
266
  };
267
+ if (licenses) {
268
+ packageInfo.licenses = licenses;
269
+ }
84
270
  applyPurl(packageInfo, purlString);
85
271
  if (vcs) {
86
272
  packageInfo.externalReferences = [{ type: "vcs", url: vcs }];
@@ -104,7 +290,9 @@ export async function parseGoModData(goModData, gosumMap) {
104
290
  return {};
105
291
  }
106
292
  const { parentComponent, entries } = decodeGoModData(goModData, gosumMap);
107
- await prefetchGoPkgMetadata(entries);
293
+ // A module whose licence notice is read locally is not asked for its
294
+ // licence; getGoPkgComponent reads the same notice for each entry below.
295
+ await prefetchGoPkgMetadata(withLocalLicenseFlags(entries));
108
296
  for (const entry of entries) {
109
297
  const component = await getGoPkgComponent(
110
298
  "",
@@ -124,8 +312,6 @@ export async function parseGoModData(goModData, gosumMap) {
124
312
  }
125
313
  pkgComponentsList.push(component);
126
314
  }
127
- // Clear the cache
128
- _clearMetadataCache();
129
315
  return {
130
316
  parentComponent,
131
317
  pkgList: pkgComponentsList.sort((a, b) =>
@@ -266,18 +452,30 @@ export async function parseGoModulesTxt(txtFile, gosumMap) {
266
452
  const pkgs = txtData
267
453
  .split("\n")
268
454
  .filter((p) => p.trim().replace(/["']/g, "").startsWith("# "));
269
- await prefetchGoPkgMetadata(
270
- // # cel.dev/expr v0.18.0
271
- pkgs.map((l) => ({ group: "", name: l.split(" ")[1] })),
272
- );
273
- for (const l of pkgs) {
455
+ // A modules.txt file lives in the vendor/ tree, so the modules it lists are
456
+ // vendored one directory up from it.
457
+ const projectRoot = dirname(dirname(txtFile));
458
+ const modules = pkgs.map((l) => {
274
459
  const tmpA = l.split(" ");
275
- if (!tmpA.length === 3) {
276
- continue;
277
- }
278
- const version = tmpA[2];
279
- const gosumHash = gosumMap[`${tmpA[1]}@${version}`];
280
- const component = await getGoPkgComponent("", tmpA[1], tmpA[2], gosumHash);
460
+ return {
461
+ group: "",
462
+ name: tmpA[1],
463
+ version: tmpA[2],
464
+ projectRoot,
465
+ };
466
+ });
467
+ // A module whose vendored licence notice is read is not asked for its
468
+ // licence; getGoPkgComponent reads the same file.
469
+ await prefetchGoPkgMetadata(withLocalLicenseFlags(modules));
470
+ for (const m of modules) {
471
+ const gosumHash = gosumMap[`${m.name}@${m.version}`];
472
+ const component = await getGoPkgComponent(
473
+ "",
474
+ m.name,
475
+ m.version,
476
+ gosumHash,
477
+ { projectRoot },
478
+ );
281
479
  let confidence = 0.7;
282
480
  if (gosumHash) {
283
481
  component.scope = "required";
@@ -308,19 +506,44 @@ export async function parseGoListDep(rawOutput, gosumMap) {
308
506
  // covers every module the loop resolves and nothing else.
309
507
  const prefetchSeen = new Set();
310
508
  const prefetchModules = [];
509
+ // The directory of the main module row, which is the project root, under
510
+ // which the vendored copies of the dependencies live. `go list` may print
511
+ // the main module last, so its row is found before the batch is built.
512
+ let mainModuleDir;
513
+ for (const l of pkgs) {
514
+ const verArr = l.trim().replace(/["']/g, "").split("|");
515
+ if (verArr?.length >= 10 && verArr[5] === "true" && verArr[9]?.length) {
516
+ mainModuleDir = verArr[9];
517
+ }
518
+ }
311
519
  for (const l of pkgs) {
312
520
  const verArr = l.trim().replace(/["']/g, "").split("|");
313
521
  if (!verArr || verArr.length < 5) {
314
522
  continue;
315
523
  }
524
+ // The row whose fifth field is true is the main module: the project
525
+ // itself, which no registry holds and which the loop below turns into
526
+ // the parent component.
527
+ if (verArr[5] === "true") {
528
+ continue;
529
+ }
316
530
  const key = `${verArr[0]}-${verArr[1]}`;
317
531
  if (prefetchSeen.has(key)) {
318
532
  continue;
319
533
  }
320
534
  prefetchSeen.add(key);
321
- prefetchModules.push({ group: "", name: verArr[0] });
535
+ prefetchModules.push({
536
+ group: "",
537
+ name: verArr[0],
538
+ version: verArr[1],
539
+ dir: verArr[9]?.length ? verArr[9] : undefined,
540
+ projectRoot: mainModuleDir,
541
+ });
322
542
  }
323
- await prefetchGoPkgMetadata(prefetchModules);
543
+ // A module whose licence notice is held on this machine is not asked for
544
+ // its licence: getGoPkgComponent reads the same notices, so the batch
545
+ // covers exactly what the loop will look up.
546
+ await prefetchGoPkgMetadata(withLocalLicenseFlags(prefetchModules));
324
547
  for (const l of pkgs) {
325
548
  const verArr = l.trim().replace(/["']/g, "").split("|");
326
549
  if (verArr && verArr.length >= 5) {
@@ -333,11 +556,17 @@ export async function parseGoListDep(rawOutput, gosumMap) {
333
556
  if (!gosumHash && verArr.length >= 8 && verArr[8]?.length) {
334
557
  gosumHash = `sha256-${verArr[8].replace("h1:", "")}`;
335
558
  }
559
+ const isMainModule = verArr[5] === "true";
336
560
  const component = await getGoPkgComponent(
337
561
  "",
338
562
  verArr[0],
339
563
  version,
340
564
  gosumHash,
565
+ {
566
+ skipRegistryLookups: isMainModule,
567
+ dir: verArr[9]?.length ? verArr[9] : undefined,
568
+ projectRoot: mainModuleDir,
569
+ },
341
570
  );
342
571
  // This is misusing the scope attribute to represent direct vs indirect
343
572
  if (verArr[2] === "false") {
@@ -380,21 +609,6 @@ export async function parseGoListDep(rawOutput, gosumMap) {
380
609
  name: "cdx:go:local_dir",
381
610
  value: verArr[9],
382
611
  });
383
- if (safeExistsSync(join(verArr[9], "LICENSE"))) {
384
- const licenseText = readFileSync(join(verArr[9], "LICENSE"), {
385
- encoding: "utf-8",
386
- });
387
- if (licenseText?.length) {
388
- component.licenses = [
389
- {
390
- license: {
391
- name: "CUSTOM",
392
- text: { contentType: "text/plain", content: licenseText },
393
- },
394
- },
395
- ];
396
- }
397
- }
398
612
  }
399
613
  if (verArr.length > 5 && verArr[5] === "true") {
400
614
  parentComponent = component;
@@ -443,6 +657,23 @@ function _addGoComponentEvidence(component, goModFile, confidence = 0.8) {
443
657
  return component;
444
658
  }
445
659
 
660
+ /**
661
+ * Build the purl of one `module@version` token from `go mod graph` output.
662
+ *
663
+ * The version is written the way Go prints it, so `+incompatible` and the build
664
+ * metadata of a pseudo-version are unescaped. Reading the token strictly would
665
+ * reject it and leave a reference that keeps the module path's upper-case
666
+ * letters, which then never matches the lower-cased component and drops the
667
+ * dependency edge.
668
+ *
669
+ * @param {string} token Module path, optionally followed by `@version`
670
+ * @returns {Purl|null} Parsed purl, or null when the token forms no valid golang purl
671
+ */
672
+ function goModGraphTokenPurl(token) {
673
+ const canonical = canonicalPurlFromLooseString(`pkg:golang/${token}`);
674
+ return canonical ? Purl.parse(canonical) : null;
675
+ }
676
+
446
677
  /**
447
678
  * Parse go mod graph
448
679
  *
@@ -512,22 +743,20 @@ export async function parseGoModGraph(
512
743
  // cdx-purl requires a namespace. Use the raw ref when parsing fails.
513
744
  let sourceRefString;
514
745
  let sourceName = tmpA[0].split("@")[0];
515
- let sourcePurl = null;
516
- try {
517
- sourcePurl = Purl.parse(`pkg:golang/${tmpA[0]}`);
746
+ const sourcePurl = goModGraphTokenPurl(tmpA[0]);
747
+ if (sourcePurl) {
518
748
  sourceRefString = decodeURIComponent(sourcePurl.toString());
519
749
  sourceName = `${sourcePurl.namespace ? `${sourcePurl.namespace}/` : ""}${sourcePurl.name}`;
520
- } catch {
750
+ } else {
521
751
  sourceRefString = `pkg:golang/${tmpA[0]}`;
522
752
  }
523
753
  let dependsRefString;
524
754
  let dependsName = tmpA[1].split("@")[0];
525
- let dependsPurl = null;
526
- try {
527
- dependsPurl = Purl.parse(`pkg:golang/${tmpA[1]}`);
755
+ const dependsPurl = goModGraphTokenPurl(tmpA[1]);
756
+ if (dependsPurl) {
528
757
  dependsRefString = decodeURIComponent(dependsPurl.toString());
529
758
  dependsName = `${dependsPurl.namespace ? `${dependsPurl.namespace}/` : ""}${dependsPurl.name}`;
530
- } catch {
759
+ } else {
531
760
  dependsRefString = `pkg:golang/${tmpA[1]}`;
532
761
  }
533
762
  // Since go mod graph over-reports direct dependencies we use the existing list
@@ -542,11 +771,20 @@ export async function parseGoModGraph(
542
771
  }
543
772
  // Add the source and depends to the pkgList
544
773
  if (!addedPkgs[tmpA[0]] && !excludedRefs.includes(sourceRefString)) {
774
+ // go mod graph prints a main module without a version. A main
775
+ // module is the project itself, or a go.work member, which no
776
+ // registry holds, so it is never looked up. It still becomes a
777
+ // component, because the graph's edges hang off it.
778
+ const isParentSource =
779
+ !tmpA[0].includes("@") ||
780
+ sourceName === goModPkgMap?.parentComponent?.name ||
781
+ sourceRefString === parentComponent?.["bom-ref"];
545
782
  const component = await getGoPkgComponent(
546
783
  "",
547
784
  sourceName,
548
785
  sourcePurl?.version || tmpA[0].split("@")[1],
549
786
  gosumMap[tmpA[0]],
787
+ { skipRegistryLookups: isParentSource },
550
788
  );
551
789
  let confidence = 0.7;
552
790
  if (goModOptionalDepsMap[component["bom-ref"]]) {
@@ -684,19 +922,49 @@ export function parseGoModWhy(rawOutput) {
684
922
  }
685
923
 
686
924
  /**
687
- * Parse go sum data
925
+ * Reports whether `go mod why -m` output says the main module does not need
926
+ * the module. go prints `(main module does not need module X)`, or
927
+ * `(main module does not need to vendor module X)` with `-vendor`, and exits
928
+ * with status 0 either way.
929
+ *
930
+ * @param {string} rawOutput Output from go mod why
931
+ * @returns {boolean} True when go reports the module as not needed
932
+ */
933
+ export function isGoModWhyNotNeeded(rawOutput) {
934
+ if (typeof rawOutput !== "string") {
935
+ return false;
936
+ }
937
+ return rawOutput
938
+ .split("\n")
939
+ .some((line) => line.trim().startsWith("(main module does not need "));
940
+ }
941
+
942
+ /**
943
+ * Read the `module@version` to hash map out of go.sum contents, without
944
+ * building components or touching the network.
945
+ *
688
946
  * @param {string} gosumData Content of go.sum
689
- * @returns package list
947
+ * @returns {Object} Map of `module@version` keys to `sha256-…` values
690
948
  */
691
- export async function parseGosumData(gosumData) {
692
- const pkgList = [];
693
- if (!gosumData) {
694
- return pkgList;
949
+ export function parseGosumHashes(gosumData) {
950
+ const gosumMap = {};
951
+ for (const entry of decodeGosumData(gosumData)) {
952
+ gosumMap[`${entry.name}@${entry.version}`] = entry.hash;
695
953
  }
696
- // Decoding every line first lets the whole module set be fetched in one
697
- // batched round before any component is built. One decode function feeds both,
698
- // so the batch cannot ask for a module the loop does not resolve.
954
+ return gosumMap;
955
+ }
956
+
957
+ /**
958
+ * Decode the go.mod lines of a go.sum file into module entries.
959
+ *
960
+ * @param {string} gosumData Content of go.sum
961
+ * @returns {Object[]} Entries with `name`, `version` and `hash`
962
+ */
963
+ function decodeGosumData(gosumData) {
699
964
  const entries = [];
965
+ if (!gosumData) {
966
+ return entries;
967
+ }
700
968
  for (const l of gosumData.split("\n")) {
701
969
  const m = l.replaceAll("\r", "");
702
970
  // look for lines containing go.mod
@@ -710,7 +978,20 @@ export async function parseGosumData(gosumData) {
710
978
  });
711
979
  }
712
980
  }
713
- await prefetchGoPkgMetadata(entries);
981
+ return entries;
982
+ }
983
+
984
+ /**
985
+ * Parse go sum data
986
+ * @param {string} gosumData Content of go.sum
987
+ * @returns package list
988
+ */
989
+ export async function parseGosumData(gosumData) {
990
+ const pkgList = [];
991
+ const entries = decodeGosumData(gosumData);
992
+ // A module whose licence notice is read locally is not asked for its
993
+ // licence; getGoPkgComponent reads the same notice for each entry below.
994
+ await prefetchGoPkgMetadata(withLocalLicenseFlags(entries));
714
995
  for (const entry of entries) {
715
996
  pkgList.push(
716
997
  await getGoPkgComponent("", entry.name, entry.version, entry.hash),
@@ -737,13 +1018,17 @@ export async function parseGopkgData(gopkgData) {
737
1018
  for (const l of pkgs) {
738
1019
  let key = null;
739
1020
  let value = null;
740
- if (l.indexOf("[[projects]]") > -1) {
1021
+ // Every table header ends the project read so far, and only a
1022
+ // [[projects]] table starts another. The keys of [solve-meta] belong to
1023
+ // no project.
1024
+ if (l.trim().startsWith("[")) {
741
1025
  if (pkg) {
742
- pkgList.push(pkg);
1026
+ pkgList.push(await finalizeGopkgProject(pkg));
743
1027
  }
744
- pkg = {};
1028
+ pkg = l.indexOf("[[projects]]") > -1 ? {} : null;
1029
+ continue;
745
1030
  }
746
- if (l.indexOf("=") > -1) {
1031
+ if (pkg && l.indexOf("=") > -1) {
747
1032
  const tmpA = l.split("=");
748
1033
  key = tmpA[0].trim();
749
1034
  value = tmpA[1].trim().replace(/"/g, "");
@@ -756,12 +1041,6 @@ export async function parseGopkgData(gopkgData) {
756
1041
  case "name":
757
1042
  pkg.group = "";
758
1043
  pkg.name = value;
759
- if (shouldFetchLicense()) {
760
- pkg.license = await getGoPkgLicense({
761
- group: pkg.group,
762
- name: pkg.name,
763
- });
764
- }
765
1044
  break;
766
1045
  case "version":
767
1046
  pkg.version = value;
@@ -773,9 +1052,31 @@ export async function parseGopkgData(gopkgData) {
773
1052
  }
774
1053
  }
775
1054
  }
1055
+ // The last project has no header after it.
1056
+ if (pkg) {
1057
+ pkgList.push(await finalizeGopkgProject(pkg));
1058
+ }
776
1059
  return pkgList;
777
1060
  }
778
1061
 
1062
+ /**
1063
+ * Attach the licence of one Gopkg project once its name and version are both
1064
+ * known, so the pkg.go.dev URL names the pinned release.
1065
+ *
1066
+ * @param {Object} pkg The project entry read so far
1067
+ * @returns {Promise<Object>} The same entry, with a licence when one was found
1068
+ */
1069
+ async function finalizeGopkgProject(pkg) {
1070
+ if (shouldFetchLicense() && pkg.name) {
1071
+ pkg.license = await getGoPkgLicense({
1072
+ group: pkg.group,
1073
+ name: pkg.name,
1074
+ version: pkg.version,
1075
+ });
1076
+ }
1077
+ return pkg;
1078
+ }
1079
+
779
1080
  /**
780
1081
  * Parses the output of `go version -m` (build info) and returns a list of Go
781
1082
  * package components for each "dep" line, including name, version, and integrity hash.
@@ -808,7 +1109,7 @@ export async function parseGoVersionData(buildInfoData) {
808
1109
  : "",
809
1110
  });
810
1111
  }
811
- await prefetchGoPkgMetadata(entries);
1112
+ await prefetchGoPkgMetadata(withLocalLicenseFlags(entries));
812
1113
  for (const entry of entries) {
813
1114
  pkgList.push(
814
1115
  await getGoPkgComponent("", entry.name, entry.version, entry.hash),