@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
@@ -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
+ }
@@ -1,5 +1,10 @@
1
1
  import path from "node:path";
2
2
 
3
+ import {
4
+ isReservedMetadataPropertyName,
5
+ METADATA_PROPERTY_CONFIG_KEYS,
6
+ parseMetadataProperties,
7
+ } from "./metadataProperties.js";
3
8
  import { toCamel } from "./paths.js";
4
9
 
5
10
  /**
@@ -87,19 +92,41 @@ function isContainedPath(configDir, value) {
87
92
  * whenever that tree is. Path options are therefore confined to the config's
88
93
  * own directory - a scanned project may choose where in itself the BOM lands,
89
94
  * but not write outside it - and options that redirect data off the host or
90
- * widen what cdxgen executes are announced on the console.
95
+ * widen what cdxgen executes are announced on the console. A project may
96
+ * describe itself with custom metadata properties, but not in the namespaces
97
+ * cdxgen reserves for the facts it establishes, so a scanned tree cannot pass
98
+ * its own claims off as cdxgen's.
91
99
  *
92
100
  * @param {Object} config parsed config file contents
93
101
  * @param {string} configDir directory the config file was read from
94
- * @returns {{config: Object, rejected: string[], announced: string[]}} sanitized config and what it did
102
+ * @returns {{config: Object, rejected: string[], announced: string[], reservedProperties: string[]}} sanitized config and what it did
95
103
  */
96
104
  export function sanitizeProjectConfig(config, configDir) {
97
105
  if (!config || typeof config !== "object") {
98
- return { announced: [], config: {}, rejected: [] };
106
+ return { announced: [], config: {}, rejected: [], reservedProperties: [] };
99
107
  }
100
108
  const sanitized = { ...config };
101
109
  const rejected = [];
102
110
  const announced = [];
111
+ const reservedProperties = [];
112
+ for (const key of METADATA_PROPERTY_CONFIG_KEYS) {
113
+ if (sanitized[key] === undefined) {
114
+ continue;
115
+ }
116
+ const properties = [];
117
+ for (const aprop of parseMetadataProperties(sanitized[key])) {
118
+ if (isReservedMetadataPropertyName(aprop.name)) {
119
+ reservedProperties.push(aprop.name);
120
+ } else {
121
+ properties.push(aprop);
122
+ }
123
+ }
124
+ if (properties.length) {
125
+ sanitized[key] = properties;
126
+ } else {
127
+ delete sanitized[key];
128
+ }
129
+ }
103
130
  for (const option of PATH_OPTIONS) {
104
131
  for (const key of optionSpellings(option)) {
105
132
  if (sanitized[key] === undefined) {
@@ -118,5 +145,5 @@ export function sanitizeProjectConfig(config, configDir) {
118
145
  }
119
146
  }
120
147
  }
121
- return { announced, config: sanitized, rejected };
148
+ return { announced, config: sanitized, rejected, reservedProperties };
122
149
  }
@@ -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 =