@cdxgen/cdxgen 13.4.0 → 13.6.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 (305) hide show
  1. package/README.md +9 -9
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +29 -5
  4. package/bin/evinse.js +8 -0
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +29 -0
  7. package/bin/validate.js +6 -0
  8. package/data/cbomosdb-queries.json +13 -1
  9. package/data/component-tags.json +8 -0
  10. package/data/license-aliases.json +24 -2
  11. package/data/queries-darwin.json +25 -1
  12. package/data/queries-win.json +19 -1
  13. package/data/queries.json +41 -0
  14. package/data/remediations.json +407 -1
  15. package/data/rules/ai-bom.yaml +1 -1
  16. package/data/rules/brotli-decoder.yaml +107 -0
  17. package/data/rules/cbom-security.yaml +235 -4
  18. package/data/rules/go-binaries.yaml +46 -0
  19. package/data/rules/golem-go.yaml +80 -0
  20. package/data/rules/hbom-compliance.yaml +40 -22
  21. package/data/rules/hbom-performance.yaml +7 -1
  22. package/data/rules/host-topology.yaml +1 -1
  23. package/data/rules/obom-runtime.yaml +31 -2
  24. package/data/rules/php-runtime.yaml +143 -0
  25. package/data/rules/vscode-extensions.yaml +75 -0
  26. package/data/spdx-license-list.json +121 -7
  27. package/data/spdx-licenses.json +69 -0
  28. package/data/spdx.schema.json +845 -2
  29. package/lib/audit/index.js +14 -0
  30. package/lib/cli/bomAssembly.js +120 -6
  31. package/lib/cli/cliOptions.js +110 -3
  32. package/lib/cli/index.js +182 -6
  33. package/lib/cli/jsBom.js +153 -64
  34. package/lib/cli/jvmBom.js +363 -66
  35. package/lib/cli/managedBom.js +853 -87
  36. package/lib/cli/nativeBom.js +267 -33
  37. package/lib/core/boundedFile.js +344 -0
  38. package/lib/core/brotliProbe.js +182 -0
  39. package/lib/core/env.js +38 -8
  40. package/lib/core/fs.js +40 -19
  41. package/lib/core/httpClient.js +172 -46
  42. package/lib/core/lockPaths.js +38 -0
  43. package/lib/core/paths.js +24 -1
  44. package/lib/core/processTree.js +5 -0
  45. package/lib/core/projectConfig.js +1 -0
  46. package/lib/core/projectTooling.js +97 -0
  47. package/lib/core/propertySanitizer.js +72 -0
  48. package/lib/core/toolRegistry.js +170 -0
  49. package/lib/ecosystems/cppEvidence.js +263 -106
  50. package/lib/ecosystems/cppIncludeResolver.js +16 -5
  51. package/lib/ecosystems/ecosystems.js +25 -10
  52. package/lib/ecosystems/gemHostProbe.js +301 -0
  53. package/lib/ecosystems/golem.js +550 -6
  54. package/lib/ecosystems/gradleutils.js +858 -244
  55. package/lib/ecosystems/parsers-bazel.js +107 -6
  56. package/lib/ecosystems/parsers-dotnet.js +117 -18
  57. package/lib/ecosystems/parsers-go.js +1090 -367
  58. package/lib/ecosystems/parsers-js.js +505 -22
  59. package/lib/ecosystems/parsers-jvm.js +259 -17
  60. package/lib/ecosystems/parsers-misc.js +438 -64
  61. package/lib/ecosystems/parsers-python.js +100 -12
  62. package/lib/ecosystems/pythonutils.js +128 -2
  63. package/lib/ecosystems/rubyutils.js +544 -34
  64. package/lib/ecosystems/rusi.js +10 -0
  65. package/lib/ecosystems/scalacliutils.js +4 -1
  66. package/lib/ecosystems/scalasem.js +798 -151
  67. package/lib/ecosystems/vendoredCode.js +237 -3
  68. package/lib/ecosystems/venvProbe.js +360 -0
  69. package/lib/evinser/evinser.js +472 -61
  70. package/lib/evinser/swiftsem.js +68 -5
  71. package/lib/helpers/agenticToolutils.js +413 -93
  72. package/lib/helpers/vsixutils.js +1574 -315
  73. package/lib/inventory/agentSkillSpec.js +17 -0
  74. package/lib/inventory/apkInstalledDb.js +243 -0
  75. package/lib/inventory/atomUtils.js +110 -0
  76. package/lib/inventory/auditCategories.js +3 -3
  77. package/lib/inventory/brotliDecoder.js +142 -0
  78. package/lib/inventory/buildToolRateLimit.js +1 -1
  79. package/lib/inventory/cacheDir.js +52 -9
  80. package/lib/inventory/cbomutils.js +56 -7
  81. package/lib/inventory/certificates.js +2371 -0
  82. package/lib/inventory/communityAiConfigParser.js +4 -9
  83. package/lib/inventory/deps.js +41 -4
  84. package/lib/inventory/display.js +200 -3
  85. package/lib/inventory/dosai.js +639 -63
  86. package/lib/inventory/dotnetRuntime.js +217 -0
  87. package/lib/inventory/dynamic.js +45 -3
  88. package/lib/inventory/envcontext.js +475 -18
  89. package/lib/inventory/evidenceUtils.js +447 -23
  90. package/lib/inventory/fetchBatch.js +1 -1
  91. package/lib/inventory/formulationParsers.js +32 -3
  92. package/lib/inventory/goBuildInfo.js +140 -0
  93. package/lib/inventory/gtfobins.js +13 -0
  94. package/lib/inventory/hbom.js +67 -7
  95. package/lib/inventory/hbomAnalysis.js +16 -0
  96. package/lib/inventory/hostRuntimeProbe.js +686 -0
  97. package/lib/inventory/hostTopology.js +21 -0
  98. package/lib/inventory/jvmLocalRepos.js +9 -2
  99. package/lib/inventory/jvmToolEnv.js +29 -0
  100. package/lib/inventory/licenseEnhancer.js +12 -0
  101. package/lib/inventory/mavenCentralMirror.js +534 -0
  102. package/lib/inventory/nodeBuildInfo.js +218 -0
  103. package/lib/inventory/nodeHostProbe.js +202 -0
  104. package/lib/inventory/osPackagePurl.js +244 -0
  105. package/lib/inventory/osPackageResolver.js +271 -87
  106. package/lib/inventory/osinfo.js +79 -31
  107. package/lib/inventory/osqueryTransform.js +114 -5
  108. package/lib/inventory/phpBuildInfo.js +438 -0
  109. package/lib/inventory/phpHostProbe.js +185 -0
  110. package/lib/inventory/rootfsCerts.js +426 -0
  111. package/lib/inventory/rubyBuildInfo.js +972 -0
  112. package/lib/inventory/rubyHostProbe.js +218 -0
  113. package/lib/inventory/scalaCoords.js +4 -3
  114. package/lib/inventory/scalaDedupe.js +440 -0
  115. package/lib/inventory/source.js +256 -5
  116. package/lib/inventory/spdx.js +144 -0
  117. package/lib/inventory/traceRunner.js +5 -1
  118. package/lib/inventory/tracedCrypto.js +129 -0
  119. package/lib/managers/binary.js +866 -186
  120. package/lib/managers/docker.js +245 -12
  121. package/lib/parsers/email.js +277 -0
  122. package/lib/parsers/largeJson.js +62 -13
  123. package/lib/parsers/vscodeExtensionId.js +183 -0
  124. package/lib/server/server.js +198 -2
  125. package/lib/stages/postgen/annotator.js +17 -1
  126. package/lib/stages/postgen/introspection/reflect.js +4 -1
  127. package/lib/stages/postgen/postgen.js +5 -5
  128. package/lib/stages/postgen/ruleEngine.js +71 -6
  129. package/lib/stages/postgen/specVersionCompat.js +30 -0
  130. package/lib/stages/pregen/envAudit.js +1 -0
  131. package/lib/stages/pregen/pregen.js +231 -62
  132. package/lib/validator/bomValidator.js +32 -6
  133. package/lib/validator/generated/runtime.mjs +13 -0
  134. package/lib/validator/generated/validate-1.6.mjs +27 -8
  135. package/lib/validator/generated/validate-1.7.mjs +32 -13
  136. package/lib/validator/generated/validate-2.0.mjs +31 -12
  137. package/lib/validator/index.js +1 -1
  138. package/package.json +19 -18
  139. package/types/bin/cdxgen-secure.d.ts +3 -0
  140. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  141. package/types/bin/repl.d.ts.map +1 -1
  142. package/types/lib/audit/index.d.ts.map +1 -1
  143. package/types/lib/cli/bomAssembly.d.ts +22 -3
  144. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  145. package/types/lib/cli/cliOptions.d.ts +43 -0
  146. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  147. package/types/lib/cli/index.d.ts.map +1 -1
  148. package/types/lib/cli/jsBom.d.ts +1 -17
  149. package/types/lib/cli/jsBom.d.ts.map +1 -1
  150. package/types/lib/cli/jvmBom.d.ts +20 -0
  151. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  152. package/types/lib/cli/managedBom.d.ts.map +1 -1
  153. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  154. package/types/lib/core/boundedFile.d.ts +70 -0
  155. package/types/lib/core/boundedFile.d.ts.map +1 -0
  156. package/types/lib/core/brotliProbe.d.ts +37 -0
  157. package/types/lib/core/brotliProbe.d.ts.map +1 -0
  158. package/types/lib/core/env.d.ts +22 -2
  159. package/types/lib/core/env.d.ts.map +1 -1
  160. package/types/lib/core/fs.d.ts.map +1 -1
  161. package/types/lib/core/httpClient.d.ts +24 -0
  162. package/types/lib/core/httpClient.d.ts.map +1 -1
  163. package/types/lib/core/lockPaths.d.ts +19 -0
  164. package/types/lib/core/lockPaths.d.ts.map +1 -0
  165. package/types/lib/core/processTree.d.ts +2 -0
  166. package/types/lib/core/processTree.d.ts.map +1 -1
  167. package/types/lib/core/projectConfig.d.ts.map +1 -1
  168. package/types/lib/core/projectTooling.d.ts +41 -0
  169. package/types/lib/core/projectTooling.d.ts.map +1 -0
  170. package/types/lib/core/propertySanitizer.d.ts +26 -0
  171. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  172. package/types/lib/core/toolRegistry.d.ts +36 -0
  173. package/types/lib/core/toolRegistry.d.ts.map +1 -0
  174. package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
  175. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  176. package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
  177. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
  178. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  179. package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
  180. package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
  181. package/types/lib/ecosystems/golem.d.ts +81 -1
  182. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  183. package/types/lib/ecosystems/gradleutils.d.ts +57 -21
  184. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  185. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
  186. package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
  187. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  188. package/types/lib/ecosystems/parsers-go.d.ts +85 -13
  189. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-js.d.ts +17 -20
  191. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  192. package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
  193. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  194. package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
  195. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  196. package/types/lib/ecosystems/parsers-python.d.ts +14 -3
  197. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  198. package/types/lib/ecosystems/pythonutils.d.ts +19 -1
  199. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  200. package/types/lib/ecosystems/rubyutils.d.ts +144 -12
  201. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  202. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  203. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  205. package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
  206. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
  207. package/types/lib/ecosystems/venvProbe.d.ts +40 -0
  208. package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
  209. package/types/lib/evinser/evinser.d.ts +88 -8
  210. package/types/lib/evinser/evinser.d.ts.map +1 -1
  211. package/types/lib/evinser/swiftsem.d.ts +10 -0
  212. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  213. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  214. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  215. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  216. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  217. package/types/lib/inventory/atomUtils.d.ts +33 -0
  218. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  219. package/types/lib/inventory/brotliDecoder.d.ts +58 -0
  220. package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
  221. package/types/lib/inventory/cacheDir.d.ts +12 -4
  222. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  223. package/types/lib/inventory/cbomutils.d.ts +17 -0
  224. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  225. package/types/lib/inventory/certificates.d.ts +205 -0
  226. package/types/lib/inventory/certificates.d.ts.map +1 -0
  227. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  228. package/types/lib/inventory/deps.d.ts +2 -1
  229. package/types/lib/inventory/deps.d.ts.map +1 -1
  230. package/types/lib/inventory/display.d.ts +18 -0
  231. package/types/lib/inventory/display.d.ts.map +1 -1
  232. package/types/lib/inventory/dosai.d.ts +162 -7
  233. package/types/lib/inventory/dosai.d.ts.map +1 -1
  234. package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
  235. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
  236. package/types/lib/inventory/dynamic.d.ts +6 -1
  237. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  238. package/types/lib/inventory/envcontext.d.ts +92 -8
  239. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  240. package/types/lib/inventory/evidenceUtils.d.ts +29 -10
  241. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  242. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  243. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  244. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  245. package/types/lib/inventory/gtfobins.d.ts.map +1 -1
  246. package/types/lib/inventory/hbom.d.ts.map +1 -1
  247. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  248. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  249. package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
  250. package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
  251. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  252. package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
  253. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  254. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
  255. package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
  256. package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
  257. package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
  258. package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
  259. package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
  260. package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
  261. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  262. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  263. package/types/lib/inventory/osPackageResolver.d.ts +44 -2
  264. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  265. package/types/lib/inventory/osinfo.d.ts +31 -6
  266. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  267. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  268. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  269. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  270. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  271. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  272. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  273. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  274. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  275. package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
  276. package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
  277. package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
  278. package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
  279. package/types/lib/inventory/source.d.ts +38 -0
  280. package/types/lib/inventory/source.d.ts.map +1 -1
  281. package/types/lib/inventory/spdx.d.ts +15 -0
  282. package/types/lib/inventory/spdx.d.ts.map +1 -1
  283. package/types/lib/inventory/traceRunner.d.ts +4 -1
  284. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  285. package/types/lib/inventory/tracedCrypto.d.ts +22 -0
  286. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
  287. package/types/lib/managers/binary.d.ts +10 -0
  288. package/types/lib/managers/binary.d.ts.map +1 -1
  289. package/types/lib/managers/docker.d.ts +24 -0
  290. package/types/lib/managers/docker.d.ts.map +1 -1
  291. package/types/lib/parsers/email.d.ts +19 -0
  292. package/types/lib/parsers/email.d.ts.map +1 -0
  293. package/types/lib/parsers/largeJson.d.ts +17 -3
  294. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  295. package/types/lib/server/server.d.ts +8 -0
  296. package/types/lib/server/server.d.ts.map +1 -1
  297. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  298. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  299. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  300. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  301. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  302. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  303. package/types/lib/stages/pregen/pregen.d.ts +26 -9
  304. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  305. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -1,10 +1,12 @@
1
1
  import fs from "node:fs";
2
+ import { builtinModules } from "node:module";
2
3
  import path, { basename, join, resolve } from "node:path";
3
4
  import process from "node:process";
4
5
 
5
6
  import { Purl } from "@cdxgen/cdx-purl";
6
7
 
7
8
  import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
9
+ import { recordDegradation } from "../core/buildLedger.js";
8
10
  import { PROJECT_TYPE_ALIASES } from "../core/env.js";
9
11
  import {
10
12
  getAllFiles,
@@ -15,6 +17,7 @@ import {
15
17
  safeRmSync,
16
18
  safeWriteSync,
17
19
  } from "../core/fs.js";
20
+ import { isProjectToolingAllowed } from "../core/projectTooling.js";
18
21
  import { hasCppSources } from "../ecosystems/cppEvidence.js";
19
22
  import {
20
23
  createIncludeAttributor,
@@ -23,11 +26,11 @@ import {
23
26
  import {
24
27
  analyzeGolemProject,
25
28
  collectGolemEvidence,
29
+ collectGolemServices,
26
30
  isGolemGoLanguage,
27
31
  } from "../ecosystems/golem.js";
28
32
  import {
29
33
  collectGradleDependencies,
30
- getGradleCommand,
31
34
  getMavenCommand,
32
35
  } from "../ecosystems/gradleutils.js";
33
36
  import {
@@ -52,6 +55,8 @@ import {
52
55
  isScalasemLanguage,
53
56
  } from "../ecosystems/scalasem.js";
54
57
  import {
58
+ atomBomFileArgs,
59
+ atomBuildToolArgs,
55
60
  atomCompileCommandsArgs,
56
61
  buildAtomCommandEnv,
57
62
  executeAtom,
@@ -77,7 +82,10 @@ import {
77
82
  collectDosaiServicesFromMethods,
78
83
  createDosaiDataFlowSlice,
79
84
  createDosaiMethodsSlice,
80
- dosaiRunWasStopped,
85
+ dosaiFailureReported,
86
+ dosaiOmittedSections,
87
+ dosaiReportFileOmittedSections,
88
+ dosaiSectionsCdxgenReads,
81
89
  isDosaiDotnetLanguage,
82
90
  normalizeDosaiServiceMap,
83
91
  persistDosaiSemanticsReport,
@@ -85,6 +93,7 @@ import {
85
93
  readDosaiMethodsReport,
86
94
  } from "../inventory/dosai.js";
87
95
  import { parseOccurrenceEvidenceLocation } from "../inventory/evidenceUtils.js";
96
+ import { splitPurlLoose } from "../inventory/purl.js";
88
97
  import { executeOsQuery } from "../managers/binary.js";
89
98
  import { MAX_JSON_TEXT_BYTES, readJsonFile } from "../parsers/largeJson.js";
90
99
  import { postProcess } from "../stages/postgen/postgen.js";
@@ -92,7 +101,8 @@ import { createOrLoad } from "./db.js";
92
101
  import { findPurlLocations } from "./scalasem.js";
93
102
  import { createSemanticsSlices } from "./swiftsem.js";
94
103
 
95
- const typePurlsCache = {};
104
+ // keyed by type names, which can be "constructor", "toString", ...: no prototype
105
+ const typePurlsCache = Object.create(null);
96
106
 
97
107
  function appendUniqueProperty(properties, name, value) {
98
108
  if (value === undefined || value === null || value === "") {
@@ -255,6 +265,7 @@ export function buildEvinseOptions(options, args, input) {
255
265
  force: false,
256
266
  withReachables: options.deep,
257
267
  usagesSlicesFile: options.usagesSlicesFile,
268
+ callsitesSlicesFile: options.callsitesSlicesFile,
258
269
  dataFlowSlicesFile: options.dataFlowSlicesFile,
259
270
  reachablesSlicesFile: options.reachablesSlicesFile,
260
271
  semanticsSlicesFile: options.semanticsSlicesFile,
@@ -401,9 +412,25 @@ export async function catalogMavenDeps(
401
412
  options = {},
402
413
  ) {
403
414
  let jarNSMapping = readNamespaceMap(dirPath, options);
404
- if (!jarNSMapping) {
415
+ if (!jarNSMapping && !isProjectToolingAllowed(options)) {
416
+ // `mvn dependency:copy-dependencies` evaluates the project's build and
417
+ // its `.mvn` configuration, so only the local repository is read.
418
+ console.log(
419
+ options.withDeepJarCollector
420
+ ? "Skipping 'mvn dependency:copy-dependencies' since dependency installation is disabled. Only the jars already in the local Maven repository are used."
421
+ : "Skipping 'mvn dependency:copy-dependencies' since dependency installation is disabled. Pass --with-deep-jar-collector to use the jars already in the local Maven repository.",
422
+ );
423
+ if (options.withDeepJarCollector) {
424
+ jarNSMapping = await collectMvnDependencies(
425
+ undefined,
426
+ undefined,
427
+ true,
428
+ true,
429
+ );
430
+ }
431
+ } else if (!jarNSMapping) {
405
432
  console.log("About to collect jar dependencies for the path", dirPath);
406
- const mavenCmd = getMavenCommand(dirPath, dirPath);
433
+ const mavenCmd = getMavenCommand(dirPath, dirPath, options);
407
434
  // collect all jars including from the cache if data-flow mode is enabled
408
435
  jarNSMapping = await collectMvnDependencies(
409
436
  mavenCmd,
@@ -457,10 +484,10 @@ export async function catalogGradleDeps(
457
484
  console.log(
458
485
  "About to collect jar dependencies from the gradle cache. This would take a while ...",
459
486
  );
460
- const gradleCmd = getGradleCommand(dirPath, dirPath);
461
- // collect all jars including from the cache if data-flow mode is enabled
487
+ // Only the Gradle cache is read, so no Gradle command is resolved: that
488
+ // would probe the project's own gradlew.
462
489
  jarNSMapping = await collectGradleDependencies(
463
- gradleCmd,
490
+ undefined,
464
491
  dirPath,
465
492
  false,
466
493
  true,
@@ -645,9 +672,9 @@ export async function createSlice(
645
672
  resolve(slicesFile),
646
673
  options,
647
674
  );
648
- // A stopped run has already said why; it says nothing about whether the
649
- // project is supported (issue 4438).
650
- if (!sliceResult && !dosaiRunWasStopped(resolve(slicesFile))) {
675
+ // A stopped run, or one the .NET host refused to start, has already said
676
+ // why; it says nothing about whether the project is supported (issue 4438).
677
+ if (!sliceResult && !dosaiFailureReported(resolve(slicesFile))) {
651
678
  console.warn(
652
679
  `Unable to generate ${sliceType} slice using dosai. Check if this is a supported .NET project.`,
653
680
  );
@@ -658,6 +685,13 @@ export async function createSlice(
658
685
  slicesFile,
659
686
  };
660
687
  }
688
+ const buildToolArgs = atomBuildToolArgs(language, options);
689
+ if (!buildToolArgs) {
690
+ console.log(
691
+ `Skipping the ${sliceType} slice for ${resolve(filePath)} since dependency installation is disabled and the installed atom cannot be stopped from running Maven. Upgrade atom to slice this project without its build.`,
692
+ );
693
+ return { tempDir: sliceOutputDir, tempDirOwned };
694
+ }
661
695
  console.log(
662
696
  `Creating ${sliceType} slice for ${resolve(filePath)}. Please wait ...`,
663
697
  );
@@ -705,7 +739,9 @@ export async function createSlice(
705
739
  args.push("--slice-depth");
706
740
  args.push(readEnvironmentVariable("ATOM_SLICE_DEPTH"));
707
741
  }
742
+ args.push(...atomBomFileArgs(sliceType, options));
708
743
  args.push(...atomCompileCommandsArgs(filePath, language, options));
744
+ args.push(...buildToolArgs);
709
745
  args.push(resolve(filePath));
710
746
  const atomExcludeEnv = buildAtomCommandEnv(options, language);
711
747
  // Execute atom
@@ -1143,6 +1179,49 @@ export function mergeServiceDefinitions(target, source) {
1143
1179
  return target;
1144
1180
  }
1145
1181
 
1182
+ /**
1183
+ * Collect the services of a Go project for the golem evidence path (#4498).
1184
+ *
1185
+ * Two sources are kept and merged by service name, the way the Scala path
1186
+ * merges a user-provided spec into its report's services. They roll services
1187
+ * up differently — golem per server, OpenAPI per path and method — so a
1188
+ * route both name appears under both entries:
1189
+ *
1190
+ * - the golem report itself, whose `services[]` section rolls the registered
1191
+ * routes up per server (a gin engine, a net/http listener) with methods and
1192
+ * frameworks, and the outbound hosts it calls into `kind: "external"`
1193
+ * services flagged as trust-boundary crossings;
1194
+ * - an OpenAPI document, either the one `--openapi-spec-file` names or the
1195
+ * `openapi.json` an earlier `atom-tools convert -t go` left in the repo,
1196
+ * which is reused when present so `golem`/`atom-tools` and `cdxgen` can be
1197
+ * run as separate steps.
1198
+ *
1199
+ * @param {object|null|undefined} golemReport Parsed golem analysis report,
1200
+ * when one was produced; a missing or `null` report adds no services.
1201
+ * @param {string} dirPath Project directory the report was produced from.
1202
+ * @param {Object} options Command line options.
1203
+ * @param {Object} [servicesMap={}] Service map to populate.
1204
+ * @returns {Object} The service map
1205
+ */
1206
+ export function collectGoServices(
1207
+ golemReport,
1208
+ dirPath,
1209
+ options = {},
1210
+ servicesMap = {},
1211
+ ) {
1212
+ servicesMap = collectGolemServices(golemReport, servicesMap);
1213
+ const openapiSpecFile = resolve(
1214
+ dirPath,
1215
+ options.openapiSpecFile || "openapi.json",
1216
+ );
1217
+ if (safeExistsSync(openapiSpecFile)) {
1218
+ const openapiServices = {};
1219
+ detectServicesFromOpenAPI("go", openapiSpecFile, openapiServices);
1220
+ mergeServiceDefinitions(servicesMap, openapiServices);
1221
+ }
1222
+ return servicesMap;
1223
+ }
1224
+
1146
1225
  /**
1147
1226
  * Function to analyze the project
1148
1227
  *
@@ -1183,6 +1262,7 @@ export async function analyzeProject(dbObjMap, options) {
1183
1262
 
1184
1263
  if (isGolemGoLanguage(language)) {
1185
1264
  const golemReport = analyzeGolemProject(dirPath, options);
1265
+ servicesMap = collectGoServices(golemReport, dirPath, options, servicesMap);
1186
1266
  if (golemReport && Object.keys(golemReport).length) {
1187
1267
  const golemEvidence = collectGolemEvidence(golemReport, [
1188
1268
  bomJson.metadata?.component,
@@ -1329,24 +1409,58 @@ export async function analyzeProject(dbObjMap, options) {
1329
1409
  options.withDataFlow = true;
1330
1410
  options.includeCrypto = true;
1331
1411
  }
1412
+ let needsUsagesRun = true;
1332
1413
  if (
1333
1414
  options.usagesSlicesFile &&
1334
1415
  usableSlicesFile(options.usagesSlicesFile)
1335
1416
  ) {
1336
1417
  usageSlice = readDosaiMethodsReport(options.usagesSlicesFile);
1337
1418
  usagesSlicesFile = options.usagesSlicesFile;
1419
+ needsUsagesRun = false;
1420
+ // A user-supplied usages slice is used as given. When it was written
1421
+ // with --sections and left out a section cdxgen reads, the evidence
1422
+ // lacks that input; persistDosaiSemanticsReport reports separately
1423
+ // that the persisted report is partial.
1424
+ const missingInputs = dosaiSectionsCdxgenReads(
1425
+ dosaiOmittedSections(usageSlice),
1426
+ );
1427
+ if (missingInputs.length) {
1428
+ recordDegradation("dosai.usages.trimmed-report", {
1429
+ ecosystem: "csharp",
1430
+ tool: "dosai",
1431
+ impact: "components",
1432
+ command: `usages ${usagesSlicesFile}`,
1433
+ detail: `The supplied dosai methods report was written without sections cdxgen reads: ${missingInputs.join(", ")}. The evidence from those sections is missing. Run dosai methods without --sections, or with every section cdxgen reads.`,
1434
+ });
1435
+ }
1338
1436
  } else if (usableSlicesFile(options.depsSlicesFile)) {
1339
1437
  // createCsharpBom already ran dosai methods on this tree for --deep runs
1340
1438
  // and wrote the deps slice. For dosai, that output IS the usages slice,
1341
- // so reuse it instead of invoking dosai a second time.
1342
- if (DEBUG_MODE) {
1343
- console.log(
1344
- `Reusing the dosai deps slices file "${options.depsSlicesFile}" for the usages analysis.`,
1345
- );
1439
+ // so reuse it instead of invoking dosai a second time - unless it was
1440
+ // written with --sections (a --deep run that performs no evidence
1441
+ // analysis trims it): evinse persists this file as the full native
1442
+ // report, so it must not build on a trimmed one and dosai runs again.
1443
+ const omittedSections = dosaiReportFileOmittedSections(
1444
+ options.depsSlicesFile,
1445
+ );
1446
+ if (omittedSections.length) {
1447
+ if (DEBUG_MODE) {
1448
+ console.log(
1449
+ `The dosai deps slices file "${options.depsSlicesFile}" omits sections (${omittedSections.join(", ")}); running dosai for the full usages report.`,
1450
+ );
1451
+ }
1452
+ } else {
1453
+ if (DEBUG_MODE) {
1454
+ console.log(
1455
+ `Reusing the dosai deps slices file "${options.depsSlicesFile}" for the usages analysis.`,
1456
+ );
1457
+ }
1458
+ usageSlice = readDosaiMethodsReport(options.depsSlicesFile);
1459
+ usagesSlicesFile = options.depsSlicesFile;
1460
+ needsUsagesRun = false;
1346
1461
  }
1347
- usageSlice = readDosaiMethodsReport(options.depsSlicesFile);
1348
- usagesSlicesFile = options.depsSlicesFile;
1349
- } else {
1462
+ }
1463
+ if (needsUsagesRun) {
1350
1464
  retMap = await createSlice(language, dirPath, "usages", options);
1351
1465
  if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
1352
1466
  usageSlice = readDosaiMethodsReport(retMap.slicesFile);
@@ -1465,6 +1579,7 @@ export async function analyzeProject(dbObjMap, options) {
1465
1579
  cryptoGeneratePurls = retMap.cryptoGeneratePurls;
1466
1580
  }
1467
1581
  // Reuse existing usages slices
1582
+ let usagesAtomFile;
1468
1583
  if (options.usagesSlicesFile && usableSlicesFile(options.usagesSlicesFile)) {
1469
1584
  usageSlice = filterAtomSliceData(
1470
1585
  readSlicesFile(options.usagesSlicesFile, "usages"),
@@ -1474,6 +1589,7 @@ export async function analyzeProject(dbObjMap, options) {
1474
1589
  } else {
1475
1590
  // Generate our own slices
1476
1591
  retMap = await createSlice(language, dirPath, "usages", options);
1592
+ usagesAtomFile = retMap?.atomFile || "";
1477
1593
  if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
1478
1594
  usageSlice = filterAtomSliceData(
1479
1595
  readSlicesFile(retMap.slicesFile, "usages"),
@@ -1553,6 +1669,35 @@ export async function analyzeProject(dbObjMap, options) {
1553
1669
  servicesMap = retMap.servicesMap;
1554
1670
  userDefinedTypesMap = retMap.userDefinedTypesMap;
1555
1671
  }
1672
+ // Parse the callsites slice (JS/TS): one record per external use site, each
1673
+ // record's own (file, line) is an occurrence of the module's package. The
1674
+ // usages slice above still provides services and endpoints.
1675
+ if (JS_LANGUAGES.includes(language)) {
1676
+ let callsitesSlice;
1677
+ if (
1678
+ options.callsitesSlicesFile &&
1679
+ usableSlicesFile(options.callsitesSlicesFile)
1680
+ ) {
1681
+ callsitesSlice = readSlicesFile(options.callsitesSlicesFile, "callsites");
1682
+ } else if (
1683
+ usagesAtomFile !== undefined &&
1684
+ !safeExistsSync(usagesAtomFile)
1685
+ ) {
1686
+ // The usages run above built no atom (stopped at the timeout, or atom
1687
+ // failed); a callsites run would rebuild the same atom and fail the same way.
1688
+ console.log(
1689
+ "Skipping the callsites slice since atom could not build the atom for the usages slice.",
1690
+ );
1691
+ } else {
1692
+ retMap = await createSlice(language, dirPath, "callsites", options);
1693
+ if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
1694
+ callsitesSlice = readSlicesFile(retMap.slicesFile, "callsites");
1695
+ }
1696
+ }
1697
+ if (callsitesSlice?.callsites?.length) {
1698
+ attributeJsCallsites(callsitesSlice, purlImportsMap, purlLocationMap);
1699
+ }
1700
+ }
1556
1701
  // A component whose calls the slices do not show (code inside the project,
1557
1702
  // such as a vendored library) is still used where its header is included
1558
1703
  for (const [purl, includeLocations] of cIncludeLocations) {
@@ -1860,8 +2005,15 @@ export async function parseSliceUsages(
1860
2005
  ) {
1861
2006
  const fileName = slice.fileName;
1862
2007
  const typesToLookup = new Set();
1863
- const lKeyOverrides = {};
1864
- const usages = slice.usages || [];
2008
+ // keyed by type names, which can be "constructor", "toString", ...: no prototype
2009
+ const lKeyOverrides = Object.create(null);
2010
+ // A user defined type has no usages; its fields' types are evidence at the fields' lines.
2011
+ const usages =
2012
+ slice.usages ||
2013
+ (slice.fields || [])
2014
+ .filter((f) => f?.typeFullName)
2015
+ .map((f) => ({ targetObj: f }));
2016
+ const isJs = JS_LANGUAGES.includes(language);
1865
2017
  // What should be the line number to use. slice.lineNumber would be quite coarse and could lead to reports such as
1866
2018
  // #1670. Line numbers under targetObj and definedBy is a safe bet for dynamic languages, but occassionally leads to
1867
2019
  // confusion when inter-procedural tracking works better than expected.
@@ -1884,13 +2036,27 @@ export async function parseSliceUsages(
1884
2036
  sliceLineNumber ||
1885
2037
  ausage?.targetObj?.lineNumber ||
1886
2038
  ausage?.definedBy?.lineNumber;
2039
+ // JavaScript entries name the imported module they resolve to; attribute those by package
2040
+ // and keep the symbol-based matching below for entries without one.
2041
+ if (isJs) {
2042
+ attributeJsModules(ausage, fileName, purlImportsMap, purlLocationMap);
2043
+ }
2044
+ const viaSymbols = (o) => !(isJs && o?.module);
1887
2045
  // First capture the types in the targetObj and definedBy
1888
2046
  for (const atype of [
1889
- [ausage?.targetObj?.isExternal, ausage?.targetObj?.typeFullName],
1890
- [ausage?.targetObj?.isExternal, ausage?.targetObj?.resolvedMethod],
1891
- [ausage?.definedBy?.name?.includes("::"), ausage?.definedBy?.name],
1892
- [ausage?.definedBy?.isExternal, ausage?.definedBy?.typeFullName],
1893
- [ausage?.definedBy?.isExternal, ausage?.definedBy?.resolvedMethod],
2047
+ ...(viaSymbols(ausage?.targetObj)
2048
+ ? [
2049
+ [ausage?.targetObj?.isExternal, ausage?.targetObj?.typeFullName],
2050
+ [ausage?.targetObj?.isExternal, ausage?.targetObj?.resolvedMethod],
2051
+ ]
2052
+ : []),
2053
+ ...(viaSymbols(ausage?.definedBy)
2054
+ ? [
2055
+ [ausage?.definedBy?.name?.includes("::"), ausage?.definedBy?.name],
2056
+ [ausage?.definedBy?.isExternal, ausage?.definedBy?.typeFullName],
2057
+ [ausage?.definedBy?.isExternal, ausage?.definedBy?.resolvedMethod],
2058
+ ]
2059
+ : []),
1894
2060
  ...(ausage?.fields || []).map((f) => [f?.isExternal, f?.typeFullName]),
1895
2061
  ]) {
1896
2062
  if (
@@ -1931,7 +2097,8 @@ export async function parseSliceUsages(
1931
2097
  for (const acall of []
1932
2098
  .concat(ausage?.invokedCalls || [])
1933
2099
  .concat(ausage?.argToCalls || [])
1934
- .concat(ausage?.procedures || [])) {
2100
+ .concat(ausage?.procedures || [])
2101
+ .filter(viaSymbols)) {
1935
2102
  if (
1936
2103
  acall.resolvedMethod?.startsWith("@") ||
1937
2104
  acall?.callName?.includes("::")
@@ -2031,9 +2198,7 @@ export async function parseSliceUsages(
2031
2198
  if (!purlLocationMap[apurl]) {
2032
2199
  purlLocationMap[apurl] = new Set();
2033
2200
  }
2034
- if (lKeyOverrides[atype]) {
2035
- purlLocationMap[apurl].add(...lKeyOverrides[atype]);
2036
- }
2201
+ addAllLocations(purlLocationMap[apurl], lKeyOverrides[atype]);
2037
2202
  }
2038
2203
  }
2039
2204
  } else {
@@ -2041,9 +2206,7 @@ export async function parseSliceUsages(
2041
2206
  if (!purlLocationMap[apurl]) {
2042
2207
  purlLocationMap[apurl] = new Set();
2043
2208
  }
2044
- if (lKeyOverrides[atype]) {
2045
- purlLocationMap[apurl].add(...lKeyOverrides[atype]);
2046
- }
2209
+ addAllLocations(purlLocationMap[apurl], lKeyOverrides[atype]);
2047
2210
  }
2048
2211
  }
2049
2212
  }
@@ -2065,9 +2228,7 @@ export async function parseSliceUsages(
2065
2228
  if (!purlLocationMap[ns.purl]) {
2066
2229
  purlLocationMap[ns.purl] = new Set();
2067
2230
  }
2068
- if (lKeyOverrides[atype]) {
2069
- purlLocationMap[ns.purl].add(...lKeyOverrides[atype]);
2070
- }
2231
+ addAllLocations(purlLocationMap[ns.purl], lKeyOverrides[atype]);
2071
2232
  }
2072
2233
  typePurlsCache[atype] = nsHits;
2073
2234
  } else {
@@ -2475,6 +2636,17 @@ export function detectServicesFromOpenAPI(
2475
2636
  * @param {Array} slice Usages array for each objectSlice
2476
2637
  * @param {Object} servicesMap Existing service map
2477
2638
  */
2639
+ /**
2640
+ * The text routes and endpoints are read from: JavaScript slices keep a call's source text in
2641
+ * `code` and the resolved callee name in `resolvedMethod`; other languages have only the latter.
2642
+ *
2643
+ * @param {Object|undefined} entry slice entry
2644
+ * @returns {string|undefined} text
2645
+ */
2646
+ function sourceTextOf(entry) {
2647
+ return entry?.code || entry?.resolvedMethod;
2648
+ }
2649
+
2478
2650
  export function detectServicesFromUsages(language, slice, servicesMap = {}) {
2479
2651
  const usages = slice.usages;
2480
2652
  if (!usages) {
@@ -2487,30 +2659,30 @@ export function detectServicesFromUsages(language, slice, servicesMap = {}) {
2487
2659
  let authenticated;
2488
2660
  if (language === "ruby" && definedBy?.name?.includes("/")) {
2489
2661
  endpoints = extractEndpoints(language, definedBy.name);
2490
- } else if (targetObj?.resolvedMethod) {
2662
+ } else if (sourceTextOf(targetObj)) {
2491
2663
  if (language !== "php") {
2492
- endpoints = extractEndpoints(language, targetObj?.resolvedMethod);
2664
+ endpoints = extractEndpoints(language, sourceTextOf(targetObj));
2493
2665
  }
2494
- if (targetObj?.resolvedMethod.toLowerCase().includes("auth")) {
2666
+ if (sourceTextOf(targetObj).toLowerCase().includes("auth")) {
2495
2667
  authenticated = true;
2496
2668
  }
2497
- } else if (definedBy?.resolvedMethod) {
2669
+ } else if (sourceTextOf(definedBy)) {
2498
2670
  if (language !== "php") {
2499
- endpoints = extractEndpoints(language, definedBy?.resolvedMethod);
2671
+ endpoints = extractEndpoints(language, sourceTextOf(definedBy));
2500
2672
  }
2501
- if (definedBy?.resolvedMethod.toLowerCase().includes("auth")) {
2673
+ if (sourceTextOf(definedBy).toLowerCase().includes("auth")) {
2502
2674
  authenticated = true;
2503
2675
  }
2504
2676
  }
2505
2677
  if (usage.invokedCalls) {
2506
2678
  for (const acall of usage.invokedCalls) {
2507
- if (acall.resolvedMethod) {
2679
+ if (sourceTextOf(acall)) {
2508
2680
  if (language !== "php") {
2509
2681
  const tmpEndpoints = extractEndpoints(
2510
2682
  language,
2511
- acall.resolvedMethod,
2683
+ sourceTextOf(acall),
2512
2684
  );
2513
- if (acall.resolvedMethod.toLowerCase().includes("auth")) {
2685
+ if (sourceTextOf(acall).toLowerCase().includes("auth")) {
2514
2686
  authenticated = true;
2515
2687
  }
2516
2688
  if (tmpEndpoints?.length) {
@@ -2716,6 +2888,72 @@ export function extractEndpoints(language, code) {
2716
2888
  return endpoints;
2717
2889
  }
2718
2890
 
2891
+ /**
2892
+ * Key naming the package a purl refers to, independent of its version,
2893
+ * qualifiers and subpath. Namespace and name are the decoded purl segments,
2894
+ * so an encoded scope (`%40angular/common`) keys like a plain one.
2895
+ *
2896
+ * @param {string} purl Purl string
2897
+ * @returns {string|undefined} `type/namespace/name`, or undefined for a non-purl
2898
+ */
2899
+ export function versionlessPurlKey(purl) {
2900
+ const parts = splitPurlLoose(purl);
2901
+ if (!parts) {
2902
+ return undefined;
2903
+ }
2904
+ return `${parts.type}/${parts.namespace || ""}/${parts.name}`;
2905
+ }
2906
+
2907
+ /**
2908
+ * Index the data-flow frames recorded under versionless flow purls by
2909
+ * {@link versionlessPurlKey}. A flow purl without a version attaches to every
2910
+ * BOM component of the same package name, whatever its version (maintainer
2911
+ * decision, 2026-10-27); chen leaves the version off exactly when it cannot
2912
+ * pick one between apps that pin the package differently.
2913
+ *
2914
+ * @param {Object} dataFlowFrames Frames keyed by flow purl
2915
+ * @returns {Object} Frames keyed by versionless purl key
2916
+ */
2917
+ export function indexVersionlessPurls(dataFlowFrames) {
2918
+ const index = {};
2919
+ for (const [purl, frames] of Object.entries(dataFlowFrames || {})) {
2920
+ if (splitPurlLoose(purl)?.version === null) {
2921
+ const key = versionlessPurlKey(purl);
2922
+ if (!key) {
2923
+ continue;
2924
+ }
2925
+ index[key] ??= [];
2926
+ index[key].push(...frames);
2927
+ }
2928
+ }
2929
+ return index;
2930
+ }
2931
+
2932
+ /**
2933
+ * Index the crypto generate pools recorded under versionless flow purls by
2934
+ * {@link versionlessPurlKey}, the crypto counterpart of
2935
+ * {@link indexVersionlessPurls}.
2936
+ *
2937
+ * @param {Object} cryptoGeneratePurls Algorithm bom-refs keyed by flow purl
2938
+ * @returns {Object} Algorithm bom-ref sets keyed by versionless purl key
2939
+ */
2940
+ export function indexVersionlessCrypto(cryptoGeneratePurls) {
2941
+ const index = {};
2942
+ for (const [purl, algos] of Object.entries(cryptoGeneratePurls || {})) {
2943
+ if (splitPurlLoose(purl)?.version === null) {
2944
+ const key = versionlessPurlKey(purl);
2945
+ if (!key) {
2946
+ continue;
2947
+ }
2948
+ index[key] ??= new Set();
2949
+ for (const ref of algos || []) {
2950
+ index[key].add(ref);
2951
+ }
2952
+ }
2953
+ }
2954
+ return index;
2955
+ }
2956
+
2719
2957
  /**
2720
2958
  * Method to create the SBOM with evidence file called evinse file.
2721
2959
  *
@@ -2767,6 +3005,15 @@ export async function createEvinseFile(sliceArtefacts, options) {
2767
3005
  let servicesPresent = false;
2768
3006
  const propertyEvidencePresent =
2769
3007
  metadataProperties.length || Object.keys(componentPropertiesMap).length;
3008
+ // Versionless flow purls attach to every same-name component (see
3009
+ // indexVersionlessPurls), so look both ways: exact purl first, then the
3010
+ // versionless index.
3011
+ const versionlessFrames = indexVersionlessPurls(dataFlowFrames);
3012
+ const versionlessCrypto = indexVersionlessCrypto(cryptoGeneratePurls);
3013
+ const depCryptoPurls =
3014
+ Object.keys(versionlessCrypto).length && cryptoGeneratePurls
3015
+ ? { ...cryptoGeneratePurls }
3016
+ : cryptoGeneratePurls;
2770
3017
  for (const comp of [bomJson.metadata?.component, ...components]) {
2771
3018
  if (!comp?.purl) {
2772
3019
  continue;
@@ -2787,7 +3034,12 @@ export async function createEvinseFile(sliceArtefacts, options) {
2787
3034
  .map((l) => parseOccurrenceEvidenceLocation(l));
2788
3035
  occEvidencePresent = true;
2789
3036
  }
2790
- const dfFrames = dataFlowFrames[comp.purl];
3037
+ const compKey = versionlessPurlKey(comp.purl);
3038
+ let dfFrames = dataFlowFrames[comp.purl];
3039
+ const extraFrames = compKey ? versionlessFrames[compKey] : undefined;
3040
+ if (extraFrames?.length) {
3041
+ dfFrames = [...(dfFrames || []), ...extraFrames];
3042
+ }
2791
3043
  if (dfFrames?.length) {
2792
3044
  if (!comp.evidence) {
2793
3045
  comp.evidence = {};
@@ -2801,10 +3053,20 @@ export async function createEvinseFile(sliceArtefacts, options) {
2801
3053
  }
2802
3054
  }
2803
3055
  // Add crypto tags if this purl offers any generation algorithm
2804
- if (
2805
- cryptoGeneratePurls?.[comp.purl] &&
2806
- Array.from(cryptoGeneratePurls[comp.purl]).length
2807
- ) {
3056
+ let compCryptoPurls = cryptoGeneratePurls?.[comp.purl];
3057
+ const extraCrypto = compKey ? versionlessCrypto[compKey] : undefined;
3058
+ if (extraCrypto?.size) {
3059
+ compCryptoPurls = new Set([...(compCryptoPurls || []), ...extraCrypto]);
3060
+ // The provides section below looks pool entries up by bom-ref, so the
3061
+ // merged set has to be reachable that way too.
3062
+ if (depCryptoPurls) {
3063
+ depCryptoPurls[comp.purl] = compCryptoPurls;
3064
+ if (comp["bom-ref"]) {
3065
+ depCryptoPurls[comp["bom-ref"]] = compCryptoPurls;
3066
+ }
3067
+ }
3068
+ }
3069
+ if (compCryptoPurls && Array.from(compCryptoPurls).length) {
2808
3070
  comp.tags = ["crypto", "crypto-generate"];
2809
3071
  }
2810
3072
  } // for
@@ -2836,14 +3098,14 @@ export async function createEvinseFile(sliceArtefacts, options) {
2836
3098
  }
2837
3099
  // Fix the dependencies section with provides information
2838
3100
  if (
2839
- cryptoGeneratePurls &&
2840
- Object.keys(cryptoGeneratePurls).length &&
3101
+ depCryptoPurls &&
3102
+ Object.keys(depCryptoPurls).length &&
2841
3103
  bomJson.dependencies
2842
3104
  ) {
2843
3105
  const newDependencies = [];
2844
3106
  for (const depObj of bomJson.dependencies) {
2845
- if (depObj.ref && cryptoGeneratePurls[depObj.ref]) {
2846
- const providedAlgos = Array.from(cryptoGeneratePurls[depObj.ref]);
3107
+ if (depObj.ref && depCryptoPurls[depObj.ref]) {
3108
+ const providedAlgos = Array.from(depCryptoPurls[depObj.ref]);
2847
3109
  if (providedAlgos.length) {
2848
3110
  depObj.provides = providedAlgos;
2849
3111
  }
@@ -3088,15 +3350,15 @@ export function collectReachableFrames(_language, reachablesSlice) {
3088
3350
  if (tagStr.includes("crypto")) {
3089
3351
  isCryptoFlow = true;
3090
3352
  }
3353
+ // Join with a space: findCryptoAlgos treats every non-alphanumeric as a
3354
+ // word separator, and a space is the one separator cleanStr keeps.
3091
3355
  if (isCryptoFlow && fnode.code) {
3092
- codeSnippets = `${codeSnippets}\\n${fnode.code}`;
3356
+ codeSnippets = `${codeSnippets} ${fnode.code}`;
3093
3357
  }
3094
- if (
3095
- tagStr.includes("crypto-generate") ||
3096
- fnode.code.includes("encrypt") ||
3097
- fnode.code.includes("decrypt") ||
3098
- fnode.code.includes("sign")
3099
- ) {
3358
+ // Pool membership is tag-driven: a node contributes its pkg: tags only
3359
+ // when the tagger marked it crypto-generate. Code substring tests read
3360
+ // assign/design/signup into encrypt/decrypt/sign and are gone.
3361
+ if (tagStr.includes("crypto-generate")) {
3100
3362
  cpurls = tagStr.split(", ").filter((t) => t.startsWith("pkg:"));
3101
3363
  for (const cpurl of cpurls) {
3102
3364
  if (!cryptoGeneratePurls[cpurl]) {
@@ -3299,6 +3561,155 @@ export function getClassTypeFromSignature(language, typeFullName) {
3299
3561
  return simplifyType(typeFullName);
3300
3562
  }
3301
3563
 
3564
+ const JS_LANGUAGES = ["javascript", "js", "ts", "typescript"];
3565
+
3566
+ /**
3567
+ * Add every location of a set to a purl's locations (Set.add takes a single value).
3568
+ *
3569
+ * @param {Set<string>} target purl location set
3570
+ * @param {Set<string>|undefined} locations locations to add
3571
+ */
3572
+ function addAllLocations(target, locations) {
3573
+ for (const l of locations || []) {
3574
+ target.add(l);
3575
+ }
3576
+ }
3577
+
3578
+ /**
3579
+ * npm package name of a JavaScript module specifier: `@scope/pkg/sub` -> `@scope/pkg`,
3580
+ * `lodash/fp` -> `lodash`. Node.js builtins (`node:fs`, `fs`) are not packages.
3581
+ *
3582
+ * @param {string} module module specifier
3583
+ * @returns {string|undefined} package name
3584
+ */
3585
+ export function jsPackageOfModule(module) {
3586
+ if (!module || module.startsWith("node:") || module.startsWith(".")) {
3587
+ return undefined;
3588
+ }
3589
+ const parts = module.split("/");
3590
+ const pkg = module.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
3591
+ if (isNodeBuiltin(pkg)) {
3592
+ return undefined;
3593
+ }
3594
+ return pkg;
3595
+ }
3596
+
3597
+ function isNodeBuiltin(name) {
3598
+ return NODE_BUILTINS.has(name);
3599
+ }
3600
+
3601
+ const NODE_BUILTINS = new Set(builtinModules);
3602
+
3603
+ const jsPurlsByPackageCache = new WeakMap();
3604
+
3605
+ function jsPurlsByPackage(purlImportsMap) {
3606
+ let byPackage = jsPurlsByPackageCache.get(purlImportsMap);
3607
+ if (!byPackage) {
3608
+ byPackage = {};
3609
+ for (const apurl of Object.keys(purlImportsMap || {})) {
3610
+ try {
3611
+ const purlObj = Purl.parse(apurl);
3612
+ if (purlObj.type !== "npm") {
3613
+ continue;
3614
+ }
3615
+ const name = purlObj.namespace
3616
+ ? `${purlObj.namespace}/${purlObj.name}`
3617
+ : purlObj.name;
3618
+ (byPackage[name] = byPackage[name] || []).push(apurl);
3619
+ } catch (_e) {
3620
+ // not a valid purl
3621
+ }
3622
+ }
3623
+ jsPurlsByPackageCache.set(purlImportsMap, byPackage);
3624
+ }
3625
+ return byPackage;
3626
+ }
3627
+
3628
+ /**
3629
+ * Attribute a JavaScript usage's module-qualified entries to the packages providing them.
3630
+ *
3631
+ * Each call entry with a `module` is a use of that module's package at the call's line. When
3632
+ * the slice's object is an import binding (defined by a require/import naming the module),
3633
+ * every call the object takes part in is a use of that package too.
3634
+ *
3635
+ * @param {Object} ausage usage entry of an object slice
3636
+ * @param {string} fileName file of the slice
3637
+ * @param {Object} purlImportsMap purl -> imported modules and symbols
3638
+ * @param {Object} purlLocationMap purl -> Set of `file#line`
3639
+ */
3640
+ export function attributeJsModules(
3641
+ ausage,
3642
+ fileName,
3643
+ purlImportsMap,
3644
+ purlLocationMap,
3645
+ ) {
3646
+ const byPackage = jsPurlsByPackage(purlImportsMap);
3647
+ const record = (module, line) => {
3648
+ const pkg = jsPackageOfModule(module);
3649
+ if (!pkg || !line) {
3650
+ return;
3651
+ }
3652
+ for (const apurl of byPackage[pkg] || []) {
3653
+ if (!purlLocationMap[apurl]) {
3654
+ purlLocationMap[apurl] = new Set();
3655
+ }
3656
+ purlLocationMap[apurl].add(`${fileName}#${line}`);
3657
+ }
3658
+ };
3659
+ const calls = []
3660
+ .concat(ausage?.invokedCalls || [])
3661
+ .concat(ausage?.argToCalls || []);
3662
+ for (const c of [ausage?.targetObj, ausage?.definedBy, ...calls]) {
3663
+ if (c?.module) {
3664
+ record(c.module, c.lineNumber);
3665
+ }
3666
+ }
3667
+ // Only an import binding stands for its module: a value merely returned by a package's call
3668
+ // (`const s = purl.toString()`) is not a use of that package wherever it goes.
3669
+ const definedBy = ausage?.definedBy;
3670
+ const isImportBinding =
3671
+ definedBy?.name === "require" ||
3672
+ definedBy?.name === "import" ||
3673
+ /^(require|import)\(/.test(definedBy?.code || "");
3674
+ const bindingModule = isImportBinding ? definedBy?.module : undefined;
3675
+ if (bindingModule) {
3676
+ for (const c of calls) {
3677
+ record(bindingModule, c?.lineNumber);
3678
+ }
3679
+ }
3680
+ }
3681
+
3682
+ /**
3683
+ * Attribute a callsites slice's records to the packages providing their modules.
3684
+ *
3685
+ * Each record of `{"callsites": [...]}` (atom `callsites` command) is one external use site
3686
+ * carrying its own file and line; the record is an occurrence of every purl whose package
3687
+ * matches the record's module.
3688
+ *
3689
+ * @param {Object} callsitesSlice callsites slice with a `callsites` array
3690
+ * @param {Object} purlImportsMap purl -> imported modules and symbols
3691
+ * @param {Object} purlLocationMap purl -> Set of `file#line`
3692
+ */
3693
+ export function attributeJsCallsites(
3694
+ callsitesSlice,
3695
+ purlImportsMap,
3696
+ purlLocationMap,
3697
+ ) {
3698
+ const byPackage = jsPurlsByPackage(purlImportsMap);
3699
+ for (const record of callsitesSlice?.callsites || []) {
3700
+ const pkg = jsPackageOfModule(record?.module);
3701
+ if (!pkg || !(record.line > 0)) {
3702
+ continue;
3703
+ }
3704
+ for (const apurl of byPackage[pkg] || []) {
3705
+ if (!purlLocationMap[apurl]) {
3706
+ purlLocationMap[apurl] = new Set();
3707
+ }
3708
+ purlLocationMap[apurl].add(`${record.file}#${record.line}`);
3709
+ }
3710
+ }
3711
+ }
3712
+
3302
3713
  function addToOverrides(lKeyOverrides, atype, fileName, ausageLineNumber) {
3303
3714
  if (!lKeyOverrides[atype]) {
3304
3715
  lKeyOverrides[atype] = new Set();