@cdxgen/cdxgen 13.3.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 (255) hide show
  1. package/README.md +9 -6
  2. package/bin/cdxgen.js +90 -26
  3. package/bin/evinse.js +18 -1
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +45 -15
  6. package/bin/tracebom.js +5 -1
  7. package/bin/validate.js +84 -18
  8. package/bin/verify.js +155 -86
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +61 -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/index.js +113 -54
  15. package/lib/cli/jsBom.js +50 -29
  16. package/lib/cli/jvmBom.js +665 -138
  17. package/lib/cli/managedBom.js +320 -200
  18. package/lib/cli/nativeBom.js +139 -30
  19. package/lib/core/activity.js +12 -0
  20. package/lib/core/env.js +17 -9
  21. package/lib/core/fs.js +3 -2
  22. package/lib/core/httpClient.js +159 -24
  23. package/lib/core/pluginRun.js +184 -0
  24. package/lib/core/processTree.js +100 -0
  25. package/lib/core/runState.js +39 -0
  26. package/lib/core/toml.js +72 -0
  27. package/lib/ecosystems/asarutils.js +4 -2
  28. package/lib/ecosystems/bunutils.js +5 -7
  29. package/lib/ecosystems/cargoCache.js +231 -8
  30. package/lib/ecosystems/cmakeResolver.js +10 -21
  31. package/lib/ecosystems/cppBuildContext.js +631 -0
  32. package/lib/ecosystems/cppEvidence.js +337 -22
  33. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  34. package/lib/ecosystems/denoutils.js +93 -66
  35. package/lib/ecosystems/ecosystems.js +1967 -788
  36. package/lib/ecosystems/golem.js +28 -12
  37. package/lib/ecosystems/gradleutils.js +314 -30
  38. package/lib/ecosystems/jsEvidence.js +85 -24
  39. package/lib/ecosystems/kosi.js +21 -4
  40. package/lib/ecosystems/npmutils.js +64 -0
  41. package/lib/ecosystems/parsers-dotnet.js +567 -151
  42. package/lib/ecosystems/parsers-elm.js +46 -28
  43. package/lib/ecosystems/parsers-gleam.js +3 -4
  44. package/lib/ecosystems/parsers-go.js +324 -61
  45. package/lib/ecosystems/parsers-js.js +92 -26
  46. package/lib/ecosystems/parsers-julia.js +2 -3
  47. package/lib/ecosystems/parsers-jvm.js +204 -12
  48. package/lib/ecosystems/parsers-misc.js +182 -55
  49. package/lib/ecosystems/parsers-python.js +141 -10
  50. package/lib/ecosystems/parsers-rust.js +4 -4
  51. package/lib/ecosystems/parsers-terraform.js +1573 -19
  52. package/lib/ecosystems/pythonutils.js +102 -6
  53. package/lib/ecosystems/remote/huggingface.js +3 -0
  54. package/lib/ecosystems/rubyutils.js +67 -17
  55. package/lib/ecosystems/rusi.js +22 -9
  56. package/lib/ecosystems/sbtutils.js +243 -187
  57. package/lib/ecosystems/scalacliutils.js +342 -0
  58. package/lib/ecosystems/scalasem.js +2618 -0
  59. package/lib/ecosystems/terraformRegistry.js +288 -0
  60. package/lib/ecosystems/utils.js +2 -0
  61. package/lib/ecosystems/vendoredCode.js +156 -0
  62. package/lib/evinser/evinser.js +578 -69
  63. package/lib/helpers/agenticToolutils.js +2 -2
  64. package/lib/helpers/bomSigner.js +576 -128
  65. package/lib/inventory/aiCollector.js +4 -1
  66. package/lib/inventory/analyzer.js +110 -127
  67. package/lib/inventory/atomUtils.js +171 -48
  68. package/lib/inventory/auditCategories.js +7 -0
  69. package/lib/inventory/buildToolRateLimit.js +144 -0
  70. package/lib/inventory/cacheCommands.js +2 -1
  71. package/lib/inventory/cmakeBuildDirs.js +254 -0
  72. package/lib/inventory/deps.js +501 -124
  73. package/lib/inventory/dosai.js +248 -44
  74. package/lib/inventory/dosaiParsers.js +310 -15
  75. package/lib/inventory/dynamic.js +52 -6
  76. package/lib/inventory/evidenceUtils.js +154 -109
  77. package/lib/inventory/fetchBatch.js +574 -38
  78. package/lib/inventory/fetchDiskCache.js +597 -0
  79. package/lib/inventory/forgeEnricher.js +14 -5
  80. package/lib/inventory/jvmLocalRepos.js +917 -0
  81. package/lib/inventory/mcpConfigParser.js +2 -3
  82. package/lib/inventory/parentSelection.js +146 -0
  83. package/lib/inventory/rustFormulationParser.js +3 -4
  84. package/lib/inventory/scalaCoords.js +153 -0
  85. package/lib/inventory/source.js +8 -8
  86. package/lib/inventory/spdx.js +101 -0
  87. package/lib/inventory/traceRunner.js +88 -7
  88. package/lib/inventory/tracedCrypto.js +294 -0
  89. package/lib/managers/docker.js +33 -32
  90. package/lib/managers/oci.js +24 -2
  91. package/lib/parsers/cmakeCache.js +69 -0
  92. package/lib/parsers/cmakeLists.js +16 -2
  93. package/lib/parsers/cmakePresets.js +400 -0
  94. package/lib/parsers/compileCommands.js +557 -0
  95. package/lib/parsers/strictJson.js +322 -0
  96. package/lib/parsers/terraformConfig.js +1073 -0
  97. package/lib/parsers/terraformSource.js +652 -0
  98. package/lib/server/server.js +23 -0
  99. package/lib/stages/postgen/introspection/markers.js +1 -1
  100. package/lib/stages/postgen/postgen.js +15 -1
  101. package/lib/stages/postgen/specVersionCompat.js +32 -0
  102. package/lib/validator/index.js +25 -10
  103. package/lib/validator/reporters/json.js +1 -0
  104. package/package.json +21 -21
  105. package/types/lib/audit/index.d.ts.map +1 -1
  106. package/types/lib/cli/index.d.ts.map +1 -1
  107. package/types/lib/cli/jsBom.d.ts.map +1 -1
  108. package/types/lib/cli/jvmBom.d.ts +24 -1
  109. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  110. package/types/lib/cli/managedBom.d.ts +7 -4
  111. package/types/lib/cli/managedBom.d.ts.map +1 -1
  112. package/types/lib/cli/nativeBom.d.ts +3 -1
  113. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  114. package/types/lib/core/activity.d.ts.map +1 -1
  115. package/types/lib/core/env.d.ts +9 -0
  116. package/types/lib/core/env.d.ts.map +1 -1
  117. package/types/lib/core/fs.d.ts.map +1 -1
  118. package/types/lib/core/httpClient.d.ts +13 -8
  119. package/types/lib/core/httpClient.d.ts.map +1 -1
  120. package/types/lib/core/pluginRun.d.ts +97 -0
  121. package/types/lib/core/pluginRun.d.ts.map +1 -0
  122. package/types/lib/core/processTree.d.ts +36 -0
  123. package/types/lib/core/processTree.d.ts.map +1 -0
  124. package/types/lib/core/runState.d.ts +29 -0
  125. package/types/lib/core/runState.d.ts.map +1 -0
  126. package/types/lib/core/toml.d.ts +20 -0
  127. package/types/lib/core/toml.d.ts.map +1 -0
  128. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  129. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  130. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  131. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  132. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  133. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  134. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  135. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  136. package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
  137. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  139. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  140. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  141. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  143. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  144. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  145. package/types/lib/ecosystems/gradleutils.d.ts +50 -0
  146. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  147. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  148. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  149. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  150. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  151. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  152. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  153. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  154. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  155. package/types/lib/ecosystems/parsers-go.d.ts +44 -2
  156. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  157. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  158. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  159. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  160. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  161. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  162. package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
  163. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  164. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  165. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  166. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  167. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  168. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  169. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  170. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  171. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  172. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  173. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  174. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  175. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  176. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  177. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  178. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  179. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  180. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  181. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  182. package/types/lib/ecosystems/utils.d.ts +2 -2
  183. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  184. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  185. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  186. package/types/lib/evinser/evinser.d.ts +75 -8
  187. package/types/lib/evinser/evinser.d.ts.map +1 -1
  188. package/types/lib/helpers/bomSigner.d.ts +80 -4
  189. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  190. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  191. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  192. package/types/lib/inventory/atomUtils.d.ts +82 -10
  193. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  194. package/types/lib/inventory/auditCategories.d.ts +3 -0
  195. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  196. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  197. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  198. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  199. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  200. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  201. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  202. package/types/lib/inventory/deps.d.ts +59 -1
  203. package/types/lib/inventory/deps.d.ts.map +1 -1
  204. package/types/lib/inventory/dosai.d.ts +57 -8
  205. package/types/lib/inventory/dosai.d.ts.map +1 -1
  206. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  207. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  208. package/types/lib/inventory/dynamic.d.ts +10 -0
  209. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  210. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  211. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  212. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  213. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  214. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  215. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  216. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  217. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  218. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  219. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  220. package/types/lib/inventory/parentSelection.d.ts +34 -0
  221. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  222. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  223. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  224. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  225. package/types/lib/inventory/source.d.ts.map +1 -1
  226. package/types/lib/inventory/spdx.d.ts +9 -0
  227. package/types/lib/inventory/spdx.d.ts.map +1 -1
  228. package/types/lib/inventory/traceRunner.d.ts +12 -0
  229. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  230. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  231. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  232. package/types/lib/managers/docker.d.ts.map +1 -1
  233. package/types/lib/managers/oci.d.ts +3 -1
  234. package/types/lib/managers/oci.d.ts.map +1 -1
  235. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  236. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  237. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  238. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  239. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  240. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  241. package/types/lib/parsers/compileCommands.d.ts +115 -0
  242. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  243. package/types/lib/parsers/strictJson.d.ts +46 -0
  244. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  245. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  246. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  247. package/types/lib/parsers/terraformSource.d.ts +66 -0
  248. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  249. package/types/lib/server/server.d.ts.map +1 -1
  250. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  252. package/types/lib/validator/index.d.ts +8 -0
  253. package/types/lib/validator/index.d.ts.map +1 -1
  254. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  255. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -1,20 +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
10
  import {
11
11
  applyPurl,
12
12
  canonicalPurlFromLooseString,
13
13
  tryBuildPurl,
14
14
  tryParsePurl,
15
15
  } from "../inventory/purl.js";
16
+ import { guessLicenseId } from "../inventory/spdx.js";
16
17
  import {
17
- _clearMetadataCache,
18
18
  getGoPkgLicense,
19
19
  getGoPkgVCSUrl,
20
20
  prefetchGoPkgMetadata,
@@ -30,6 +30,163 @@ function toBase64(hexString) {
30
30
  return Buffer.from(hexString, "hex").toString("base64");
31
31
  }
32
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
+
33
190
  /**
34
191
  * Builds a Go package component object containing purl, bom-ref, integrity hash,
35
192
  * and optionally license and VCS external reference information.
@@ -38,11 +195,31 @@ function toBase64(hexString) {
38
195
  * @param {string} name Package name (full module path when group is empty)
39
196
  * @param {string} version Package version string
40
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
41
206
  * @returns {Promise<Object>} Component object ready for inclusion in a BOM package list
42
207
  */
43
- export async function getGoPkgComponent(group, name, version, hash) {
208
+ export async function getGoPkgComponent(
209
+ group,
210
+ name,
211
+ version,
212
+ hash,
213
+ options = {},
214
+ ) {
44
215
  let license;
45
- 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) {
46
223
  if (DEBUG_MODE) {
47
224
  console.log(
48
225
  `About to fetch go package license information for ${group}:${name}`,
@@ -51,6 +228,7 @@ export async function getGoPkgComponent(group, name, version, hash) {
51
228
  license = await getGoPkgLicense({
52
229
  group: group,
53
230
  name: name,
231
+ version: version,
54
232
  });
55
233
  }
56
234
  // Split the full module path into namespace and name, since the caller
@@ -76,8 +254,8 @@ export async function getGoPkgComponent(group, name, version, hash) {
76
254
  version: version || null,
77
255
  })?.replace(/%2F/g, "/") || null;
78
256
  let vcs;
79
- if (shouldFetchVCS()) {
80
- vcs = await getGoPkgVCSUrl(group, name);
257
+ if (shouldFetchVCS() && !options.skipRegistryLookups) {
258
+ vcs = await getGoPkgVCSUrl(group, name, version);
81
259
  }
82
260
  const packageInfo = {
83
261
  group: group,
@@ -86,6 +264,9 @@ export async function getGoPkgComponent(group, name, version, hash) {
86
264
  _integrity: hash,
87
265
  license: license,
88
266
  };
267
+ if (licenses) {
268
+ packageInfo.licenses = licenses;
269
+ }
89
270
  applyPurl(packageInfo, purlString);
90
271
  if (vcs) {
91
272
  packageInfo.externalReferences = [{ type: "vcs", url: vcs }];
@@ -109,7 +290,9 @@ export async function parseGoModData(goModData, gosumMap) {
109
290
  return {};
110
291
  }
111
292
  const { parentComponent, entries } = decodeGoModData(goModData, gosumMap);
112
- 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));
113
296
  for (const entry of entries) {
114
297
  const component = await getGoPkgComponent(
115
298
  "",
@@ -129,8 +312,6 @@ export async function parseGoModData(goModData, gosumMap) {
129
312
  }
130
313
  pkgComponentsList.push(component);
131
314
  }
132
- // Clear the cache
133
- _clearMetadataCache();
134
315
  return {
135
316
  parentComponent,
136
317
  pkgList: pkgComponentsList.sort((a, b) =>
@@ -271,18 +452,30 @@ export async function parseGoModulesTxt(txtFile, gosumMap) {
271
452
  const pkgs = txtData
272
453
  .split("\n")
273
454
  .filter((p) => p.trim().replace(/["']/g, "").startsWith("# "));
274
- await prefetchGoPkgMetadata(
275
- // # cel.dev/expr v0.18.0
276
- pkgs.map((l) => ({ group: "", name: l.split(" ")[1] })),
277
- );
278
- 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) => {
279
459
  const tmpA = l.split(" ");
280
- if (!tmpA.length === 3) {
281
- continue;
282
- }
283
- const version = tmpA[2];
284
- const gosumHash = gosumMap[`${tmpA[1]}@${version}`];
285
- 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
+ );
286
479
  let confidence = 0.7;
287
480
  if (gosumHash) {
288
481
  component.scope = "required";
@@ -313,19 +506,44 @@ export async function parseGoListDep(rawOutput, gosumMap) {
313
506
  // covers every module the loop resolves and nothing else.
314
507
  const prefetchSeen = new Set();
315
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
+ }
316
519
  for (const l of pkgs) {
317
520
  const verArr = l.trim().replace(/["']/g, "").split("|");
318
521
  if (!verArr || verArr.length < 5) {
319
522
  continue;
320
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
+ }
321
530
  const key = `${verArr[0]}-${verArr[1]}`;
322
531
  if (prefetchSeen.has(key)) {
323
532
  continue;
324
533
  }
325
534
  prefetchSeen.add(key);
326
- 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
+ });
327
542
  }
328
- 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));
329
547
  for (const l of pkgs) {
330
548
  const verArr = l.trim().replace(/["']/g, "").split("|");
331
549
  if (verArr && verArr.length >= 5) {
@@ -338,11 +556,17 @@ export async function parseGoListDep(rawOutput, gosumMap) {
338
556
  if (!gosumHash && verArr.length >= 8 && verArr[8]?.length) {
339
557
  gosumHash = `sha256-${verArr[8].replace("h1:", "")}`;
340
558
  }
559
+ const isMainModule = verArr[5] === "true";
341
560
  const component = await getGoPkgComponent(
342
561
  "",
343
562
  verArr[0],
344
563
  version,
345
564
  gosumHash,
565
+ {
566
+ skipRegistryLookups: isMainModule,
567
+ dir: verArr[9]?.length ? verArr[9] : undefined,
568
+ projectRoot: mainModuleDir,
569
+ },
346
570
  );
347
571
  // This is misusing the scope attribute to represent direct vs indirect
348
572
  if (verArr[2] === "false") {
@@ -385,21 +609,6 @@ export async function parseGoListDep(rawOutput, gosumMap) {
385
609
  name: "cdx:go:local_dir",
386
610
  value: verArr[9],
387
611
  });
388
- if (safeExistsSync(join(verArr[9], "LICENSE"))) {
389
- const licenseText = readFileSync(join(verArr[9], "LICENSE"), {
390
- encoding: "utf-8",
391
- });
392
- if (licenseText?.length) {
393
- component.licenses = [
394
- {
395
- license: {
396
- name: "CUSTOM",
397
- text: { contentType: "text/plain", content: licenseText },
398
- },
399
- },
400
- ];
401
- }
402
- }
403
612
  }
404
613
  if (verArr.length > 5 && verArr[5] === "true") {
405
614
  parentComponent = component;
@@ -562,11 +771,20 @@ export async function parseGoModGraph(
562
771
  }
563
772
  // Add the source and depends to the pkgList
564
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"];
565
782
  const component = await getGoPkgComponent(
566
783
  "",
567
784
  sourceName,
568
785
  sourcePurl?.version || tmpA[0].split("@")[1],
569
786
  gosumMap[tmpA[0]],
787
+ { skipRegistryLookups: isParentSource },
570
788
  );
571
789
  let confidence = 0.7;
572
790
  if (goModOptionalDepsMap[component["bom-ref"]]) {
@@ -722,19 +940,31 @@ export function isGoModWhyNotNeeded(rawOutput) {
722
940
  }
723
941
 
724
942
  /**
725
- * Parse go sum data
943
+ * Read the `module@version` to hash map out of go.sum contents, without
944
+ * building components or touching the network.
945
+ *
726
946
  * @param {string} gosumData Content of go.sum
727
- * @returns package list
947
+ * @returns {Object} Map of `module@version` keys to `sha256-…` values
728
948
  */
729
- export async function parseGosumData(gosumData) {
730
- const pkgList = [];
731
- if (!gosumData) {
732
- 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;
733
953
  }
734
- // Decoding every line first lets the whole module set be fetched in one
735
- // batched round before any component is built. One decode function feeds both,
736
- // 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) {
737
964
  const entries = [];
965
+ if (!gosumData) {
966
+ return entries;
967
+ }
738
968
  for (const l of gosumData.split("\n")) {
739
969
  const m = l.replaceAll("\r", "");
740
970
  // look for lines containing go.mod
@@ -748,7 +978,20 @@ export async function parseGosumData(gosumData) {
748
978
  });
749
979
  }
750
980
  }
751
- 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));
752
995
  for (const entry of entries) {
753
996
  pkgList.push(
754
997
  await getGoPkgComponent("", entry.name, entry.version, entry.hash),
@@ -775,13 +1018,17 @@ export async function parseGopkgData(gopkgData) {
775
1018
  for (const l of pkgs) {
776
1019
  let key = null;
777
1020
  let value = null;
778
- 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("[")) {
779
1025
  if (pkg) {
780
- pkgList.push(pkg);
1026
+ pkgList.push(await finalizeGopkgProject(pkg));
781
1027
  }
782
- pkg = {};
1028
+ pkg = l.indexOf("[[projects]]") > -1 ? {} : null;
1029
+ continue;
783
1030
  }
784
- if (l.indexOf("=") > -1) {
1031
+ if (pkg && l.indexOf("=") > -1) {
785
1032
  const tmpA = l.split("=");
786
1033
  key = tmpA[0].trim();
787
1034
  value = tmpA[1].trim().replace(/"/g, "");
@@ -794,12 +1041,6 @@ export async function parseGopkgData(gopkgData) {
794
1041
  case "name":
795
1042
  pkg.group = "";
796
1043
  pkg.name = value;
797
- if (shouldFetchLicense()) {
798
- pkg.license = await getGoPkgLicense({
799
- group: pkg.group,
800
- name: pkg.name,
801
- });
802
- }
803
1044
  break;
804
1045
  case "version":
805
1046
  pkg.version = value;
@@ -811,9 +1052,31 @@ export async function parseGopkgData(gopkgData) {
811
1052
  }
812
1053
  }
813
1054
  }
1055
+ // The last project has no header after it.
1056
+ if (pkg) {
1057
+ pkgList.push(await finalizeGopkgProject(pkg));
1058
+ }
814
1059
  return pkgList;
815
1060
  }
816
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
+
817
1080
  /**
818
1081
  * Parses the output of `go version -m` (build info) and returns a list of Go
819
1082
  * package components for each "dep" line, including name, version, and integrity hash.
@@ -846,7 +1109,7 @@ export async function parseGoVersionData(buildInfoData) {
846
1109
  : "",
847
1110
  });
848
1111
  }
849
- await prefetchGoPkgMetadata(entries);
1112
+ await prefetchGoPkgMetadata(withLocalLicenseFlags(entries));
850
1113
  for (const entry of entries) {
851
1114
  pkgList.push(
852
1115
  await getGoPkgComponent("", entry.name, entry.version, entry.hash),