@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
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Shared plumbing for plugin (rusi/golem/kosi) invocations: resolving the
3
+ * directory to analyze, and reporting a failed run.
4
+ */
5
+
6
+ import { isAbsolute, join, normalize, resolve, sep } from "node:path";
7
+ import process from "node:process";
8
+
9
+ import { DEBUG_MODE } from "./activity.js";
10
+ import { deferFailOnError } from "./deferredExit.js";
11
+ import { safeExistsSync } from "./fs.js";
12
+
13
+ /**
14
+ * The working directory, when a relative source argument names it.
15
+ *
16
+ * cdxgen is frequently launched with its working directory already set to the
17
+ * project (depscan does exactly that) while the path argument stays relative
18
+ * to some repository root, e.g. cwd `/work/repo/test/data/app` with argument
19
+ * `test/data/app`. A plain `path.resolve` then produces the doubled
20
+ * `/work/repo/test/data/app/test/data/app`. This recognises that shape and
21
+ * nothing looser: the argument's path segments must be the trailing segments
22
+ * of the working directory, so a mistyped argument run from some other
23
+ * project is never silently redirected to that project.
24
+ *
25
+ * @param {string} src The source path as the caller typed it.
26
+ * @param {string} [cwd] Working directory; defaults to `process.cwd()`.
27
+ * @returns {string|undefined} The absolute working directory when the
28
+ * argument names it, otherwise undefined.
29
+ */
30
+ export function cwdNamedByRelativeSource(src, cwd = process.cwd()) {
31
+ if (typeof src !== "string" || !src || isAbsolute(src)) {
32
+ return undefined;
33
+ }
34
+ // normalize() converts separators to the platform's, so one split on sep
35
+ // handles both "a/b" and "a\\b" on Windows; on POSIX a backslash is an
36
+ // ordinary file-name character and must not split.
37
+ const relParts = normalize(src)
38
+ .split(sep)
39
+ .filter((part) => part && part !== ".");
40
+ if (!relParts.length || relParts.includes("..")) {
41
+ return undefined;
42
+ }
43
+ const absCwd = resolve(cwd);
44
+ const cwdParts = absCwd.split(sep).filter(Boolean);
45
+ if (relParts.length > cwdParts.length) {
46
+ return undefined;
47
+ }
48
+ const tail = cwdParts.slice(cwdParts.length - relParts.length);
49
+ const caseInsensitive = process.platform === "win32";
50
+ const same = (a, b) =>
51
+ caseInsensitive ? a.toLowerCase() === b.toLowerCase() : a === b;
52
+ return relParts.every((part, i) => same(part, tail[i])) ? absCwd : undefined;
53
+ }
54
+
55
+ /**
56
+ * Resolve the source path argument once, at cdxgen's entry point.
57
+ *
58
+ * Everything downstream — the BOM generators, evinse, and the rusi/golem/kosi
59
+ * launchers — resolves the argument against the working directory, so the
60
+ * doubled-directory shape has to be corrected before any of them run;
61
+ * correcting it only in the plugin launchers leaves the main BOM built from a
62
+ * directory that does not exist.
63
+ *
64
+ * @param {string} src The source path as the caller typed it.
65
+ * @param {string} [cwd] Working directory; defaults to `process.cwd()`.
66
+ * @returns {{path: string, rewritten: boolean, missing: boolean}} `path` is
67
+ * the argument to use: unchanged when it resolves to an existing path, the
68
+ * working directory when the argument names it (`rewritten`), and otherwise
69
+ * unchanged with `missing` set.
70
+ */
71
+ export function resolveSourcePathArgument(src, cwd = process.cwd()) {
72
+ if (typeof src !== "string" || !src) {
73
+ return { path: src, rewritten: false, missing: false };
74
+ }
75
+ if (safeExistsSync(resolve(cwd, src))) {
76
+ return { path: src, rewritten: false, missing: false };
77
+ }
78
+ const named = cwdNamedByRelativeSource(src, cwd);
79
+ if (named) {
80
+ return { path: named, rewritten: true, missing: false };
81
+ }
82
+ return { path: src, rewritten: false, missing: true };
83
+ }
84
+
85
+ /**
86
+ * Resolve the directory a plugin should analyze.
87
+ *
88
+ * The entry point already corrects the doubled-directory shape (see
89
+ * resolveSourcePathArgument); this is the same rule for callers that reach a
90
+ * plugin launcher directly (evinse, the library API). An existing directory
91
+ * wins and an absolute path is never rewritten. Otherwise the working
92
+ * directory is used only when the argument names it AND it carries the
93
+ * project marker the plugin needs (a `Cargo.toml` for rusi, a `go.mod` for
94
+ * golem). Anything else keeps the resolved path so the plugin's own error
95
+ * names the directory that was actually attempted.
96
+ *
97
+ * @param {string} src The source path as the caller typed it.
98
+ * @param {string[]} markerFiles File names that, present in the working
99
+ * directory, identify it as the project the plugin should analyze.
100
+ * @returns {string} The directory to pass to the plugin.
101
+ */
102
+ export function resolvePluginSourceDir(src, markerFiles = []) {
103
+ const resolved = resolve(src);
104
+ if (safeExistsSync(resolved) || isAbsolute(src)) {
105
+ return resolved;
106
+ }
107
+ const cwd = cwdNamedByRelativeSource(src);
108
+ if (!cwd) {
109
+ return resolved;
110
+ }
111
+ for (const marker of markerFiles) {
112
+ if (marker && safeExistsSync(join(cwd, marker))) {
113
+ console.error(
114
+ `Resolved source directory ${resolved} does not exist; the working directory ${cwd} is the project the argument names (found ${marker}), using it instead.`,
115
+ );
116
+ return cwd;
117
+ }
118
+ }
119
+ return resolved;
120
+ }
121
+
122
+ /**
123
+ * Report a failed plugin run, keeping the two failure classes apart.
124
+ *
125
+ * A spawn error means the plugin binary itself could not run — not installed,
126
+ * not executable, blocked by the command policy — which is an installation
127
+ * problem. A non-zero exit status or a missing report means the plugin ran
128
+ * and the analysis failed, which must not be dressed up as an installation
129
+ * problem: the message names the directory and says the reachability
130
+ * evidence will be missing, because a silent degradation is what let a
131
+ * doubled `--dir` go unnoticed in the first place.
132
+ *
133
+ * Under `--fail-on-error` the failure additionally claims the process exit
134
+ * status through the deferred-exit contract.
135
+ *
136
+ * @param {Object} run What ran and how it failed.
137
+ * @param {string} run.tool Plugin name, e.g. "rusi".
138
+ * @param {string} run.ecosystem Ecosystem the plugin serves, e.g. "rust".
139
+ * @param {string} run.executable The binary that was invoked.
140
+ * @param {string[]} run.args The arguments it was invoked with.
141
+ * @param {string} run.dir The directory that was analyzed.
142
+ * @param {Object} run.result The spawn result.
143
+ * @param {string} run.outputFile The report file the plugin should have written.
144
+ * @param {Object} [run.options] CLI options, for the `--fail-on-error` contract.
145
+ * @returns {void}
146
+ */
147
+ export function reportPluginRunFailure({
148
+ tool,
149
+ ecosystem,
150
+ executable,
151
+ args,
152
+ dir,
153
+ result,
154
+ outputFile,
155
+ options,
156
+ }) {
157
+ const command = `${executable} ${(args || []).join(" ")}`.trim();
158
+ if (result?.error) {
159
+ console.error(
160
+ `${tool} plugin could not be executed for ${dir}: ${result.error.message}. Check if the ${tool} plugin was installed successfully.`,
161
+ );
162
+ } else {
163
+ const status =
164
+ typeof result?.status === "number" ? result.status : "unknown";
165
+ const reason = safeExistsSync(outputFile)
166
+ ? "exited unsuccessfully"
167
+ : "wrote no report";
168
+ console.error(
169
+ `${tool} analysis ${reason} for directory ${dir} (exit status ${status}); ${ecosystem} reachability evidence will be missing from the BOM.`,
170
+ );
171
+ }
172
+ if (DEBUG_MODE && (result?.stdout || result?.stderr)) {
173
+ console.error(result.stdout, result.stderr);
174
+ }
175
+ deferFailOnError(options, {
176
+ ecosystem,
177
+ tool,
178
+ detail: result?.error
179
+ ? `${tool} plugin could not be executed: ${result.error.message}`
180
+ : `${tool} analysis failed for directory ${dir}`,
181
+ exitCode: typeof result?.status === "number" ? result.status : undefined,
182
+ command,
183
+ });
184
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Helpers for external commands stopped by cdxgen's spawn timeout.
3
+ *
4
+ * Layer 0: this module imports nothing outside `lib/core`, so both the atom
5
+ * and the dosai runners (layer 2) can share it.
6
+ */
7
+
8
+ import { DEBUG_MODE } from "./activity.js";
9
+ import { safeSpawnSync } from "./fs.js";
10
+ import { isWin } from "./paths.js";
11
+
12
+ /**
13
+ * A duration for a message: seconds to one decimal below a minute, minutes to
14
+ * one decimal from there.
15
+ *
16
+ * @param {number} ms Duration in milliseconds
17
+ * @returns {string} Human-readable duration
18
+ */
19
+ export function formatDuration(ms) {
20
+ if (ms >= 60_000) {
21
+ const minutes = Math.round(ms / 6_000) / 10;
22
+ return `${minutes} minute${minutes === 1 ? "" : "s"}`;
23
+ }
24
+ return `${Math.round(ms / 100) / 10} seconds`;
25
+ }
26
+
27
+ /**
28
+ * Stop the descendants a timed-out run left behind on Windows.
29
+ *
30
+ * The spawn timeout terminates only the process cdxgen spawned, and Windows
31
+ * neither kills nor re-parents its children: a shell's command, a runtime's
32
+ * workers, or the build hosts a .NET analyzer starts keep running while still
33
+ * pointing at the dead process's pid. `taskkill /T` on that pid finds nothing,
34
+ * because the root is already gone. The live descendants are therefore found
35
+ * by parent pid in a process snapshot and stopped, limited to processes
36
+ * created after the run started. The root's own children must also predate
37
+ * its end: once the root is gone its pid can be handed to an unrelated
38
+ * process, whose children would otherwise look like the run's. Elsewhere this
39
+ * does nothing: an orphan is re-parented and can no longer be told apart.
40
+ *
41
+ * @param {number|undefined} rootPid pid of the process cdxgen spawned
42
+ * @param {number} startedAt Epoch milliseconds at which the run started
43
+ * @param {number} [endedAt] Epoch milliseconds at which the spawned process ended
44
+ * @param {string} [toolName] Name of the tool, for the debug message
45
+ * @returns {number[]} The pids that were stopped
46
+ */
47
+ export function reapProcessTree(
48
+ rootPid,
49
+ startedAt,
50
+ endedAt = Date.now(),
51
+ toolName = "external",
52
+ ) {
53
+ if (!isWin || !Number.isInteger(rootPid) || rootPid <= 0) {
54
+ return [];
55
+ }
56
+ // All three values are integers, so interpolating them cannot inject anything.
57
+ const since = Math.max(0, Math.floor(startedAt) - 1000);
58
+ const until = Math.max(since, Math.floor(endedAt) + 1000);
59
+ const script = [
60
+ `$root = ${rootPid}`,
61
+ `$since = [DateTimeOffset]::FromUnixTimeMilliseconds(${since}).UtcDateTime`,
62
+ `$until = [DateTimeOffset]::FromUnixTimeMilliseconds(${until}).UtcDateTime`,
63
+ "$children = @{}",
64
+ "foreach ($p in Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate) {",
65
+ " if (-not $p.CreationDate) { continue }",
66
+ " $created = $p.CreationDate.ToUniversalTime()",
67
+ " if ($created -lt $since) { continue }",
68
+ " if ([int]$p.ParentProcessId -eq $root -and $created -gt $until) { continue }",
69
+ " $children[[int]$p.ParentProcessId] += @([int]$p.ProcessId)",
70
+ "}",
71
+ "$found = [System.Collections.Generic.List[int]]::new()",
72
+ "$queue = [System.Collections.Generic.Queue[int]]::new()",
73
+ "$queue.Enqueue($root)",
74
+ "while ($queue.Count) {",
75
+ " foreach ($child in @($children[$queue.Dequeue()])) {",
76
+ " if ($child -and -not $found.Contains($child)) { $found.Add($child); $queue.Enqueue($child) }",
77
+ " }",
78
+ "}",
79
+ "foreach ($child in $found) { Stop-Process -Id $child -Force -ErrorAction SilentlyContinue }",
80
+ "$found -join ','",
81
+ ].join("\n");
82
+ const result = safeSpawnSync(
83
+ "powershell",
84
+ ["-NoProfile", "-NonInteractive", "-Command", script],
85
+ { timeout: 60000 },
86
+ );
87
+ const reaped = `${result?.stdout || ""}`
88
+ .trim()
89
+ .split(",")
90
+ .map((pid) => Number.parseInt(pid, 10))
91
+ .filter((pid) => pid > 0);
92
+ if (DEBUG_MODE) {
93
+ console.log(
94
+ reaped.length
95
+ ? `Stopped ${reaped.length} process(es) the timed-out ${toolName} run left behind: ${reaped.join(", ")}.`
96
+ : `The timed-out ${toolName} run left no processes behind.`,
97
+ );
98
+ }
99
+ return reaped;
100
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * State that lasts for one run.
3
+ *
4
+ * Registry answers, prefetched documents, recorded misses and local cache
5
+ * lookups are kept in module state, so that a package several lockfiles name
6
+ * costs one lookup. A run is one BOM generation. The CLI exits when it ends, but
7
+ * a long-lived process such as the server performs many, and nothing a run
8
+ * remembered should outlive it: the registries publish new releases, builds
9
+ * download new artifacts, and the memory belongs to the next scan.
10
+ *
11
+ * Every module that keeps such state registers a reset when it loads. The
12
+ * server calls {@link resetRunState} between scans. A module that has not
13
+ * loaded yet holds nothing to reset.
14
+ */
15
+
16
+ /** @type {Map<string, () => void>} */
17
+ const runResets = new Map();
18
+
19
+ /**
20
+ * Register the function that empties a module's per-run state.
21
+ *
22
+ * @param {string} name Unique name, normally the module and what it holds
23
+ * @param {() => void} reset Empties that state
24
+ * @returns {void}
25
+ */
26
+ export function registerRunReset(name, reset) {
27
+ runResets.set(name, reset);
28
+ }
29
+
30
+ /**
31
+ * Empty the per-run state of every module that registered one.
32
+ *
33
+ * @returns {void}
34
+ */
35
+ export function resetRunState() {
36
+ for (const reset of runResets.values()) {
37
+ reset();
38
+ }
39
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * TOML parsing with stable, ordinary objects.
3
+ *
4
+ * smol-toml 1.9 began returning tables and inline tables with a `null`
5
+ * prototype so that a hostile `__proto__` key in a manifest cannot pollute
6
+ * `Object.prototype`. The SBOM data itself is unaffected, but cdxgen hands
7
+ * parsed manifests straight to callers (and `assert.deepStrictEqual` in the
8
+ * test suite), which compare by prototype and would see every table as a
9
+ * different kind of value. Re-wrapping the parse result with the standard
10
+ * prototype keeps the pre-1.9 contract: plain objects all the way down,
11
+ * `__proto__` keys included as ordinary own properties.
12
+ */
13
+
14
+ import { parse as smolParse } from "smol-toml";
15
+
16
+ /**
17
+ * Recursively rebuild a parsed value with `Object.prototype` roots.
18
+ *
19
+ * Class instances other than plain tables (smol-toml dates, strings, numbers)
20
+ * are passed through untouched, and arrays are copied only when an element
21
+ * actually changes, so unmodified branches keep their original identity.
22
+ *
23
+ * @param {unknown} value value produced by smol-toml's parse
24
+ * @returns {unknown} the same data with plain-prototype objects
25
+ */
26
+ function toPlainPrototype(value) {
27
+ if (!value || typeof value !== "object") {
28
+ return value;
29
+ }
30
+ if (Array.isArray(value)) {
31
+ let changed = false;
32
+ const out = new Array(value.length);
33
+ for (let i = 0; i < value.length; i++) {
34
+ out[i] = toPlainPrototype(value[i]);
35
+ if (out[i] !== value[i]) {
36
+ changed = true;
37
+ }
38
+ }
39
+ return changed ? out : value;
40
+ }
41
+ const proto = Object.getPrototypeOf(value);
42
+ if (proto !== null && proto !== Object.prototype) {
43
+ return value;
44
+ }
45
+ const out = {};
46
+ for (const [key, entry] of Object.entries(value)) {
47
+ if (key === "__proto__") {
48
+ // Plain assignment would re-point the new object's prototype instead
49
+ // of recording the key, reintroducing the pollution smol-toml guards
50
+ // against.
51
+ Object.defineProperty(out, key, {
52
+ value: toPlainPrototype(entry),
53
+ enumerable: true,
54
+ writable: true,
55
+ configurable: true,
56
+ });
57
+ } else {
58
+ out[key] = toPlainPrototype(entry);
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ /**
65
+ * Parse a TOML document into plain objects.
66
+ *
67
+ * @param {string} text TOML source
68
+ * @returns {Object} parsed document whose tables use `Object.prototype`
69
+ */
70
+ export function parseToml(text) {
71
+ return toPlainPrototype(smolParse(text));
72
+ }
@@ -30,6 +30,7 @@ import {
30
30
  } from "../inventory/analyzer.js";
31
31
  import { tryBuildPurl } from "../inventory/purl.js";
32
32
  import { xml2js } from "../parsers/xml.js";
33
+ import { resolveNpmLicense } from "./npmutils.js";
33
34
 
34
35
  const ASAR_JS_ANALYSIS_EXTENSIONS = new Set([
35
36
  ".cjs",
@@ -1040,8 +1041,9 @@ function createArchiveParentComponent(
1040
1041
  ? primaryPackageJson.author
1041
1042
  : primaryPackageJson.author?.name || "";
1042
1043
  }
1043
- if (primaryPackageJson?.license) {
1044
- component.license = primaryPackageJson.license;
1044
+ const license = resolveNpmLicense(primaryPackageJson);
1045
+ if (license) {
1046
+ component.license = license;
1045
1047
  }
1046
1048
  if (primaryPackageJson?.repository) {
1047
1049
  const repositoryUrl =
@@ -3,8 +3,8 @@ import { dirname } from "node:path";
3
3
 
4
4
  import { build } from "@cdxgen/cdx-purl";
5
5
 
6
- import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
7
- import { shouldFetchLicense, shouldFetchVCS } from "../core/env.js";
6
+ import { DEBUG_MODE } from "../core/activity.js";
7
+ import { shouldFetchPackageMetadata } from "../core/env.js";
8
8
  import { safeExistsSync } from "../core/fs.js";
9
9
  import { getNpmMetadata } from "./ecosystems.js";
10
10
  import {
@@ -554,11 +554,9 @@ export async function parseBunLock(bunLockFile, options = {}) {
554
554
  });
555
555
  }
556
556
 
557
- if (
558
- shouldFetchLicense() ||
559
- shouldFetchVCS() ||
560
- readEnvironmentVariable("FETCH_LICENSE") === "true"
561
- ) {
557
+ // The shared metadata gate: FETCH_LICENSE and the provenance fetch that
558
+ // --bom-audit turns on both reach the registry from here.
559
+ if (shouldFetchPackageMetadata()) {
562
560
  if (DEBUG_MODE) {
563
561
  console.log(
564
562
  `About to fetch npm registry metadata for ${pkgList.length} packages in parseBunLock`,