@cdxgen/cdxgen 13.1.0 → 13.3.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 (283) hide show
  1. package/README.md +32 -15
  2. package/bin/cdxgen.js +243 -122
  3. package/bin/evinse.js +7 -0
  4. package/bin/sign.js +88 -14
  5. package/bin/tracebom.js +64 -4
  6. package/bin/validate.js +29 -7
  7. package/bin/verify.js +79 -39
  8. package/data/crypto-oid.json +88 -12
  9. package/data/remediations.json +47 -0
  10. package/data/rules/ai-bom.yaml +78 -0
  11. package/data/rules/cargo-rust.yaml +132 -0
  12. package/data/rules/mcp-servers.yaml +122 -0
  13. package/lib/audit/targets.js +6 -1
  14. package/lib/cli/bomAssembly.js +117 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +235 -5
  17. package/lib/cli/jsBom.js +108 -26
  18. package/lib/cli/jvmBom.js +68 -12
  19. package/lib/cli/managedBom.js +264 -41
  20. package/lib/cli/nativeBom.js +923 -15
  21. package/lib/core/activity.js +120 -2
  22. package/lib/core/env.js +44 -7
  23. package/lib/core/fs.js +174 -6
  24. package/lib/core/metadataProperties.js +97 -0
  25. package/lib/core/projectConfig.js +31 -4
  26. package/lib/core/userAgent.js +151 -0
  27. package/lib/ecosystems/bunutils.js +269 -101
  28. package/lib/ecosystems/cargoCache.js +364 -0
  29. package/lib/ecosystems/caxa.js +447 -7
  30. package/lib/ecosystems/cmakeResolver.js +66 -0
  31. package/lib/ecosystems/cppEvidence.js +104 -33
  32. package/lib/ecosystems/ecosystems.js +260 -99
  33. package/lib/ecosystems/golem.js +11 -1
  34. package/lib/ecosystems/gradleutils.js +185 -9
  35. package/lib/ecosystems/jsEvidence.js +19 -20
  36. package/lib/ecosystems/kosi.js +1263 -0
  37. package/lib/ecosystems/npmutils.js +110 -9
  38. package/lib/ecosystems/parsers-crystal.js +179 -0
  39. package/lib/ecosystems/parsers-dotnet.js +120 -49
  40. package/lib/ecosystems/parsers-elm.js +816 -0
  41. package/lib/ecosystems/parsers-gleam.js +1 -1
  42. package/lib/ecosystems/parsers-go.js +47 -9
  43. package/lib/ecosystems/parsers-js.js +171 -50
  44. package/lib/ecosystems/parsers-julia.js +268 -0
  45. package/lib/ecosystems/parsers-lua.js +218 -0
  46. package/lib/ecosystems/parsers-misc.js +919 -161
  47. package/lib/ecosystems/parsers-nim.js +221 -0
  48. package/lib/ecosystems/parsers-opam.js +180 -0
  49. package/lib/ecosystems/parsers-perl.js +277 -0
  50. package/lib/ecosystems/parsers-python.js +27 -15
  51. package/lib/ecosystems/parsers-r.js +148 -0
  52. package/lib/ecosystems/parsers-rebar.js +411 -0
  53. package/lib/ecosystems/parsers-rust.js +954 -33
  54. package/lib/ecosystems/parsers-spack.js +204 -0
  55. package/lib/ecosystems/parsers-stack.js +264 -0
  56. package/lib/ecosystems/parsers-terraform.js +194 -0
  57. package/lib/ecosystems/rubyutils.js +1426 -166
  58. package/lib/ecosystems/rusi.js +158 -7
  59. package/lib/evinser/evinser.js +547 -129
  60. package/lib/evinser/swiftsem.js +1914 -302
  61. package/lib/helpers/agenticToolutils.js +925 -0
  62. package/lib/helpers/bomSigner.js +652 -221
  63. package/lib/inventory/agentFormulationParser.js +57 -4
  64. package/lib/inventory/aiCollector.js +504 -10
  65. package/lib/inventory/aiInventory.js +12 -0
  66. package/lib/inventory/analyzer.js +488 -30
  67. package/lib/inventory/astWorker.js +6 -0
  68. package/lib/inventory/atomUtils.js +568 -31
  69. package/lib/inventory/auditCategories.js +8 -0
  70. package/lib/inventory/cbomutils.js +30 -0
  71. package/lib/inventory/cdxrs.js +110 -14
  72. package/lib/inventory/communityAiConfigParser.js +215 -2
  73. package/lib/inventory/depsUtils.js +183 -11
  74. package/lib/inventory/display.js +4 -5
  75. package/lib/inventory/dosai.js +627 -13
  76. package/lib/inventory/dynamic.js +39 -1
  77. package/lib/inventory/envcontext.js +9 -6
  78. package/lib/inventory/evidenceUtils.js +26 -5
  79. package/lib/inventory/fetchBatch.js +190 -19
  80. package/lib/inventory/fetchRate.js +74 -11
  81. package/lib/inventory/forgeEnricher.js +3 -2
  82. package/lib/inventory/jvmToolEnv.js +6 -5
  83. package/lib/inventory/mcpConfigParser.js +166 -2
  84. package/lib/inventory/mcpDiscovery.js +11 -0
  85. package/lib/inventory/mcpServerManifest.js +389 -0
  86. package/lib/inventory/mcpbBundle.js +419 -0
  87. package/lib/inventory/plugins.js +3 -0
  88. package/lib/inventory/protobom.js +62 -89
  89. package/lib/inventory/protobomLoader.js +4 -0
  90. package/lib/inventory/purl.js +152 -3
  91. package/lib/inventory/rustFormulationParser.js +1 -1
  92. package/lib/inventory/sbomDocument.js +2 -70
  93. package/lib/inventory/source.js +47 -23
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/traceRunner.js +210 -21
  97. package/lib/managers/binary.js +115 -24
  98. package/lib/managers/docker.js +440 -20
  99. package/lib/managers/oci.js +39 -1
  100. package/lib/parsers/cmakeLists.js +452 -0
  101. package/lib/parsers/huggingfaceUtils.js +15 -7
  102. package/lib/parsers/largeJson.js +454 -0
  103. package/lib/parsers/npmrc.js +19 -1
  104. package/lib/parsers/onnx.js +294 -0
  105. package/lib/parsers/safetensors.js +114 -0
  106. package/lib/stages/postgen/introspection/markers.js +29 -6
  107. package/lib/stages/postgen/postgen.js +237 -16
  108. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  109. package/lib/validator/bomValidator.js +6 -16
  110. package/lib/validator/index.js +48 -10
  111. package/lib/validator/reporters/console.js +3 -0
  112. package/lib/validator/reporters/json.js +1 -0
  113. package/lib/validator/reporters/sarif.js +7 -0
  114. package/package.json +27 -27
  115. package/types/lib/audit/targets.d.ts.map +1 -1
  116. package/types/lib/cli/bomAssembly.d.ts +2 -1
  117. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  118. package/types/lib/cli/cliOptions.d.ts +33 -1
  119. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  120. package/types/lib/cli/index.d.ts +2 -2
  121. package/types/lib/cli/index.d.ts.map +1 -1
  122. package/types/lib/cli/jsBom.d.ts +23 -0
  123. package/types/lib/cli/jsBom.d.ts.map +1 -1
  124. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  125. package/types/lib/cli/managedBom.d.ts +33 -0
  126. package/types/lib/cli/managedBom.d.ts.map +1 -1
  127. package/types/lib/cli/nativeBom.d.ts +88 -8
  128. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  129. package/types/lib/core/activity.d.ts.map +1 -1
  130. package/types/lib/core/env.d.ts +21 -0
  131. package/types/lib/core/env.d.ts.map +1 -1
  132. package/types/lib/core/fs.d.ts +55 -0
  133. package/types/lib/core/fs.d.ts.map +1 -1
  134. package/types/lib/core/metadataProperties.d.ts +39 -0
  135. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  136. package/types/lib/core/projectConfig.d.ts +6 -2
  137. package/types/lib/core/projectConfig.d.ts.map +1 -1
  138. package/types/lib/core/userAgent.d.ts +56 -0
  139. package/types/lib/core/userAgent.d.ts.map +1 -0
  140. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  141. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  143. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  144. package/types/lib/ecosystems/caxa.d.ts +14 -0
  145. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  146. package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
  147. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  148. package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
  149. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  150. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  151. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  152. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  153. package/types/lib/ecosystems/gradleutils.d.ts +40 -4
  154. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  155. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  156. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  157. package/types/lib/ecosystems/kosi.d.ts +86 -0
  158. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  159. package/types/lib/ecosystems/npmutils.d.ts +42 -0
  160. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  161. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  162. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  163. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  164. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  165. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  166. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  167. package/types/lib/ecosystems/parsers-go.d.ts +10 -0
  168. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  169. package/types/lib/ecosystems/parsers-js.d.ts +10 -4
  170. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  171. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  172. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  173. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  174. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
  176. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  177. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  178. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  179. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  180. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  181. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  182. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  183. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  184. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  185. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  187. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  188. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  189. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  191. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  192. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  193. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  194. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  195. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  196. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  197. package/types/lib/ecosystems/rubyutils.d.ts +143 -2
  198. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  199. package/types/lib/ecosystems/rusi.d.ts +13 -0
  200. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  201. package/types/lib/evinser/evinser.d.ts +81 -2
  202. package/types/lib/evinser/evinser.d.ts.map +1 -1
  203. package/types/lib/evinser/swiftsem.d.ts +280 -15
  204. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  205. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  206. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  207. package/types/lib/helpers/bomSigner.d.ts +106 -13
  208. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  209. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  210. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  211. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  212. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  213. package/types/lib/inventory/atomUtils.d.ts +145 -3
  214. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +4 -0
  216. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  217. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  218. package/types/lib/inventory/cdxrs.d.ts +8 -3
  219. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  220. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  221. package/types/lib/inventory/depsUtils.d.ts +15 -0
  222. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/display.d.ts.map +1 -1
  224. package/types/lib/inventory/dosai.d.ts +87 -1
  225. package/types/lib/inventory/dosai.d.ts.map +1 -1
  226. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  227. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  228. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  229. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  230. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  231. package/types/lib/inventory/fetchRate.d.ts +9 -6
  232. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  233. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  234. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  235. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  236. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  237. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  238. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  239. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  240. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  241. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  242. package/types/lib/inventory/plugins.d.ts.map +1 -1
  243. package/types/lib/inventory/protobom.d.ts +18 -13
  244. package/types/lib/inventory/protobom.d.ts.map +1 -1
  245. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  246. package/types/lib/inventory/purl.d.ts +71 -2
  247. package/types/lib/inventory/purl.d.ts.map +1 -1
  248. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  249. package/types/lib/inventory/source.d.ts.map +1 -1
  250. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  251. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  252. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  253. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  254. package/types/lib/inventory/traceRunner.d.ts +47 -1
  255. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  256. package/types/lib/managers/binary.d.ts +30 -1
  257. package/types/lib/managers/binary.d.ts.map +1 -1
  258. package/types/lib/managers/docker.d.ts +29 -0
  259. package/types/lib/managers/docker.d.ts.map +1 -1
  260. package/types/lib/managers/oci.d.ts +14 -0
  261. package/types/lib/managers/oci.d.ts.map +1 -1
  262. package/types/lib/parsers/cmakeLists.d.ts +76 -0
  263. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  264. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  265. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  266. package/types/lib/parsers/largeJson.d.ts +52 -0
  267. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  268. package/types/lib/parsers/npmrc.d.ts +5 -0
  269. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  270. package/types/lib/parsers/onnx.d.ts +28 -0
  271. package/types/lib/parsers/onnx.d.ts.map +1 -0
  272. package/types/lib/parsers/safetensors.d.ts +22 -0
  273. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  275. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  276. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  277. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  278. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  279. package/types/lib/validator/index.d.ts +10 -1
  280. package/types/lib/validator/index.d.ts.map +1 -1
  281. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  282. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  283. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
@@ -50,6 +50,7 @@ import {
50
50
  parseGitHubWorkflowData,
51
51
  parseHelmYamlData,
52
52
  } from "../ecosystems/parsers-misc.js";
53
+ import { parseCpanSnapshot } from "../ecosystems/parsers-perl.js";
53
54
  import {
54
55
  generatePixiLockFile,
55
56
  parseMojoProject,
@@ -61,6 +62,8 @@ import {
61
62
  parseReqFile,
62
63
  parseSetupPyFile,
63
64
  } from "../ecosystems/parsers-python.js";
65
+ import { parseRenvLock } from "../ecosystems/parsers-r.js";
66
+ import { parseTerraformLockFile } from "../ecosystems/parsers-terraform.js";
64
67
  import { collectEmbeddedSboms } from "../ecosystems/pep770.js";
65
68
  import { isPyLockFile } from "../ecosystems/pylockutils.js";
66
69
  import {
@@ -70,7 +73,10 @@ import {
70
73
  getPyModules,
71
74
  } from "../ecosystems/pythonutils.js";
72
75
  import {
76
+ addRubyGemsDistributionUrls,
77
+ addRubyRuntimeComponent,
73
78
  collectGemModuleNames,
79
+ describeProjectGem,
74
80
  enrichGemsFromLocalCache,
75
81
  parseBundleConfig,
76
82
  parseGemfileLockData,
@@ -101,7 +107,7 @@ import {
101
107
  createDosaiMethodsSlice,
102
108
  isDosaiDotnetLanguage,
103
109
  normalizeDosaiServiceMap,
104
- readDosaiJsonFile,
110
+ readDosaiMethodsReport,
105
111
  } from "../inventory/dosai.js";
106
112
  import { addEvidenceForDotnet } from "../inventory/evidenceUtils.js";
107
113
  import { nugetPurl, pypiBomRef } from "../inventory/purl.js";
@@ -570,8 +576,17 @@ export async function createPythonBom(path, options) {
570
576
  }
571
577
  if (requirementsMode || pipenvMode) {
572
578
  if (pipenvMode) {
573
- // TODO: Support for nested directories
574
- safeSpawnSync("pipenv", ["install"], { cwd: path, shell: isWin });
579
+ // `pipenv install` builds and installs the project's own dependencies
580
+ // (including `-e .` self-references that execute setup.py), so unlike
581
+ // the lockfile parsing below it must honor --no-install-deps and the
582
+ // pre-build lifecycle.
583
+ if (options.installDeps !== false) {
584
+ safeSpawnSync("pipenv", ["install"], { cwd: path, shell: isWin });
585
+ } else {
586
+ console.log(
587
+ "Skipping 'pipenv install' since dependency installation is disabled.",
588
+ );
589
+ }
575
590
  const piplockFile = join(path, "Pipfile.lock");
576
591
  if (safeExistsSync(piplockFile)) {
577
592
  let dlist;
@@ -1349,6 +1364,7 @@ export async function createRubyBom(path, options) {
1349
1364
  let pkgList = [];
1350
1365
  let dependencies = [];
1351
1366
  let rootList = [];
1367
+ const projectGemRefs = [];
1352
1368
  const parentComponent = createDefaultParentComponent(path, "gem", options);
1353
1369
  const gemFileMode = gemFiles.length;
1354
1370
  const gemLockMode = gemLockFiles.length;
@@ -1402,10 +1418,18 @@ export async function createRubyBom(path, options) {
1402
1418
  if (retMap.rootList?.length) {
1403
1419
  rootList = rootList.concat(retMap.rootList);
1404
1420
  }
1421
+ // `PATH remote: .` is relative to the lockfile, so only the lockfiles
1422
+ // of the scanned directory can serve the gem the root gemspec builds
1423
+ if (
1424
+ retMap.projectGemRefs?.length &&
1425
+ dirname(resolve(f)) === resolve(path)
1426
+ ) {
1427
+ projectGemRefs.push(...retMap.projectGemRefs);
1428
+ }
1405
1429
  }
1406
1430
  }
1407
1431
  // Parsing .gemspec files would help us get more metadata such as description, authors, licenses etc
1408
- let rootGemspecComponent;
1432
+ let projectGem;
1409
1433
  if (gemspecFiles.length) {
1410
1434
  if (!gemLockFiles.length && !hasAnyProjectType(["oci"], options, false)) {
1411
1435
  console.log(
@@ -1417,38 +1441,51 @@ export async function createRubyBom(path, options) {
1417
1441
  detail: "only gemspec files were available to parse",
1418
1442
  });
1419
1443
  }
1444
+ // Bundler evaluated the root gemspec to lock it under `PATH remote: .`,
1445
+ // so the locked release stands in for a version computed at runtime. It
1446
+ // is resolved before the merge, so that the gemspec and lockfile
1447
+ // sightings of the gem reconcile into one component.
1448
+ const lockedVersions = Object.create(null);
1449
+ for (const p of pkgList) {
1450
+ if (p.version && projectGemRefs.includes(p["bom-ref"])) {
1451
+ lockedVersions[p.name] = p.version;
1452
+ }
1453
+ }
1420
1454
  for (const f of gemspecFiles) {
1421
1455
  const gemspecData = readFileSync(f, { encoding: "utf-8" });
1422
- const gpkgList = await parseGemspecData(gemspecData, f);
1423
- if (gpkgList.length) {
1424
- pkgList = pkgList.concat(gpkgList);
1425
- pkgList = trimComponents(pkgList);
1426
- if (
1427
- !rootGemspecComponent &&
1428
- dirname(resolve(f)) === resolve(path) &&
1429
- gpkgList[0]?.name
1430
- ) {
1431
- rootGemspecComponent = gpkgList[0];
1432
- }
1456
+ const isRootGemspec = dirname(resolve(f)) === resolve(path);
1457
+ const gpkgList = await parseGemspecData(
1458
+ gemspecData,
1459
+ f,
1460
+ isRootGemspec ? { lockedVersions } : {},
1461
+ );
1462
+ if (!gpkgList.length) {
1463
+ continue;
1433
1464
  }
1434
- }
1435
- if (
1436
- rootGemspecComponent &&
1437
- !("project-name" in options) &&
1438
- options.projectName === undefined
1439
- ) {
1440
- parentComponent.name = rootGemspecComponent.name;
1441
- parentComponent.version = rootGemspecComponent.version || "latest";
1442
- const parentPurl = build({
1443
- type: "gem",
1444
- namespace: parentComponent.group || null,
1445
- name: parentComponent.name,
1446
- version: parentComponent.version || null,
1447
- });
1448
- parentComponent["bom-ref"] = decodeURIComponent(parentPurl);
1449
- parentComponent["purl"] = parentPurl;
1465
+ // The gemspec in the scanned directory is the gem the project builds
1466
+ if (!projectGem && isRootGemspec && gpkgList[0]?.name) {
1467
+ projectGem = gpkgList[0];
1468
+ }
1469
+ pkgList = trimComponents(pkgList.concat(gpkgList));
1450
1470
  }
1451
1471
  }
1472
+ // Describe that gem in metadata.component, unless `--project-name` names a
1473
+ // different project or an image is being scanned
1474
+ if (
1475
+ projectGem &&
1476
+ !hasAnyProjectType(["oci"], options, false) &&
1477
+ (!("project-name" in options) || options.projectName === projectGem.name)
1478
+ ) {
1479
+ ({ pkgList, dependencies, rootList } = describeProjectGem(
1480
+ parentComponent,
1481
+ projectGem,
1482
+ pkgList,
1483
+ dependencies,
1484
+ rootList,
1485
+ projectGemRefs,
1486
+ options,
1487
+ ));
1488
+ }
1452
1489
  if (rootList.length) {
1453
1490
  dependencies = mergeDependencies(
1454
1491
  dependencies,
@@ -1544,6 +1581,21 @@ export async function createRubyBom(path, options) {
1544
1581
  // lookup, gives us the sha256 of the exact native build, and unlike the
1545
1582
  // registry lookups it also works in dry-run mode.
1546
1583
  pkgList = await enrichGemsFromLocalCache(pkgList, { gemHome });
1584
+ // The download URL of a gem from the public registry needs no network call
1585
+ pkgList = addRubyGemsDistributionUrls(pkgList);
1586
+ // Gems declare the Ruby they run on; surface it as the runtime they depend
1587
+ // on. The component says the runtime is external, which only CycloneDX 1.7
1588
+ // can express, and a container image ships its own Ruby.
1589
+ if (
1590
+ options.specVersion >= 1.7 &&
1591
+ !hasAnyProjectType(["oci"], options, false)
1592
+ ) {
1593
+ ({ pkgList, dependencies } = addRubyRuntimeComponent(
1594
+ pkgList,
1595
+ parentComponent,
1596
+ dependencies,
1597
+ ));
1598
+ }
1547
1599
  return buildBomNSData(options, pkgList, "gem", {
1548
1600
  src: path,
1549
1601
  dependencies,
@@ -2003,9 +2055,13 @@ export async function createCsharpBom(path, options) {
2003
2055
  pkgList = trimComponents(pkgList);
2004
2056
  // Perform deep analysis using dosai
2005
2057
  if (options.deep) {
2058
+ // resolve (not join) so a fully-qualified --deps-slices-file is used
2059
+ // as-is: join would append it to the scan path, which is never a valid
2060
+ // path on Windows and silently loses the .NET evidence elsewhere
2061
+ // (issue 4394). A relative value still resolves against the scan path.
2006
2062
  const slicesFile = resolve(
2007
2063
  options.depsSlicesFile
2008
- ? join(path, options.depsSlicesFile)
2064
+ ? resolve(path, options.depsSlicesFile)
2009
2065
  : join(getTmpDir(), "dosai.json"),
2010
2066
  );
2011
2067
  // Create the slices file if it doesn't exist
@@ -2018,14 +2074,24 @@ export async function createCsharpBom(path, options) {
2018
2074
  resolve(slicesFile),
2019
2075
  options,
2020
2076
  );
2021
- if (!sliceResult && DEBUG_MODE) {
2022
- console.log(
2023
- "Slicing with dosai was unsuccessful. Check the errors reported in the logs above.",
2077
+ if (!sliceResult) {
2078
+ // Visible without --debug: otherwise the components silently lose
2079
+ // the dependency-slice evidence while the scan still exits 0.
2080
+ // --evidence (which implies --deep) runs dosai again during evinse
2081
+ // and can still add occurrences, so this names only what is lost.
2082
+ console.warn(
2083
+ "Slicing with dosai was unsuccessful, so the .NET components get no occurrence, reachability, or services evidence from the dependency slice. Re-run with --debug for the dosai errors.",
2024
2084
  );
2025
2085
  }
2086
+ } else if (DEBUG_MODE) {
2087
+ console.log(
2088
+ `Reusing the existing dosai methods slice "${slicesFile}" without checking whether it honours the current --exclude patterns. Delete the file to force a fresh run.`,
2089
+ );
2026
2090
  }
2027
2091
  pkgList = addEvidenceForDotnet(pkgList, slicesFile);
2028
- const methodsSlice = readDosaiJsonFile(slicesFile);
2092
+ const methodsSlice = readDosaiMethodsReport(slicesFile, {
2093
+ sections: ["Metadata", "Services", "ApiEndpoints"],
2094
+ });
2029
2095
  // Provider-backed Services[] first so bom-refs, trust zones, and data
2030
2096
  // classifications survive; ApiEndpoints derivation fills in the rest.
2031
2097
  const servicesMap = collectDosaiServiceComponents(methodsSlice, {});
@@ -2033,12 +2099,20 @@ export async function createCsharpBom(path, options) {
2033
2099
  services = normalizeDosaiServiceMap(servicesMap);
2034
2100
  }
2035
2101
  }
2036
- // Parent dependency tree
2102
+ // Parent dependency tree. A single project file already contributed an
2103
+ // entry for the same parent, so the lock file roots are merged into it
2104
+ // rather than added as a second entry for the same ref.
2037
2105
  if (parentDependsOn.size && parentComponent?.["bom-ref"]) {
2038
- dependencies.splice(0, 0, {
2039
- ref: parentComponent["bom-ref"],
2040
- dependsOn: Array.from(parentDependsOn).sort(),
2041
- });
2106
+ dependencies = mergeDependencies(
2107
+ [
2108
+ {
2109
+ ref: parentComponent["bom-ref"],
2110
+ dependsOn: Array.from(parentDependsOn).sort(),
2111
+ },
2112
+ ],
2113
+ dependencies,
2114
+ parentComponent,
2115
+ );
2042
2116
  }
2043
2117
  if (shouldFetchLicense()) {
2044
2118
  const retMap = await getNugetMetadata(pkgList, dependencies);
@@ -2150,3 +2224,152 @@ export async function createCryptoCertsBom(path, options) {
2150
2224
  },
2151
2225
  };
2152
2226
  }
2227
+
2228
+ /**
2229
+ * Function to create bom string for R projects using renv.
2230
+ *
2231
+ * The `renv.lock` file carries the resolved package set for the project
2232
+ * library, including the R version it was produced under.
2233
+ *
2234
+ * @param {string} path to the project
2235
+ * @param {Object} options Parse options from the cli
2236
+ * @returns {Object} BOM object
2237
+ */
2238
+ export function createRBom(path, options) {
2239
+ let pkgList = [];
2240
+ let dependencies = [];
2241
+ let rootInputs = [];
2242
+ let rVersion;
2243
+ const renvLockFiles = getAllFiles(
2244
+ path,
2245
+ `${options.multiProject ? "**/" : ""}renv.lock`,
2246
+ options,
2247
+ );
2248
+ for (const renvLockFile of renvLockFiles) {
2249
+ if (DEBUG_MODE) {
2250
+ console.log(`Parsing ${renvLockFile}`);
2251
+ }
2252
+ const result = parseRenvLock(renvLockFile);
2253
+ if (result.pkgList?.length) {
2254
+ pkgList = pkgList.concat(result.pkgList);
2255
+ }
2256
+ if (result.dependencies?.length) {
2257
+ dependencies = dependencies.concat(result.dependencies);
2258
+ }
2259
+ if (result.rootInputs?.length) {
2260
+ rootInputs = rootInputs.concat(result.rootInputs);
2261
+ }
2262
+ if (!rVersion) {
2263
+ rVersion = result.rVersion;
2264
+ }
2265
+ }
2266
+ if (!pkgList.length) {
2267
+ return {};
2268
+ }
2269
+ // renv locks the library, not the project: they carry no project name, so
2270
+ // the directory is the name the project is already known by. The R version
2271
+ // the library was resolved under is a property of that project.
2272
+ const projectName = basename(resolve(path));
2273
+ const parentComponent = {
2274
+ type: "application",
2275
+ name: projectName,
2276
+ "bom-ref": `application:${projectName}:latest`,
2277
+ description: `R project: ${projectName}`,
2278
+ properties: [
2279
+ { name: "internal:SrcFile", value: renvLockFiles.join(", ") },
2280
+ ...(rVersion ? [{ name: "cdx:renv:rVersion", value: rVersion }] : []),
2281
+ ],
2282
+ };
2283
+ if (rootInputs.length) {
2284
+ dependencies.push({
2285
+ ref: parentComponent["bom-ref"],
2286
+ dependsOn: [...new Set(rootInputs)].sort(),
2287
+ });
2288
+ }
2289
+ return buildBomNSData(options, pkgList, "cran", {
2290
+ src: path,
2291
+ filename: renvLockFiles.join(", "),
2292
+ dependencies,
2293
+ parentComponent,
2294
+ });
2295
+ }
2296
+
2297
+ /**
2298
+ * Function to create bom string for Perl projects using Carton.
2299
+ *
2300
+ * The `cpanfile.snapshot` carries the resolved install set; the declared
2301
+ * `cpanfile` is consulted to distinguish direct dependencies.
2302
+ *
2303
+ * @param {string} path to the project
2304
+ * @param {Object} options Parse options from the cli
2305
+ * @returns {Object} BOM object
2306
+ */
2307
+ export function createPerlBom(path, options) {
2308
+ let pkgList = [];
2309
+ let dependencies = [];
2310
+ const snapshotFiles = getAllFiles(
2311
+ path,
2312
+ `${options.multiProject ? "**/" : ""}cpanfile.snapshot`,
2313
+ options,
2314
+ );
2315
+ for (const snapshotFile of snapshotFiles) {
2316
+ if (DEBUG_MODE) {
2317
+ console.log(`Parsing ${snapshotFile}`);
2318
+ }
2319
+ const baseDir = dirname(snapshotFile);
2320
+ const cpanfile = join(baseDir, "cpanfile");
2321
+ const result = parseCpanSnapshot(
2322
+ snapshotFile,
2323
+ safeExistsSync(cpanfile) ? cpanfile : undefined,
2324
+ );
2325
+ if (result.pkgList?.length) {
2326
+ pkgList = pkgList.concat(result.pkgList);
2327
+ }
2328
+ if (result.dependencies?.length) {
2329
+ dependencies = dependencies.concat(result.dependencies);
2330
+ }
2331
+ }
2332
+ if (pkgList.length) {
2333
+ return buildBomNSData(options, pkgList, "cpan", {
2334
+ src: path,
2335
+ filename: snapshotFiles.join(", "),
2336
+ dependencies,
2337
+ });
2338
+ }
2339
+ return {};
2340
+ }
2341
+
2342
+ /**
2343
+ * Function to create bom string for Terraform and OpenTofu projects.
2344
+ *
2345
+ * The `.terraform.lock.hcl` file pins the providers the configuration
2346
+ * resolves to, with content hashes suitable for the CycloneDX hashes array.
2347
+ *
2348
+ * @param {string} path to the project
2349
+ * @param {Object} options Parse options from the cli
2350
+ * @returns {Object} BOM object
2351
+ */
2352
+ export function createTerraformBom(path, options) {
2353
+ let pkgList = [];
2354
+ const lockFiles = getAllFiles(
2355
+ path,
2356
+ `${options.multiProject ? "**/" : ""}.terraform.lock.hcl`,
2357
+ options,
2358
+ );
2359
+ for (const lockFile of lockFiles) {
2360
+ if (DEBUG_MODE) {
2361
+ console.log(`Parsing ${lockFile}`);
2362
+ }
2363
+ const result = parseTerraformLockFile(lockFile);
2364
+ if (result.pkgList?.length) {
2365
+ pkgList = pkgList.concat(result.pkgList);
2366
+ }
2367
+ }
2368
+ if (pkgList.length) {
2369
+ return buildBomNSData(options, pkgList, "terraform", {
2370
+ src: path,
2371
+ filename: lockFiles.join(", "),
2372
+ });
2373
+ }
2374
+ return {};
2375
+ }