@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,12 +1,11 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
3
 
4
- import * as toml from "smol-toml";
5
-
6
4
  import {
7
5
  sanitizeBomPropertyValue,
8
6
  sanitizeBomUrl,
9
7
  } from "../core/propertySanitizer.js";
8
+ import { parseToml } from "../core/toml.js";
10
9
  import { parseJsonLike } from "./jsonLike.js";
11
10
  import { classifyMcpReference } from "./mcp.js";
12
11
  import {
@@ -813,7 +812,7 @@ export const mcpConfigParser = {
813
812
  let configJson;
814
813
  try {
815
814
  configJson = filePath.toLowerCase().endsWith(".toml")
816
- ? toml.parse(raw)
815
+ ? parseToml(raw)
817
816
  : parseJsonLike(raw);
818
817
  } catch {
819
818
  continue;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Which detected project becomes the parent (`metadata.component`) when one
3
+ * scan covers several ecosystems and no parent was given.
4
+ *
5
+ * Each ecosystem detects its own project from its own manifest. The project
6
+ * whose manifest sits at the scan root describes the repository; one found
7
+ * deeper is a part of it (a web client in `tools/ui/`, a helper script's
8
+ * `pyproject.toml`). So candidates are ordered by how deep their ecosystem's
9
+ * manifests sit, the scan root first, and ties keep the ecosystem order the
10
+ * scan ran in.
11
+ */
12
+
13
+ import { readdirSync } from "node:fs";
14
+ import { dirname, relative, resolve, sep } from "node:path";
15
+
16
+ import { getAllFiles } from "../core/fs.js";
17
+
18
+ /**
19
+ * Manifest file names (or `*.ext` suffixes) per purl type: the files a
20
+ * project of that ecosystem is described by.
21
+ */
22
+ const MANIFESTS_BY_PURL_TYPE = {
23
+ npm: ["package.json"],
24
+ pypi: [
25
+ "pyproject.toml",
26
+ "setup.py",
27
+ "setup.cfg",
28
+ "Pipfile",
29
+ "requirements.txt",
30
+ "uv.lock",
31
+ "poetry.lock",
32
+ "pixi.toml",
33
+ ],
34
+ generic: [
35
+ "CMakeLists.txt",
36
+ "meson.build",
37
+ "conanfile.txt",
38
+ "conanfile.py",
39
+ "vcpkg.json",
40
+ "xmake.lua",
41
+ ],
42
+ maven: ["pom.xml", "build.gradle", "build.gradle.kts", "build.sbt"],
43
+ golang: ["go.mod"],
44
+ cargo: ["Cargo.toml"],
45
+ composer: ["composer.json"],
46
+ gem: ["Gemfile", "*.gemspec"],
47
+ nuget: ["*.csproj", "*.fsproj", "*.vbproj", "*.sln", "packages.config"],
48
+ pub: ["pubspec.yaml"],
49
+ hex: ["mix.exs"],
50
+ hackage: ["*.cabal", "stack.yaml"],
51
+ swift: ["Package.swift"],
52
+ cocoapods: ["Podfile"],
53
+ clojars: ["deps.edn", "project.clj"],
54
+ };
55
+
56
+ /** The purl type of a component, or `undefined`. */
57
+ function purlTypeOf(component) {
58
+ const purl = `${component?.purl || ""}`;
59
+ if (!purl.startsWith("pkg:")) {
60
+ return undefined;
61
+ }
62
+ const rest = purl.slice(4);
63
+ const end = rest.search(/[/@?#]/);
64
+ return (end < 0 ? rest : rest.slice(0, end)).toLowerCase();
65
+ }
66
+
67
+ function matchesManifest(name, patterns) {
68
+ return patterns.some((p) =>
69
+ p.startsWith("*.") ? name.endsWith(p.slice(1)) : name === p,
70
+ );
71
+ }
72
+
73
+ /**
74
+ * How deep the shallowest manifest of an ecosystem sits under a scan root:
75
+ * 0 for one at the root itself, `Infinity` when there is none (or the
76
+ * ecosystem is not one this module knows).
77
+ *
78
+ * @param {string} root Scan root
79
+ * @param {string} purlType Purl type of the ecosystem
80
+ * @param {Object} options CLI options (exclusions)
81
+ * @returns {number}
82
+ */
83
+ export function manifestDepth(root, purlType, options = {}) {
84
+ const patterns = MANIFESTS_BY_PURL_TYPE[purlType];
85
+ if (!patterns || !root) {
86
+ return Number.POSITIVE_INFINITY;
87
+ }
88
+ const absRoot = resolve(root);
89
+ try {
90
+ if (readdirSync(absRoot).some((name) => matchesManifest(name, patterns))) {
91
+ return 0;
92
+ }
93
+ } catch (_err) {
94
+ return Number.POSITIVE_INFINITY;
95
+ }
96
+ const glob =
97
+ patterns.length === 1 ? `**/${patterns[0]}` : `**/{${patterns.join(",")}}`;
98
+ const files = getAllFiles(absRoot, glob, options);
99
+ let depth = Number.POSITIVE_INFINITY;
100
+ for (const f of files) {
101
+ const rel = relative(absRoot, dirname(resolve(f)));
102
+ if (!rel || rel.startsWith("..")) {
103
+ continue;
104
+ }
105
+ depth = Math.min(depth, rel.split(sep).length);
106
+ }
107
+ return depth;
108
+ }
109
+
110
+ /**
111
+ * Order parent candidates by the depth of their ecosystem's manifests under
112
+ * the scan roots (the shallowest first). The sort is stable, so candidates of
113
+ * equal depth keep the order the ecosystems were scanned in.
114
+ *
115
+ * @param {Object[]} candidates Detected parent components, in scan order
116
+ * @param {string[]} roots Scan roots
117
+ * @param {Object} options CLI options
118
+ * @returns {Object[]} The candidates, reordered
119
+ */
120
+ export function orderParentCandidates(candidates, roots, options = {}) {
121
+ const depthByType = new Map();
122
+ const depthOf = (component) => {
123
+ const type = purlTypeOf(component);
124
+ if (!type) {
125
+ return Number.POSITIVE_INFINITY;
126
+ }
127
+ if (!depthByType.has(type)) {
128
+ depthByType.set(
129
+ type,
130
+ Math.min(
131
+ Number.POSITIVE_INFINITY,
132
+ ...(roots || []).map((r) => manifestDepth(r, type, options)),
133
+ ),
134
+ );
135
+ }
136
+ return depthByType.get(type);
137
+ };
138
+ return candidates
139
+ .map((component, index) => ({
140
+ component,
141
+ index,
142
+ depth: depthOf(component),
143
+ }))
144
+ .sort((a, b) => a.depth - b.depth || a.index - b.index)
145
+ .map((c) => c.component);
146
+ }
@@ -1,9 +1,8 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { basename, dirname, join } from "node:path";
3
3
 
4
- import * as toml from "smol-toml";
5
-
6
4
  import { safeExistsSync } from "../core/fs.js";
5
+ import { parseToml } from "../core/toml.js";
7
6
 
8
7
  // Keep this list conservative and high-signal: it is meant to surface common
9
8
  // Rust native build helpers for audit triage, not to exhaustively model every
@@ -67,7 +66,7 @@ function createFormulationComponent(name, filePath, toolName) {
67
66
  function parseCargoManifest(filePath) {
68
67
  let cargoData;
69
68
  try {
70
- cargoData = toml.parse(readFileSync(filePath, { encoding: "utf-8" }));
69
+ cargoData = parseToml(readFileSync(filePath, { encoding: "utf-8" }));
71
70
  } catch {
72
71
  return undefined;
73
72
  }
@@ -252,7 +251,7 @@ function parseCargoManifest(filePath) {
252
251
  function parsePyProject(filePath) {
253
252
  let pyprojectData;
254
253
  try {
255
- pyprojectData = toml.parse(readFileSync(filePath, { encoding: "utf-8" }));
254
+ pyprojectData = parseToml(readFileSync(filePath, { encoding: "utf-8" }));
256
255
  } catch {
257
256
  return undefined;
258
257
  }
@@ -0,0 +1,153 @@
1
+ import { Purl } from "@cdxgen/cdx-purl";
2
+
3
+ // Scala libraries publish their Maven artifacts with a trailing Scala binary
4
+ // version suffix (`jwt-core_3`, `coursier_2.13`), optionally preceded by a
5
+ // platform suffix for Scala.js and Scala Native builds (`upickle_sjs1_3`,
6
+ // `upickle_native0.5_3`). sbt components strip the binary suffix and keep the
7
+ // platform suffix (`jwt-core`, `upickle_sjs1`), so the same library is named
8
+ // differently depending on the build tool that reported it.
9
+ const SCALA_BINARY_SUFFIX_REGEX = /_(2\.\d+|3)$/;
10
+ const SCALA_PLATFORM_SUFFIX_REGEX = /_(sjs1|sjs0\.6|native0\.\d+)$/;
11
+
12
+ /**
13
+ * Parse a Scala-published Maven artifact name into its coordinate parts.
14
+ *
15
+ * @param {string} name Maven artifactId, for example `upickle_sjs1_3`
16
+ * @returns {{ originalName: string, artifactBase: string, binaryVersion: string|null,
17
+ * platformSuffix: string, purlName: string }} The artifact name without any
18
+ * Scala suffix (`upickle`), the Scala binary version from the suffix (`3`),
19
+ * the platform suffix including the underscore (`_sjs1`) or an empty string,
20
+ * and the artifact name sbt-style purls emit (binary suffix dropped, platform
21
+ * suffix kept).
22
+ */
23
+ export function parseScalaArtifact(name) {
24
+ const result = {
25
+ originalName: name,
26
+ artifactBase: name,
27
+ binaryVersion: null,
28
+ platformSuffix: "",
29
+ purlName: name,
30
+ };
31
+ if (!name) {
32
+ return result;
33
+ }
34
+ let rest = name;
35
+ const binaryMatch = rest.match(SCALA_BINARY_SUFFIX_REGEX);
36
+ if (binaryMatch) {
37
+ result.binaryVersion = binaryMatch[1];
38
+ rest = rest.slice(0, rest.length - binaryMatch[0].length);
39
+ }
40
+ const platformMatch = rest.match(SCALA_PLATFORM_SUFFIX_REGEX);
41
+ if (platformMatch) {
42
+ result.platformSuffix = platformMatch[0];
43
+ rest = rest.slice(0, rest.length - platformMatch[0].length);
44
+ }
45
+ result.artifactBase = rest;
46
+ result.purlName = binaryMatch ? `${rest}${result.platformSuffix}` : name;
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * Build a qualifier-free coordinate key for a Scala-published artifact.
52
+ *
53
+ * The key ignores purl qualifiers such as `repository_url` and `type`, so the
54
+ * POM-coordinate keys of the jar namespace mapping and the sbt-style purls of
55
+ * the components resolve to the same entry.
56
+ *
57
+ * @param {Object} parts Coordinate parts
58
+ * @param {string} [parts.type] Purl type, defaults to `maven`
59
+ * @param {string} [parts.group] Maven groupId
60
+ * @param {string} parts.artifactBase Artifact name without any Scala suffix
61
+ * @param {string} [parts.platformSuffix] `_sjs1`, `_native0.5` or empty
62
+ * @param {string} [parts.version] Package version
63
+ * @returns {string} The coordinate key
64
+ */
65
+ export function scalaCoordinateKey({
66
+ type = "maven",
67
+ group = "",
68
+ artifactBase = "",
69
+ platformSuffix = "",
70
+ version = "",
71
+ } = {}) {
72
+ return [type, group, artifactBase, platformSuffix, version].join(":");
73
+ }
74
+
75
+ /**
76
+ * Build the qualifier-free coordinate key of a purl string.
77
+ *
78
+ * The artifact name is normalized with {@link parseScalaArtifact}, so
79
+ * `pkg:maven/g/foo_3@1.0` and the sbt-style `pkg:maven/g/foo@1.0` share a key,
80
+ * while the Scala.js build `foo_sjs1_3` keeps its own key (`foo` plus the
81
+ * `_sjs1` platform suffix) and never joins the plain `foo` component.
82
+ *
83
+ * @param {string} purlString Package URL to normalize
84
+ * @returns {string|undefined} The coordinate key, or undefined when the string
85
+ * is not a parseable purl
86
+ */
87
+ export function coordinateKeyFromPurl(purlString) {
88
+ return scalaCoordinateOfPurl(purlString)?.key;
89
+ }
90
+
91
+ /**
92
+ * The coordinate key of a purl together with the Scala binary version its
93
+ * artifact name carries, if any.
94
+ *
95
+ * @param {string} purlString Package URL to normalize
96
+ * @returns {{ key: string, binaryVersion: string|null }|undefined} The key
97
+ * and binary version, or undefined when the string is not a parseable purl
98
+ */
99
+ export function scalaCoordinateOfPurl(purlString) {
100
+ if (!purlString?.startsWith("pkg:")) {
101
+ return undefined;
102
+ }
103
+ let purlObj;
104
+ try {
105
+ purlObj = Purl.parse(purlString);
106
+ } catch (_err) {
107
+ return undefined;
108
+ }
109
+ if (!purlObj?.name) {
110
+ return undefined;
111
+ }
112
+ const scalaInfo = parseScalaArtifact(purlObj.name);
113
+ return {
114
+ key: scalaCoordinateKey({
115
+ type: purlObj.type,
116
+ group: purlObj.namespace || "",
117
+ artifactBase: scalaInfo.artifactBase,
118
+ platformSuffix: scalaInfo.platformSuffix,
119
+ version: purlObj.version || "",
120
+ }),
121
+ binaryVersion: scalaInfo.binaryVersion,
122
+ };
123
+ }
124
+
125
+ // An artifact name that already ends in a full Scala version, as scala-cli
126
+ // `:::` and sbt `CrossVersion.full` artifacts do (`_3.3.7`, `_2.13.16`). A
127
+ // binary suffix such as `_3` left on a name is part of the name: the
128
+ // component producers strip exactly one, into the compiler version.
129
+ const SCALA_FULL_VERSION_SUFFIX_REGEX = /_\d+\.\d+\.\d+(?:-[A-Za-z0-9.]+)?$/;
130
+
131
+ /**
132
+ * The artifactId the library is published under, for cache and repository
133
+ * lookups only. Components a Scala build tool reports carry the name with the
134
+ * Scala binary suffix stripped (the platform suffix kept) together with
135
+ * `cdx:scala:compilerVersion`; on disk and in the repository the artifact is
136
+ * `cats-core_2.13` or `upickle_sjs1_3`. Names that end in a full Scala version
137
+ * are used as they are.
138
+ *
139
+ * The component's own `name`, `purl` and `bom-ref` never change.
140
+ *
141
+ * @param {Object} pkg Package with `name` and `properties`.
142
+ * @returns {string} The artifactId to look up.
143
+ */
144
+ export function publishedArtifactId(pkg) {
145
+ const name = pkg?.name;
146
+ if (!name || SCALA_FULL_VERSION_SUFFIX_REGEX.test(name)) {
147
+ return name;
148
+ }
149
+ const binaryVersion = (pkg.properties || []).find(
150
+ (property) => property.name === "cdx:scala:compilerVersion",
151
+ )?.value;
152
+ return binaryVersion ? `${name}_${binaryVersion}` : name;
153
+ }
@@ -7,7 +7,6 @@ import { Purl } from "@cdxgen/cdx-purl";
7
7
  import { coerce, diff, prerelease } from "semver";
8
8
 
9
9
  import {
10
- cdxgenAgent,
11
10
  createDryRunError,
12
11
  DEBUG_MODE,
13
12
  hasDangerousUnicode,
@@ -24,11 +23,12 @@ import {
24
23
  } from "../core/fs.js";
25
24
  import { thoughtLog } from "../core/logger.js";
26
25
  import { isValidDriveRoot, isWin } from "../core/paths.js";
26
+ import { registerRunReset } from "../core/runState.js";
27
27
  import {
28
+ gatedGet,
28
29
  prefetchEnabled,
29
30
  prefetchedResponse,
30
31
  prefetchJson,
31
- withHostRateLimit,
32
32
  } from "./fetchBatch.js";
33
33
  import { canonicalPurlFromLooseString } from "./purl.js";
34
34
 
@@ -1165,6 +1165,8 @@ export function resetGitUrlSourcePrefetch() {
1165
1165
  gitUrlSourcePrefetch = new Map();
1166
1166
  }
1167
1167
 
1168
+ registerRunReset("source:registry-documents", resetGitUrlSourcePrefetch);
1169
+
1168
1170
  /**
1169
1171
  * Prefetch the registry documents a list of purls will be resolved from.
1170
1172
  *
@@ -1218,12 +1220,10 @@ async function fetchRegistryDocument(lookup) {
1218
1220
  // whose policy is one request per second however the request was reached.
1219
1221
  return (
1220
1222
  prefetchedResponse(gitUrlSourcePrefetch, lookup.url) ||
1221
- (await withHostRateLimit(lookup.url, () =>
1222
- cdxgenAgent.get(lookup.url, {
1223
- ...(lookup.accept ? { headers: { Accept: lookup.accept } } : {}),
1224
- responseType: "json",
1225
- }),
1226
- ))
1223
+ (await gatedGet(lookup.url, {
1224
+ ...(lookup.accept ? { headers: { Accept: lookup.accept } } : {}),
1225
+ responseType: "json",
1226
+ }))
1227
1227
  );
1228
1228
  }
1229
1229
 
@@ -343,12 +343,113 @@ export function findLicenseId(name) {
343
343
  : name;
344
344
  }
345
345
 
346
+ /**
347
+ * Identify a license from the title and opening of its full text. A license
348
+ * text quotes other licenses (the GNU GPL's text names the GNU Lesser GPL),
349
+ * so the substring scan of `guessLicenseId` alone can name the wrong one.
350
+ *
351
+ * @param {string} content License file contents
352
+ * @returns {string|undefined} SPDX license id or expression
353
+ */
354
+ export function licenseIdFromText(content) {
355
+ if (typeof content !== "string" || !content.trim()) {
356
+ return undefined;
357
+ }
358
+ const head = content.slice(0, 4000).replace(/\s+/g, " ").trim().toUpperCase();
359
+ const version = (prefix) => {
360
+ const at = head.indexOf(prefix);
361
+ if (at < 0) {
362
+ return undefined;
363
+ }
364
+ const tail = head.slice(at + prefix.length, at + prefix.length + 60);
365
+ const match = tail.match(/VERSION (\d(?:\.\d)?)/);
366
+ return match?.[1];
367
+ };
368
+ // the first lines name the license; quotations of others come later, so
369
+ // of the GNU licenses the one named first is the one the text is
370
+ const title = head.slice(0, 200);
371
+ const gnu = [
372
+ ["GNU AFFERO GENERAL PUBLIC LICENSE", { 3: "AGPL-3.0-only" }],
373
+ [
374
+ "GNU LESSER GENERAL PUBLIC LICENSE",
375
+ { 3: "LGPL-3.0-only", 2.1: "LGPL-2.1-only", 2: "LGPL-2.0-only" },
376
+ ],
377
+ [
378
+ "GNU LIBRARY GENERAL PUBLIC LICENSE",
379
+ { 2.1: "LGPL-2.1-only", 2: "LGPL-2.0-only" },
380
+ ],
381
+ [
382
+ "GNU GENERAL PUBLIC LICENSE",
383
+ { 3: "GPL-3.0-only", 2: "GPL-2.0-only", 1: "GPL-1.0-only" },
384
+ ],
385
+ ]
386
+ .map(([name, ids]) => ({ name, ids, at: title.indexOf(name) }))
387
+ .filter((t) => t.at >= 0)
388
+ .sort((a, b) => a.at - b.at)[0];
389
+ if (gnu) {
390
+ return gnu.ids[version(gnu.name)];
391
+ }
392
+ if (head.includes("APACHE LICENSE") && head.includes("VERSION 2.0")) {
393
+ return head.includes("LLVM EXCEPTION")
394
+ ? "Apache-2.0 WITH LLVM-exception"
395
+ : "Apache-2.0";
396
+ }
397
+ if (
398
+ title.includes("MOZILLA PUBLIC LICENSE") &&
399
+ head.includes("VERSION 2.0")
400
+ ) {
401
+ return "MPL-2.0";
402
+ }
403
+ if (title.includes("BOOST SOFTWARE LICENSE - VERSION 1.0")) {
404
+ return "BSL-1.0";
405
+ }
406
+ if (
407
+ head.includes(
408
+ "THIS IS FREE AND UNENCUMBERED SOFTWARE RELEASED INTO THE PUBLIC DOMAIN",
409
+ )
410
+ ) {
411
+ return "Unlicense";
412
+ }
413
+ if (head.includes("PERMISSION IS HEREBY GRANTED, FREE OF CHARGE")) {
414
+ return "MIT";
415
+ }
416
+ if (
417
+ head.includes(
418
+ "PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED",
419
+ )
420
+ ) {
421
+ return "ISC";
422
+ }
423
+ if (
424
+ head.includes("THIS SOFTWARE IS PROVIDED 'AS-IS'") &&
425
+ head.includes(
426
+ "PERMISSION IS GRANTED TO ANYONE TO USE THIS SOFTWARE FOR ANY PURPOSE",
427
+ )
428
+ ) {
429
+ return "Zlib";
430
+ }
431
+ if (head.includes("REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS")) {
432
+ if (head.includes("ALL ADVERTISING MATERIALS MENTIONING")) {
433
+ return "BSD-4-Clause";
434
+ }
435
+ return head.includes("NEITHER THE NAME") ||
436
+ head.includes("THE NAME OF THE AUTHOR MAY NOT BE USED")
437
+ ? "BSD-3-Clause"
438
+ : "BSD-2-Clause";
439
+ }
440
+ return undefined;
441
+ }
442
+
346
443
  /**
347
444
  * Method to guess the spdx license id from license contents
348
445
  *
349
446
  * @param {string} content License file contents
350
447
  */
351
448
  export function guessLicenseId(content) {
449
+ const fromText = licenseIdFromText(content);
450
+ if (fromText) {
451
+ return fromText;
452
+ }
352
453
  content = content.replace(/\n/g, " ");
353
454
  for (const l of licenseMapping) {
354
455
  for (const j in l.names) {
@@ -77,6 +77,7 @@ export function parseCommand(cmdStr) {
77
77
  export function resolveSaferExecBinary() {
78
78
  const currentPlatform = platform();
79
79
  const currentArch = arch();
80
+ const isWindows = currentPlatform === "win32";
80
81
  let pkgName = "";
81
82
 
82
83
  if (currentPlatform === "darwin") {
@@ -91,6 +92,14 @@ export function resolveSaferExecBinary() {
91
92
  } else if (currentArch === "arm64") {
92
93
  pkgName = "@cdxgen/safer-exec-linux-arm64";
93
94
  }
95
+ } else if (isWindows) {
96
+ // The Windows packages ship the MXC executor (wxc-exec.exe) next to the
97
+ // engine, which is where the engine looks for it.
98
+ if (currentArch === "x64") {
99
+ pkgName = "@cdxgen/safer-exec-windows-amd64";
100
+ } else if (currentArch === "arm64") {
101
+ pkgName = "@cdxgen/safer-exec-windows-arm64";
102
+ }
94
103
  }
95
104
 
96
105
  if (!pkgName) {
@@ -117,9 +126,14 @@ export function resolveSaferExecBinary() {
117
126
 
118
127
  for (const modulesDir of searchDirs) {
119
128
  // Direct structure under node_modules. The Go runtime inside the
120
- // platform package is bin/safer-exec-rt; "safer-exec" is the JS
129
+ // platform package is bin/safer-exec-rt(.exe); "safer-exec" is the JS
121
130
  // dispatcher's npm bin name, not a file on disk.
122
- const directPath = join(modulesDir, pkgName, "bin", "safer-exec-rt");
131
+ const directPath = join(
132
+ modulesDir,
133
+ pkgName,
134
+ "bin",
135
+ isWindows ? "safer-exec-rt.exe" : "safer-exec-rt",
136
+ );
123
137
  let realDirectPath;
124
138
  try {
125
139
  realDirectPath = realpathSync(directPath);
@@ -127,10 +141,12 @@ export function resolveSaferExecBinary() {
127
141
  realDirectPath = directPath;
128
142
  }
129
143
  if (existsSync(realDirectPath)) {
130
- try {
131
- chmodSync(realDirectPath, 0o755);
132
- } catch (_err) {
133
- // ignore
144
+ if (!isWindows) {
145
+ try {
146
+ chmodSync(realDirectPath, 0o755);
147
+ } catch (_err) {
148
+ // ignore
149
+ }
134
150
  }
135
151
  return realDirectPath;
136
152
  }
@@ -252,6 +268,14 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
252
268
  if (options.dryRun) {
253
269
  exec.enableDryRun();
254
270
  }
271
+ if (platform() === "win32" && options.blockInterpreters) {
272
+ // safer-exec reports --private-tmp and --protect-home on Windows when a
273
+ // grant defeats them, but --block-interpreters is macOS-only and stays
274
+ // silent (cdxgen/safer-exec#4).
275
+ console.warn(
276
+ "[cdxgen trace] --block-interpreters has no effect on Windows.",
277
+ );
278
+ }
255
279
  if (options.blockInterpreters) {
256
280
  exec.blockInterpreters();
257
281
  }
@@ -376,8 +400,41 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
376
400
 
377
401
  const result = await exec.run(cmd, args);
378
402
  const stderr = result?.stderr || "";
403
+ const { warnings, error: engineError } = engineMessages(stderr);
404
+ // Windows records DLL loads through an ETW session, which only an
405
+ // elevated process (or a Performance Log Users member) may start.
406
+ const etwDenied =
407
+ platform() === "win32" &&
408
+ warnings.some(
409
+ (line) =>
410
+ line.includes("DLL tracing:") &&
411
+ line.includes("administrator rights"),
412
+ );
413
+ // run() captures the engine's stderr, so a control the sandbox could not
414
+ // apply would otherwise read like one it did.
415
+ for (const line of warnings) {
416
+ if (etwDenied && line.includes("DLL tracing:")) {
417
+ continue;
418
+ }
419
+ console.warn(line);
420
+ }
421
+ if (etwDenied) {
422
+ console.warn(
423
+ "[cdxgen trace] library tracing on Windows needs an elevated prompt (or membership in the Performance Log Users group); the BOM has no library components. Egress services from --proxy-egress are still recorded.",
424
+ );
425
+ }
379
426
  if (result && result.exitCode !== 0) {
380
- if (stderr.includes("[safer-exec] Error:")) {
427
+ if (engineError) {
428
+ console.error(engineError);
429
+ if (options.strict) {
430
+ // Under --strict the sandbox refused the run; an empty BOM would
431
+ // read as a command that loaded nothing.
432
+ throw Object.assign(
433
+ new Error(`Tracing refused under --strict: ${engineError}`),
434
+ { strictRefusal: true },
435
+ );
436
+ }
437
+ } else if (stderr.includes("[safer-exec] Error:")) {
381
438
  console.error("Tracing launcher execution failed:", stderr.trim());
382
439
  } else {
383
440
  // A failing command usually means a partial trace (for example a
@@ -488,11 +545,35 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
488
545
  dryRunSummary,
489
546
  };
490
547
  } catch (err) {
548
+ if (err?.strictRefusal) {
549
+ throw err;
550
+ }
491
551
  console.error("Tracing command execution failed:", err);
492
552
  }
493
553
  return emptyResult;
494
554
  }
495
555
 
556
+ /**
557
+ * Picks the safer-exec engine's own lines out of the stderr it shares with the
558
+ * traced command. Warnings start with "safer-exec: warning:"; when the engine
559
+ * stops a run, its error is the last other "safer-exec: " line it prints.
560
+ *
561
+ * @param {string} stderr Captured stderr of a safer-exec run
562
+ * @returns {{warnings: string[], error: string|undefined}} Unique engine warnings, and the engine's error if any
563
+ */
564
+ export function engineMessages(stderr) {
565
+ const lines = String(stderr || "")
566
+ .split(/\r?\n/)
567
+ .filter((line) => line.startsWith("safer-exec: "));
568
+ const warnings = [
569
+ ...new Set(lines.filter((line) => line.startsWith("safer-exec: warning:"))),
570
+ ];
571
+ const others = lines.filter(
572
+ (line) => !line.startsWith("safer-exec: warning:"),
573
+ );
574
+ return { warnings, error: others.at(-1) };
575
+ }
576
+
496
577
  /**
497
578
  * Returns the sorted, de-duplicated parameter names of a query string.
498
579
  *