@cdxgen/cdxgen 13.5.0 → 13.7.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 (380) hide show
  1. package/README.md +17 -10
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +260 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +28 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +24 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/license-aliases.json +24 -2
  15. package/data/pqc-migration.json +239 -0
  16. package/data/remediations.json +176 -0
  17. package/data/rules/ai-bom.yaml +1 -1
  18. package/data/rules/brotli-decoder.yaml +107 -0
  19. package/data/rules/cbom-security.yaml +4 -3
  20. package/data/rules/package-integrity.yaml +28 -0
  21. package/data/rules/vscode-extensions.yaml +75 -0
  22. package/data/spdx-license-list.json +121 -7
  23. package/data/spdx-licenses.json +69 -0
  24. package/data/spdx.schema.json +845 -2
  25. package/lib/audit/index.js +25 -3
  26. package/lib/cli/bomAssembly.js +172 -4
  27. package/lib/cli/cliOptions.js +121 -17
  28. package/lib/cli/index.js +720 -902
  29. package/lib/cli/jsBom.js +156 -64
  30. package/lib/cli/jvmBom.js +602 -86
  31. package/lib/cli/managedBom.js +161 -19
  32. package/lib/cli/nativeBom.js +52 -5
  33. package/lib/core/boundedFile.js +344 -0
  34. package/lib/core/brotliProbe.js +182 -0
  35. package/lib/core/env.js +34 -0
  36. package/lib/core/fs.js +138 -3
  37. package/lib/core/globPatterns.js +218 -0
  38. package/lib/core/httpClient.js +98 -1
  39. package/lib/core/lockPaths.js +38 -0
  40. package/lib/core/paths.js +24 -1
  41. package/lib/core/processTree.js +100 -8
  42. package/lib/core/projectConfig.js +332 -25
  43. package/lib/core/toolRegistry.js +184 -0
  44. package/lib/ecosystems/cppEvidence.js +263 -106
  45. package/lib/ecosystems/cppIncludeResolver.js +16 -5
  46. package/lib/ecosystems/denoutils.js +2 -2
  47. package/lib/ecosystems/ecosystems.js +25 -10
  48. package/lib/ecosystems/gemHostProbe.js +301 -0
  49. package/lib/ecosystems/golem.js +3 -8
  50. package/lib/ecosystems/gradleutils.js +1909 -229
  51. package/lib/ecosystems/jsEvidence.js +90 -38
  52. package/lib/ecosystems/kosi.js +3 -5
  53. package/lib/ecosystems/npmVersionResolution.js +376 -0
  54. package/lib/ecosystems/parsers-bazel.js +107 -6
  55. package/lib/ecosystems/parsers-js.js +505 -22
  56. package/lib/ecosystems/parsers-misc.js +209 -20
  57. package/lib/ecosystems/parsers-python.js +178 -30
  58. package/lib/ecosystems/parsers-rust.js +117 -15
  59. package/lib/ecosystems/pythonutils.js +128 -2
  60. package/lib/ecosystems/remote/huggingface.js +22 -0
  61. package/lib/ecosystems/rubyutils.js +538 -31
  62. package/lib/ecosystems/rusi.js +2 -4
  63. package/lib/ecosystems/scalacliutils.js +2 -0
  64. package/lib/ecosystems/scalasem.js +798 -152
  65. package/lib/ecosystems/vendoredCode.js +237 -3
  66. package/lib/ecosystems/venvProbe.js +360 -0
  67. package/lib/evinser/evinser.js +536 -71
  68. package/lib/evinser/scalasem.js +5 -1
  69. package/lib/helpers/agenticToolutils.js +525 -94
  70. package/lib/helpers/bomSigner.js +43 -9
  71. package/lib/helpers/jssSigner.js +1276 -0
  72. package/lib/helpers/vsixutils.js +1574 -315
  73. package/lib/inventory/agentBlueprint.js +193 -0
  74. package/lib/inventory/agentToolNames.js +145 -0
  75. package/lib/inventory/aiCollector.js +153 -7
  76. package/lib/inventory/aiInventory.js +44 -8
  77. package/lib/inventory/aiProviders.js +324 -0
  78. package/lib/inventory/atomUtils.js +77 -0
  79. package/lib/inventory/bomUtils.js +8 -1
  80. package/lib/inventory/brotliDecoder.js +142 -0
  81. package/lib/inventory/buildToolRateLimit.js +1 -1
  82. package/lib/inventory/cbomutils.js +56 -7
  83. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  84. package/lib/inventory/citations.js +31 -2
  85. package/lib/inventory/communityAiConfigParser.js +978 -80
  86. package/lib/inventory/cyclonedx20.js +152 -0
  87. package/lib/inventory/cyclonedx20Common.js +286 -0
  88. package/lib/inventory/cyclonedx20Derived.js +159 -0
  89. package/lib/inventory/cyclonedx20Parties.js +552 -0
  90. package/lib/inventory/cyclonedx20Read.js +768 -0
  91. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  92. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  93. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  94. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  95. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  96. package/lib/inventory/cyclonedx20Render.js +766 -0
  97. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  98. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  99. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  100. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  101. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  102. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  103. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  104. package/lib/inventory/cyclonedx20Tables.js +594 -0
  105. package/lib/inventory/deps.js +141 -13
  106. package/lib/inventory/dosai.js +375 -6
  107. package/lib/inventory/dosaiParsers.js +3 -6
  108. package/lib/inventory/dotnetRuntime.js +367 -0
  109. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  110. package/lib/inventory/envcontext.js +436 -48
  111. package/lib/inventory/evidenceUtils.js +13 -1
  112. package/lib/inventory/fetchBatch.js +1 -1
  113. package/lib/inventory/formulationParsers.js +124 -14
  114. package/lib/inventory/gtfobins.js +13 -0
  115. package/lib/inventory/hbom.js +45 -6
  116. package/lib/inventory/hbomLoader.js +166 -0
  117. package/lib/inventory/hostRuntimeProbe.js +686 -0
  118. package/lib/inventory/jvmLocalRepos.js +9 -2
  119. package/lib/inventory/jvmToolEnv.js +29 -0
  120. package/lib/inventory/licenseEnhancer.js +12 -0
  121. package/lib/inventory/mavenCentralMirror.js +534 -0
  122. package/lib/inventory/mcpConfigParser.js +5 -0
  123. package/lib/inventory/nodeBuildInfo.js +218 -0
  124. package/lib/inventory/nodeHostProbe.js +202 -0
  125. package/lib/inventory/occurrenceKey.js +90 -0
  126. package/lib/inventory/osPackageResolver.js +182 -0
  127. package/lib/inventory/phpBuildInfo.js +10 -59
  128. package/lib/inventory/phpHostProbe.js +32 -488
  129. package/lib/inventory/pqcMigration.js +426 -0
  130. package/lib/inventory/rubyBuildInfo.js +972 -0
  131. package/lib/inventory/rubyHostProbe.js +218 -0
  132. package/lib/inventory/rustFormulationParser.js +21 -10
  133. package/lib/inventory/scalaCoords.js +4 -3
  134. package/lib/inventory/scalaDedupe.js +440 -0
  135. package/lib/inventory/source.js +114 -8
  136. package/lib/managers/binary.js +337 -3
  137. package/lib/managers/docker.js +79 -0
  138. package/lib/parsers/huggingfaceManifest.js +9 -1
  139. package/lib/parsers/huggingfaceUtils.js +18 -0
  140. package/lib/parsers/kiroPermissions.js +166 -0
  141. package/lib/parsers/largeJson.js +28 -11
  142. package/lib/parsers/vscodeExtensionId.js +183 -0
  143. package/lib/server/server.js +48 -35
  144. package/lib/stages/postgen/postgen.js +41 -1
  145. package/lib/stages/postgen/ruleEngine.js +13 -5
  146. package/lib/stages/postgen/specVersionCompat.js +76 -0
  147. package/lib/stages/pregen/envAudit.js +1 -0
  148. package/lib/stages/pregen/pregen.js +114 -57
  149. package/lib/validator/bomValidator.js +14 -2
  150. package/lib/validator/complianceEngine.js +30 -3
  151. package/lib/validator/complianceRules.js +1416 -4
  152. package/lib/validator/generated/validate-1.6.mjs +1 -1
  153. package/lib/validator/generated/validate-1.7.mjs +1 -1
  154. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  155. package/lib/validator/index.js +49 -8
  156. package/lib/validator/reporters/json.js +6 -0
  157. package/package.json +11 -10
  158. package/types/bin/repl.d.ts.map +1 -1
  159. package/types/lib/audit/index.d.ts +6 -2
  160. package/types/lib/audit/index.d.ts.map +1 -1
  161. package/types/lib/cli/bomAssembly.d.ts +29 -1
  162. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  163. package/types/lib/cli/cliOptions.d.ts +6 -0
  164. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  165. package/types/lib/cli/index.d.ts.map +1 -1
  166. package/types/lib/cli/jsBom.d.ts +3 -18
  167. package/types/lib/cli/jsBom.d.ts.map +1 -1
  168. package/types/lib/cli/jvmBom.d.ts +15 -0
  169. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  170. package/types/lib/cli/managedBom.d.ts.map +1 -1
  171. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  172. package/types/lib/core/boundedFile.d.ts +100 -0
  173. package/types/lib/core/boundedFile.d.ts.map +1 -0
  174. package/types/lib/core/brotliProbe.d.ts +37 -0
  175. package/types/lib/core/brotliProbe.d.ts.map +1 -0
  176. package/types/lib/core/env.d.ts +14 -0
  177. package/types/lib/core/env.d.ts.map +1 -1
  178. package/types/lib/core/fs.d.ts +4 -0
  179. package/types/lib/core/fs.d.ts.map +1 -1
  180. package/types/lib/core/globPatterns.d.ts +70 -0
  181. package/types/lib/core/globPatterns.d.ts.map +1 -0
  182. package/types/lib/core/httpClient.d.ts +24 -0
  183. package/types/lib/core/httpClient.d.ts.map +1 -1
  184. package/types/lib/core/lockPaths.d.ts +19 -0
  185. package/types/lib/core/lockPaths.d.ts.map +1 -0
  186. package/types/lib/core/paths.d.ts +9 -0
  187. package/types/lib/core/paths.d.ts.map +1 -1
  188. package/types/lib/core/processTree.d.ts +17 -4
  189. package/types/lib/core/processTree.d.ts.map +1 -1
  190. package/types/lib/core/projectConfig.d.ts +47 -8
  191. package/types/lib/core/projectConfig.d.ts.map +1 -1
  192. package/types/lib/core/toolRegistry.d.ts +36 -0
  193. package/types/lib/core/toolRegistry.d.ts.map +1 -0
  194. package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
  195. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  196. package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
  197. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
  198. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  199. package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
  200. package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
  201. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  202. package/types/lib/ecosystems/gradleutils.d.ts +294 -20
  203. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  205. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  206. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  207. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  208. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  209. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
  210. package/types/lib/ecosystems/parsers-js.d.ts +17 -20
  211. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  212. package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
  213. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  214. package/types/lib/ecosystems/parsers-python.d.ts +10 -0
  215. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  216. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  217. package/types/lib/ecosystems/pythonutils.d.ts +19 -1
  218. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  219. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  220. package/types/lib/ecosystems/rubyutils.d.ts +144 -12
  221. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  222. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  223. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  224. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  225. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  226. package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
  227. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
  228. package/types/lib/ecosystems/venvProbe.d.ts +40 -0
  229. package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
  230. package/types/lib/evinser/evinser.d.ts +120 -13
  231. package/types/lib/evinser/evinser.d.ts.map +1 -1
  232. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  233. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  234. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  235. package/types/lib/helpers/bomSigner.d.ts +20 -3
  236. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  237. package/types/lib/helpers/jssSigner.d.ts +288 -0
  238. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  239. package/types/lib/helpers/vsixutils.d.ts +303 -19
  240. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  241. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  242. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  243. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  244. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  245. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  246. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  247. package/types/lib/inventory/aiProviders.d.ts +85 -0
  248. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  249. package/types/lib/inventory/atomUtils.d.ts +19 -0
  250. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  251. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  252. package/types/lib/inventory/brotliDecoder.d.ts +58 -0
  253. package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
  254. package/types/lib/inventory/cbomutils.d.ts +17 -0
  255. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  256. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  257. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  258. package/types/lib/inventory/citations.d.ts +5 -0
  259. package/types/lib/inventory/citations.d.ts.map +1 -1
  260. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  261. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  262. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  263. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  264. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  265. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  266. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  267. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  268. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  269. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  270. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  271. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  272. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  273. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  274. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  275. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  276. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  277. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  278. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  279. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  280. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  281. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  282. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  283. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  284. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  285. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  286. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  287. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  288. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  289. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  290. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  291. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  292. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  293. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  294. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  295. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  296. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  297. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  298. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  299. package/types/lib/inventory/deps.d.ts +35 -2
  300. package/types/lib/inventory/deps.d.ts.map +1 -1
  301. package/types/lib/inventory/dosai.d.ts +36 -1
  302. package/types/lib/inventory/dosai.d.ts.map +1 -1
  303. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  304. package/types/lib/inventory/dotnetRuntime.d.ts +91 -0
  305. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
  306. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  307. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  308. package/types/lib/inventory/envcontext.d.ts +74 -12
  309. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  310. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  311. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  312. package/types/lib/inventory/gtfobins.d.ts.map +1 -1
  313. package/types/lib/inventory/hbom.d.ts +11 -0
  314. package/types/lib/inventory/hbom.d.ts.map +1 -1
  315. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  316. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  317. package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
  318. package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
  319. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  320. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  321. package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
  322. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  323. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
  324. package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
  325. package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
  326. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  327. package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
  328. package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
  329. package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
  330. package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
  331. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  332. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  333. package/types/lib/inventory/osPackageResolver.d.ts +38 -0
  334. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  335. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
  336. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
  337. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  338. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  339. package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
  340. package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
  341. package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
  342. package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
  343. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  344. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  345. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  346. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  347. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  348. package/types/lib/inventory/source.d.ts +25 -0
  349. package/types/lib/inventory/source.d.ts.map +1 -1
  350. package/types/lib/managers/binary.d.ts.map +1 -1
  351. package/types/lib/managers/docker.d.ts +12 -0
  352. package/types/lib/managers/docker.d.ts.map +1 -1
  353. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  354. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  355. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  356. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  357. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  358. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  359. package/types/lib/parsers/largeJson.d.ts +4 -3
  360. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  361. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  362. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  363. package/types/lib/server/server.d.ts.map +1 -1
  364. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  365. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  366. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  367. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  368. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  369. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  370. package/types/lib/stages/pregen/pregen.d.ts +12 -8
  371. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  372. package/types/lib/validator/bomValidator.d.ts +5 -1
  373. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  374. package/types/lib/validator/complianceEngine.d.ts +4 -4
  375. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  376. package/types/lib/validator/complianceRules.d.ts +14 -2
  377. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  378. package/types/lib/validator/index.d.ts +10 -0
  379. package/types/lib/validator/index.d.ts.map +1 -1
  380. package/types/lib/validator/reporters/json.d.ts.map +1 -1
package/lib/cli/index.js CHANGED
@@ -50,9 +50,14 @@ import {
50
50
  buildDependencyTrackBomRequest,
51
51
  getDependencyTrackBomApiUrl,
52
52
  } from "../helpers/remote/dependency-track.js";
53
+ import { collectObomIdeInventory } from "../helpers/vsixutils.js";
53
54
  import { optionIncludesAiInventoryProjectType } from "../inventory/aiInventory.js";
54
55
  import { readApkInstalledDb } from "../inventory/apkInstalledDb.js";
55
- import { isCycloneDxComponentTypeEnabled } from "../inventory/bomUtils.js";
56
+ import {
57
+ isCycloneDx20SpecVersion,
58
+ isCycloneDxComponentTypeEnabled,
59
+ } from "../inventory/bomUtils.js";
60
+ import { DEPENDENCY_TRACK_CDX20_ERROR } from "../inventory/cyclonedx20.js";
56
61
  import {
57
62
  mergeDependencies,
58
63
  mergeServices,
@@ -70,16 +75,28 @@ import {
70
75
  hasHbomProjectType,
71
76
  } from "../inventory/hbom.js";
72
77
  import { mergeHostInventoryBoms } from "../inventory/hostTopology.js";
78
+ import { probeHostNode } from "../inventory/nodeHostProbe.js";
79
+ import { markOsInstalledLanguagePackages } from "../inventory/osPackageResolver.js";
73
80
  import { orderParentCandidates } from "../inventory/parentSelection.js";
74
81
  import { probeHostPhp } from "../inventory/phpHostProbe.js";
75
82
  // Imported directly rather than through the utils.js barrel: purl validity is
76
83
  // checked in a hot path and the barrel is already the root of several import
77
84
  // cycles.
78
85
  import { applyPurl, ociPurl } from "../inventory/purl.js";
86
+ import {
87
+ deriveOsPackagedRubyRuntimes,
88
+ linkDefaultGemsToRuntimes,
89
+ } from "../inventory/rubyBuildInfo.js";
90
+ import { probeHostRuby } from "../inventory/rubyHostProbe.js";
79
91
  import { table } from "../inventory/table.js";
80
92
 
81
93
  export { summarizeAiInventory } from "../inventory/aiInventory.js";
82
94
 
95
+ import { probeHostRubyGems } from "../ecosystems/gemHostProbe.js";
96
+ import {
97
+ mergeHostPythonEnvironmentPackages,
98
+ probeHostPythonEnvironments,
99
+ } from "../ecosystems/venvProbe.js";
83
100
  import {
84
101
  enrichOSComponentsWithTrustData,
85
102
  executeOsQuery,
@@ -240,6 +257,28 @@ switch (_platform()) {
240
257
  break;
241
258
  }
242
259
 
260
+ /**
261
+ * Home directories whose editor extension folders an OBOM reads: every user's
262
+ * home from osquery when cdxgen runs as root, and otherwise the current
263
+ * user's, which is the only one it can read.
264
+ *
265
+ * @returns {string[]} Home directories
266
+ */
267
+ function obomUserHomes() {
268
+ const ownHome = homedir();
269
+ if (typeof process.getuid !== "function" || process.getuid() !== 0) {
270
+ return [ownHome];
271
+ }
272
+ const homes = new Set([ownHome]);
273
+ for (const row of executeOsQuery("SELECT directory FROM users;") || []) {
274
+ const dir = row?.directory;
275
+ if (typeof dir === "string" && dir.startsWith("/") && safeExistsSync(dir)) {
276
+ homes.add(dir);
277
+ }
278
+ }
279
+ return [...homes];
280
+ }
281
+
243
282
  /**
244
283
  * Function to create obom string for the current OS using osquery
245
284
  *
@@ -247,7 +286,7 @@ switch (_platform()) {
247
286
  * @param {Object} options Parse options from the cli
248
287
  * @returns {Promise<Object>} Promise resolving to BOM object
249
288
  */
250
- export function createOSBom(_path, options) {
289
+ export async function createOSBom(_path, options) {
251
290
  console.warn(
252
291
  "About to generate OBOM for the current OS installation. This will take several minutes ...",
253
292
  );
@@ -309,6 +348,53 @@ export function createOSBom(_path, options) {
309
348
  if (hostPhp.components.length) {
310
349
  pkgList = pkgList.concat(hostPhp.components);
311
350
  }
351
+ // Nor a Node.js from a release tarball or a version manager, or the Brotli
352
+ // decoder compiled into it.
353
+ const hostNode = probeHostNode({
354
+ executeOsQuery,
355
+ packageComponents: pkgList,
356
+ });
357
+ if (hostNode.components.length) {
358
+ pkgList = pkgList.concat(hostNode.components);
359
+ }
360
+ // Nor a Ruby from ruby-build, a version manager or a toolcache tarball.
361
+ const hostRuby = probeHostRuby({
362
+ executeOsQuery,
363
+ packageComponents: pkgList,
364
+ });
365
+ if (hostRuby.components.length) {
366
+ pkgList = pkgList.concat(hostRuby.components);
367
+ }
368
+ // A Ruby a package manager installed is also named by its upstream version.
369
+ pkgList = pkgList.concat(deriveOsPackagedRubyRuntimes(pkgList));
370
+ // osquery has no table for RubyGems; the gems of the host's gem homes are
371
+ // read from their specifications.
372
+ const hostGems = await probeHostRubyGems({
373
+ executeOsQuery,
374
+ packageComponents: pkgList,
375
+ });
376
+ if (hostGems.length) {
377
+ const knownRefs = new Set(pkgList.map((component) => component["bom-ref"]));
378
+ pkgList = pkgList.concat(
379
+ hostGems.filter((gem) => !knownRefs.has(gem["bom-ref"])),
380
+ );
381
+ }
382
+ // osquery reads the interpreter's own site-packages; packages installed into
383
+ // a virtual environment or a pyenv interpreter are read from their METADATA.
384
+ pkgList = mergeHostPythonEnvironmentPackages(
385
+ pkgList,
386
+ probeHostPythonEnvironments(),
387
+ );
388
+ // osquery reads the extension registry of a fixed list of editors; the
389
+ // editors themselves, and the extensions of the others, come from the
390
+ // extension folders. A root scan reads every user's home, as osquery does.
391
+ try {
392
+ pkgList = pkgList.concat(
393
+ collectObomIdeInventory(pkgList, { homes: obomUserHomes() }),
394
+ );
395
+ } catch (err) {
396
+ thoughtLog(`Unable to read the editor extension folders: ${err.message}`);
397
+ }
312
398
  const hostTrustInventory = enrichOSComponentsWithTrustData(pkgList);
313
399
  if (hostTrustInventory?.components?.length) {
314
400
  pkgList = hostTrustInventory.components;
@@ -770,13 +856,52 @@ export function summarizePurlTypes(components) {
770
856
  * data object (components, dependencies, bomJson, parentComponent, etc.)
771
857
  */
772
858
  export async function createMultiXBom(pathList, options) {
859
+ /**
860
+ * What createMultiXBom reads from the result of a create*Bom builder.
861
+ *
862
+ * @typedef {Object} BuilderBomData
863
+ * @property {{components?: Object[], dependencies?: Object[], services?: Object[]}} [bomJson] The generated BOM
864
+ * @property {Object} [parentComponent] The project the builder detected
865
+ * @property {Object[]} [dependencies] Dependencies returned beside bomJson (crypto scan)
866
+ * @property {Object[]} [formulationList] Formulation components for postProcess
867
+ * @property {Object[]} [metadataProperties] Properties for metadata (php platform data)
868
+ */
869
+
870
+ /**
871
+ * One ecosystem that createMultiXBom scans every path for.
872
+ *
873
+ * @typedef {Object} PathScanStep
874
+ * @property {(path: string, options: Object) => boolean} applies Whether the step runs for this path
875
+ * @property {(path: string, options: Object) => any} [prepare] Computes a value once for the hooks below
876
+ * @property {(path: string, options: Object, context: any) => void} [announce] Thought logs written before the scan
877
+ * @property {string|((context: any) => string)} activityType Project type recorded in the activity context
878
+ * @property {(path: string) => void} [beforeBuild] Runs after the activity context is set
879
+ * @property {(path: string, options: Object) => any} build The create*Bom builder, whose result is read as BuilderBomData
880
+ * @property {(bomData: BuilderBomData | undefined) => boolean} [isUseful] Whether the result is merged; by default, when it has components
881
+ * @property {(count: number, path: string, context: any) => string} [foundThought] Thought log for the components found
882
+ * @property {(count: number, path: string, context: any) => string} debugMessage Debug log for the components found
883
+ * @property {"concat"|"topLevel"|"mergeWithParent"} [dependencyMerge] How dependencies are merged; mergeDependencies by default
884
+ * @property {boolean} [mergeServices] Merge bomJson.services into options.services
885
+ * @property {boolean} [keepParent] Record the detected project as a sub-component; true by default
886
+ * @property {"plain"|"gradle"} [hoistSubComponents] Hoist the project's own sub-components
887
+ * @property {boolean} [collectMetadataProperties] Collect metadataProperties whether or not components were found
888
+ * @property {boolean} [collectFormulation] Collect the formulation list whether or not components were found
889
+ */
890
+
891
+ /** @type {Object[]} */
773
892
  let components = [];
893
+ /** @type {Object[]} */
774
894
  let dependencies = [];
895
+ /** @type {BuilderBomData | undefined} */
775
896
  let bomData;
897
+ /** @type {Object[]} */
776
898
  let formulationList = [];
899
+ /** @type {Object[]} */
777
900
  let metadataProperties = [];
778
901
  let parentComponent = determineParentComponent(options) || {};
902
+ /** @type {Object[]} */
779
903
  let parentSubComponents = [];
904
+ let osFileOwners;
780
905
  const setProjectTypeActivityContext = (projectType, sourcePath) => {
781
906
  setActivityContext({
782
907
  projectType,
@@ -797,6 +922,8 @@ export async function createMultiXBom(pathList, options) {
797
922
  const {
798
923
  osPackages,
799
924
  osPackageFiles,
925
+ nativeExtensionFiles,
926
+ ownerByPath,
800
927
  dependenciesList,
801
928
  allTypes,
802
929
  bundledSdks,
@@ -811,6 +938,7 @@ export async function createMultiXBom(pathList, options) {
811
938
  options.exportData?.inspectData?.Config,
812
939
  options,
813
940
  );
941
+ osFileOwners = ownerByPath;
814
942
  osPhase.succeed(
815
943
  summarizeCounts([
816
944
  [osPackages.length, "OS package", "OS packages"],
@@ -859,6 +987,7 @@ export async function createMultiXBom(pathList, options) {
859
987
  components = components.concat(osPackageFiles || []);
860
988
  components = components.concat(executables);
861
989
  components = components.concat(sharedLibs);
990
+ components = components.concat(nativeExtensionFiles || []);
862
991
  if (dependenciesList?.length) {
863
992
  dependencies = mergeDependencies(dependencies, dependenciesList);
864
993
  }
@@ -871,945 +1000,602 @@ export async function createMultiXBom(pathList, options) {
871
1000
  osPackages.concat(osPackageFiles || []).map((p) => p["bom-ref"]),
872
1001
  );
873
1002
  dependencies.splice(0, 0, {
874
- ref: parentComponent["bom-ref"],
875
- dependsOn: Array.from(parentDependsOn).sort(),
876
- });
877
- }
878
- }
879
- if (hasAnyProjectType(["os"], options, false) && options.bomData) {
880
- bomData = options.bomData;
881
- if (bomData?.bomJson?.components) {
882
- if (DEBUG_MODE) {
883
- console.log(`Found ${bomData.bomJson.components.length} OS components`);
884
- }
885
- if (bomData.bomJson.components.length) {
886
- thoughtLog(
887
- `I found ${bomData.bomJson.components.length} OS packages 😎.`,
888
- );
889
- }
890
- components = components.concat(bomData.bomJson.components);
891
- }
892
- }
893
- const sharedOptions = options;
894
- // The exploded image layers are written once by the extractor and only read
895
- // from here on, so repeated walks of them can share one view of the tree.
896
- setDirWalkCacheRoot(options.allLayersExplodedDir);
897
- const scanOnePath = async (path) => {
898
- try {
899
- return await scanOnePathInner(path);
900
- } catch (err) {
901
- if (!isDeferredFailOnError(err)) {
902
- throw err;
903
- }
904
- // The failing path contributes nothing, and the remaining paths still
905
- // do: fail-on-error scopes the loss to the leg that failed.
906
- return {
907
- components: [],
908
- dependencies: [],
909
- options: { ...sharedOptions },
910
- parentSubComponents: [],
911
- metadataProperties: [],
912
- };
913
- }
914
- };
915
- const scanOnePathInner = async (path) => {
916
- // Each path accumulates into its own lists and its own view of options, so
917
- // that legs which record a parent component or a tool cannot overwrite
918
- // another path's. The lists are merged below in pathList order.
919
- const options = { ...sharedOptions };
920
- let bomData;
921
- let components = [];
922
- let dependencies = [];
923
- let parentSubComponents = [];
924
- let metadataProperties = [];
925
- // Each builder detects the project of its own ecosystem. A builder that sets
926
- // options.parentComponent to force its project into its own BOM must not
927
- // make that project the next builder's too, or the next ecosystem's project
928
- // is lost: every builder starts from the parent the caller gave.
929
- const callerParent = options.parentComponent;
930
- const withOwnParent = async (build) => {
931
- options.parentComponent = callerParent;
932
- try {
933
- return await build();
934
- } finally {
935
- options.parentComponent = callerParent;
936
- }
937
- };
938
- setActivityContext({
939
- projectType: Array.isArray(options.projectType)
940
- ? options.projectType.join(",")
941
- : options.projectType,
942
- sourcePath: path,
943
- });
944
- recordActivity({
945
- kind: "read",
946
- reason:
947
- "Scanning project path for supported languages and package managers.",
948
- status: "completed",
949
- target: path,
950
- });
951
- if (DEBUG_MODE) {
952
- console.log("Scanning", path);
953
- }
954
- if (pathList.length > 2) {
955
- thoughtLog(`Let's thoroughly check the path ${path}.`);
956
- }
957
- // Node.js and AI inventory
958
- if (
959
- hasAnyProjectType(["oci", "js"], options) ||
960
- hasAnyProjectType(["mcp", "ai-skill", "ai"], options) ||
961
- getDirectAiInventoryType(path, options)
962
- ) {
963
- const exactAiInventoryType = getDirectAiInventoryType(path, options);
964
- if (!hasAnyProjectType(["oci"], options, false)) {
965
- if (exactAiInventoryType === "mcp") {
966
- thoughtLog(
967
- "**MCP**: Looking for MCP services, MCP configs, and related AI control-plane artifacts.",
968
- );
969
- } else if (exactAiInventoryType === "ai") {
970
- thoughtLog(
971
- "**AI-BOM**: Looking for AI models, direct model metadata, pedigree, quantization, and operator-facing AI context.",
972
- );
973
- } else if (exactAiInventoryType === "ai-skill") {
974
- thoughtLog(
975
- "**AI-SKILL**: Looking for AI instruction, skill, and agent-definition files that can influence build or release flows.",
976
- );
977
- } else {
978
- thoughtLog(
979
- "**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
980
- );
981
- }
982
- }
983
- setProjectTypeActivityContext(exactAiInventoryType || "js", path);
984
- bomData = await withOwnParent(() => createNodejsBom(path, options));
985
- if (bomData?.bomJson?.components?.length) {
986
- if (exactAiInventoryType) {
987
- thoughtLog(
988
- `I found ${bomData.bomJson.components.length} ${exactAiInventoryType} component(s). Let's keep looking.`,
989
- );
990
- } else {
991
- thoughtLog(
992
- `I found ${bomData.bomJson.components.length} npm packages. Let's keep looking.`,
993
- );
994
- }
995
- if (DEBUG_MODE) {
996
- console.log(
997
- `Found ${bomData.bomJson.components.length} ${exactAiInventoryType || "npm"} components at ${path}`,
998
- );
999
- }
1000
- components = components.concat(bomData.bomJson.components);
1001
- dependencies = mergeDependencies(
1002
- dependencies,
1003
- bomData.bomJson.dependencies,
1004
- );
1005
- if (bomData?.bomJson?.services?.length) {
1006
- options.services = mergeServices(
1007
- options.services || [],
1008
- bomData.bomJson.services,
1009
- );
1010
- }
1011
- if (
1012
- bomData.parentComponent &&
1013
- Object.keys(bomData.parentComponent).length
1014
- ) {
1015
- parentSubComponents.push(bomData.parentComponent);
1016
- }
1017
- // Retain metadata.component.components
1018
- if (bomData.parentComponent.components?.length) {
1019
- parentSubComponents = parentSubComponents.concat(
1020
- bomData.parentComponent.components,
1021
- );
1022
- delete bomData.parentComponent.components;
1023
- }
1024
- }
1025
- }
1026
- // Java
1027
- if (hasAnyProjectType(["oci", "java"], options)) {
1028
- if (!hasAnyProjectType(["oci"], options, false)) {
1029
- thoughtLog(
1030
- "**JAVA**: Looking for Java projects (e.g., Maven, Gradle, SBT). I hope all configurations—from Java version to individual build settings—are correctly aligned.",
1031
- );
1032
- }
1033
- setProjectTypeActivityContext("java", path);
1034
- bomData = await withOwnParent(() => createJavaBom(path, options));
1035
- if (bomData?.bomJson?.components?.length) {
1036
- thoughtLog(
1037
- `I found ${bomData.bomJson.components.length} java packages.`,
1038
- );
1039
- if (DEBUG_MODE) {
1040
- console.log(
1041
- `Found ${bomData.bomJson.components.length} java packages at ${path}`,
1042
- );
1043
- }
1044
- components = components.concat(bomData.bomJson.components);
1045
- dependencies = mergeDependencies(
1046
- dependencies,
1047
- bomData.bomJson.dependencies,
1048
- );
1049
- if (
1050
- bomData.parentComponent &&
1051
- Object.keys(bomData.parentComponent).length
1052
- ) {
1053
- parentSubComponents.push(bomData.parentComponent);
1054
- }
1055
- // Retain metadata.component.components, but add duplicates to the list of current components
1056
- // and removing them from metadata.component.components -- the components are merged later
1057
- if (bomData.parentComponent.components?.length) {
1058
- let bomSubComponents = bomData.parentComponent.components;
1059
- if (
1060
- !["false", "0"].includes(
1061
- readEnvironmentVariable("GRADLE_RESOLVE_FROM_NODE"),
1062
- )
1063
- ) {
1064
- thoughtLog(
1065
- "Wait, the user wants me to resolve gradle projects from npm.",
1066
- );
1067
- const allRefs = components.map((c) => c["bom-ref"]);
1068
- const duplicateComponents = bomSubComponents.filter((c) =>
1069
- allRefs.includes(c["bom-ref"]),
1070
- );
1071
- components = components.concat(duplicateComponents);
1072
- const duplicateComponentRefs = duplicateComponents.map(
1073
- (c) => c["bom-ref"],
1074
- );
1075
- bomSubComponents = bomSubComponents.filter(
1076
- (c) => !duplicateComponentRefs.includes(c["bom-ref"]),
1077
- );
1078
- }
1079
- parentSubComponents = parentSubComponents.concat(bomSubComponents);
1080
- delete bomData.parentComponent.components;
1081
- }
1082
- }
1083
- }
1084
- if (hasAnyProjectType(["oci", "py"], options)) {
1085
- if (!hasAnyProjectType(["oci"], options, false)) {
1086
- thoughtLog(
1087
- "**PYTHON**: Looking for Python projects with package managers such as pip, poetry, uv, etc. Wish me good luck!",
1088
- );
1089
- }
1090
- if (
1091
- readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true" &&
1092
- pathList.length <= 2
1093
- ) {
1094
- thoughtLog(
1095
- "I'm running in a non-container environment. Let's hope the correct build tools are available ✌️.",
1096
- );
1097
- }
1098
- setProjectTypeActivityContext("py", path);
1099
- bomData = await withOwnParent(() => createPythonBom(path, options));
1100
- if (bomData?.bomJson?.components?.length) {
1101
- thoughtLog(
1102
- `I found ${bomData.bomJson.components.length} python packages.`,
1103
- );
1104
- if (DEBUG_MODE) {
1105
- console.log(
1106
- `Found ${bomData.bomJson.components.length} python packages at ${path}`,
1107
- );
1108
- }
1109
- components = components.concat(bomData.bomJson.components);
1110
- dependencies = mergeDependencies(
1111
- dependencies,
1112
- bomData.bomJson.dependencies,
1113
- );
1114
- if (
1115
- bomData.parentComponent &&
1116
- Object.keys(bomData.parentComponent).length
1117
- ) {
1118
- parentSubComponents.push(bomData.parentComponent);
1119
- }
1120
- // Retain metadata.component.components. uv workspace members are
1121
- // nested under the python project, so hoisting them keeps them
1122
- // siblings of the other sub-components instead of riding one level
1123
- // deeper (or being dropped by the same-name collapse) when another
1124
- // ecosystem's project becomes the parent.
1125
- if (bomData.parentComponent?.components?.length) {
1126
- parentSubComponents = parentSubComponents.concat(
1127
- bomData.parentComponent.components,
1128
- );
1129
- delete bomData.parentComponent.components;
1130
- }
1131
- }
1132
- if (bomData?.formulationList?.length) {
1133
- formulationList = formulationList.concat(bomData.formulationList);
1134
- }
1135
- }
1136
- if (hasAnyProjectType(["oci", "go"], options)) {
1137
- if (!hasAnyProjectType(["oci"], options, false)) {
1138
- thoughtLog(
1139
- "**GO**: Looking for go projects. I need to be cautious about purl namespaces and potential failures with the 'go list' command.",
1140
- );
1141
- }
1142
- setProjectTypeActivityContext("go", path);
1143
- bomData = await withOwnParent(() => createGoBom(path, options));
1144
- if (bomData?.bomJson?.components?.length) {
1145
- thoughtLog(`I found ${bomData.bomJson.components.length} go packages.`);
1146
- if (DEBUG_MODE) {
1147
- console.log(
1148
- `Found ${bomData.bomJson.components.length} go packages at ${path}`,
1149
- );
1150
- }
1151
- components = components.concat(bomData.bomJson.components);
1152
- dependencies = mergeDependencies(
1153
- dependencies,
1154
- bomData.bomJson.dependencies,
1155
- );
1156
- if (
1157
- bomData.parentComponent &&
1158
- Object.keys(bomData.parentComponent).length
1159
- ) {
1160
- parentSubComponents.push(bomData.parentComponent);
1161
- }
1162
- }
1163
- }
1164
- if (hasAnyProjectType(["oci", "rust"], options)) {
1165
- if (!hasAnyProjectType(["oci"], options, false)) {
1166
- thoughtLog(
1167
- "**RUST**: Let's search for Cargo/Rust projects. Should I warn the user that we don't support Cargo 'features' and native dependencies, which may lead to both false positives and false negatives? 🤔?",
1168
- );
1169
- }
1170
- setProjectTypeActivityContext("rust", path);
1171
- bomData = await withOwnParent(() => createRustBom(path, options));
1172
- if (bomData?.bomJson?.components?.length) {
1173
- thoughtLog(
1174
- `I found ${bomData.bomJson.components.length} rust packages.`,
1175
- );
1176
- if (DEBUG_MODE) {
1177
- console.log(
1178
- `Found ${bomData.bomJson.components.length} rust packages at ${path}`,
1179
- );
1180
- }
1181
- components = components.concat(bomData.bomJson.components);
1182
- dependencies = mergeDependencies(
1183
- dependencies,
1184
- bomData.bomJson.dependencies,
1185
- );
1186
- if (
1187
- bomData.parentComponent &&
1188
- Object.keys(bomData.parentComponent).length
1189
- ) {
1190
- parentSubComponents.push(bomData.parentComponent);
1191
- }
1192
- // Retain metadata.component.components
1193
- if (bomData.parentComponent?.components?.length) {
1194
- parentSubComponents = parentSubComponents.concat(
1195
- bomData.parentComponent.components,
1196
- );
1197
- delete bomData.parentComponent.components;
1198
- }
1199
- }
1200
- }
1201
- if (hasAnyProjectType(["oci", "php"], options)) {
1202
- if (!hasAnyProjectType(["oci"], options, false)) {
1203
- thoughtLog(
1204
- "**PHP**: About to search for Composer-based projects. I hope lock files are available; otherwise, the 'composer install' command might fail for various reasons.",
1205
- );
1206
- }
1207
- setProjectTypeActivityContext("php", path);
1208
- bomData = await withOwnParent(() => createPHPBom(path, options));
1209
- // A lock with no packages can still hold the project's platform data.
1210
- if (bomData?.metadataProperties?.length) {
1211
- metadataProperties = metadataProperties.concat(
1212
- bomData.metadataProperties,
1213
- );
1214
- }
1215
- if (bomData?.bomJson?.components?.length) {
1216
- thoughtLog(
1217
- `I found ${bomData.bomJson.components.length} php packages.`,
1218
- );
1219
- if (DEBUG_MODE) {
1220
- console.log(
1221
- `Found ${bomData.bomJson.components.length} php packages at ${path}`,
1222
- );
1223
- }
1224
- components = components.concat(bomData.bomJson.components);
1225
- dependencies = mergeDependencies(
1226
- dependencies,
1227
- bomData.bomJson.dependencies,
1228
- );
1229
- if (
1230
- bomData.parentComponent &&
1231
- Object.keys(bomData.parentComponent).length
1232
- ) {
1233
- parentSubComponents.push(bomData.parentComponent);
1234
- }
1235
- // Retain metadata.component.components
1236
- if (bomData.parentComponent?.components?.length) {
1237
- parentSubComponents = parentSubComponents.concat(
1238
- bomData.parentComponent.components,
1239
- );
1240
- delete bomData.parentComponent.components;
1241
- }
1242
- }
1243
- }
1244
- if (hasAnyProjectType(["oci", "ruby"], options)) {
1245
- if (!hasAnyProjectType(["oci"], options, false)) {
1246
- thoughtLog(
1247
- "**RUBY**: Are there any Ruby projects in this path? There's only one way to know.",
1248
- );
1249
- }
1250
- setProjectTypeActivityContext("ruby", path);
1251
- bomData = await withOwnParent(() => createRubyBom(path, options));
1252
- if (bomData?.bomJson?.components?.length) {
1253
- thoughtLog(
1254
- `We got ${bomData.bomJson.components.length} ruby packages.`,
1255
- );
1256
- if (DEBUG_MODE) {
1257
- console.log(
1258
- `Found ${bomData.bomJson.components.length} ruby packages at ${path}`,
1259
- );
1260
- }
1261
- components = components.concat(bomData.bomJson.components);
1262
- dependencies = mergeDependencies(
1263
- dependencies,
1264
- bomData.bomJson.dependencies,
1265
- bomData.parentComponent,
1266
- );
1267
- if (
1268
- bomData.parentComponent &&
1269
- Object.keys(bomData.parentComponent).length
1270
- ) {
1271
- parentSubComponents.push(bomData.parentComponent);
1272
- }
1273
- // Retain metadata.component.components
1274
- if (bomData.parentComponent?.components?.length) {
1275
- parentSubComponents = parentSubComponents.concat(
1276
- bomData.parentComponent.components,
1277
- );
1278
- delete bomData.parentComponent.components;
1279
- }
1280
- }
1281
- }
1282
- if (hasAnyProjectType(["oci", "csharp"], options)) {
1283
- if (!hasAnyProjectType(["oci"], options, false)) {
1284
- thoughtLog("**CSHARP**: What about csharp and fsharp projects?");
1285
- }
1286
- setProjectTypeActivityContext("csharp", path);
1287
- bomData = await withOwnParent(() => createCsharpBom(path, options));
1288
- // Every project the scan read has a dependency entry of its own, so a
1289
- // project that uses no packages is kept along with its parent.
1290
- if (
1291
- bomData?.bomJson?.components?.length ||
1292
- bomData?.bomJson?.dependencies?.length
1293
- ) {
1294
- thoughtLog(
1295
- `There are ${bomData.bomJson.components.length} csharp packages.`,
1296
- );
1297
- if (DEBUG_MODE) {
1298
- console.log(
1299
- `Found ${bomData.bomJson.components.length} csharp packages at ${path}`,
1300
- );
1301
- }
1302
- components = components.concat(bomData.bomJson.components);
1303
- dependencies = mergeDependencies(
1304
- dependencies,
1305
- bomData.bomJson.dependencies,
1306
- );
1307
- if (
1308
- bomData.parentComponent &&
1309
- Object.keys(bomData.parentComponent).length
1310
- ) {
1311
- parentSubComponents.push(bomData.parentComponent);
1312
- }
1313
- // Retain metadata.component.components
1314
- if (bomData.parentComponent?.components?.length) {
1315
- parentSubComponents = parentSubComponents.concat(
1316
- bomData.parentComponent.components,
1317
- );
1318
- delete bomData.parentComponent.components;
1319
- }
1320
- }
1321
- }
1322
- if (hasAnyProjectType(["oci", "dart"], options)) {
1323
- if (!hasAnyProjectType(["oci"], options, false)) {
1324
- thoughtLog(
1325
- "**DART**: Looking for Dart projects. These are rare ones. Should I inform the user that they can pass the types argument via the command-line to speed things up?",
1326
- );
1327
- }
1328
- setProjectTypeActivityContext("dart", path);
1329
- bomData = await withOwnParent(() => createDartBom(path, options));
1330
- if (bomData?.bomJson?.components?.length) {
1331
- thoughtLog(
1332
- `I found ${bomData.bomJson.components.length} pub packages.`,
1333
- );
1334
- if (DEBUG_MODE) {
1335
- console.log(
1336
- `Found ${bomData.bomJson.components.length} pub packages at ${path}`,
1337
- );
1338
- }
1339
- components = components.concat(bomData.bomJson.components);
1340
- dependencies = mergeDependencies(
1341
- dependencies,
1342
- bomData.bomJson.dependencies,
1343
- );
1344
- if (
1345
- bomData.parentComponent &&
1346
- Object.keys(bomData.parentComponent).length
1347
- ) {
1348
- parentSubComponents.push(bomData.parentComponent);
1349
- }
1350
- }
1351
- }
1352
- if (hasAnyProjectType(["oci", "haskell"], options)) {
1353
- if (!hasAnyProjectType(["oci"], options, false)) {
1354
- thoughtLog(
1355
- "**HASKELL**: Looking for Haskell projects. They're rarely encountered.",
1356
- );
1357
- }
1358
- setProjectTypeActivityContext("haskell", path);
1359
- bomData = await withOwnParent(() => createHaskellBom(path, options));
1360
- if (bomData?.bomJson?.components?.length) {
1361
- thoughtLog(
1362
- `I found ${bomData.bomJson.components.length} hackage packages.`,
1363
- );
1364
- if (DEBUG_MODE) {
1365
- console.log(
1366
- `Found ${bomData.bomJson.components.length} hackage packages at ${path}`,
1367
- );
1368
- }
1369
- components = components.concat(bomData.bomJson.components);
1370
- dependencies = mergeDependencies(
1371
- dependencies,
1372
- bomData.bomJson.dependencies,
1373
- );
1374
- if (
1375
- bomData.parentComponent &&
1376
- Object.keys(bomData.parentComponent).length
1377
- ) {
1378
- parentSubComponents.push(bomData.parentComponent);
1379
- }
1380
- }
1381
- }
1382
- if (hasAnyProjectType(["oci", "elixir"], options)) {
1383
- if (!hasAnyProjectType(["oci"], options, false)) {
1384
- thoughtLog(
1385
- "**ELIXIR**: Looking for Elixir projects—they're quite rare as well.",
1386
- );
1387
- }
1388
- setProjectTypeActivityContext("elixir", path);
1389
- bomData = await withOwnParent(() => createElixirBom(path, options));
1390
- if (bomData?.bomJson?.components?.length) {
1391
- thoughtLog(
1392
- `I found ${bomData.bomJson.components.length} mix packages.`,
1393
- );
1394
- if (DEBUG_MODE) {
1395
- console.log(
1396
- `Found ${bomData.bomJson.components.length} mix packages at ${path}`,
1397
- );
1398
- }
1399
- components = components.concat(bomData.bomJson.components);
1400
- dependencies = mergeDependencies(
1401
- dependencies,
1402
- bomData.bomJson.dependencies,
1403
- );
1404
- if (
1405
- bomData.parentComponent &&
1406
- Object.keys(bomData.parentComponent).length
1407
- ) {
1408
- parentSubComponents.push(bomData.parentComponent);
1409
- }
1410
- }
1411
- }
1412
- if (hasAnyProjectType(["oci", "c"], options)) {
1413
- if (!hasAnyProjectType(["oci"], options, false)) {
1414
- thoughtLog(
1415
- "**C/C++**: Looking for C/C++ projects. Should I warn the user that the generated SBOM might have low accuracy and contain errors?",
1416
- );
1417
- }
1418
- setProjectTypeActivityContext("c", path);
1419
- bomData = await withOwnParent(() => createCppBom(path, options));
1420
- if (bomData?.bomJson?.components?.length) {
1421
- thoughtLog(
1422
- `I found ${bomData.bomJson.components.length} cpp packages.`,
1423
- );
1424
- if (DEBUG_MODE) {
1425
- console.log(
1426
- `Found ${bomData.bomJson.components.length} cpp packages at ${path}`,
1427
- );
1428
- }
1429
- components = components.concat(bomData.bomJson.components);
1430
- dependencies = mergeDependencies(
1431
- dependencies,
1432
- bomData.bomJson.dependencies,
1433
- );
1434
- if (
1435
- bomData.parentComponent &&
1436
- Object.keys(bomData.parentComponent).length
1437
- ) {
1438
- parentSubComponents.push(bomData.parentComponent);
1439
- }
1440
- }
1441
- // presets and compilers describe the build, whatever it depends on
1442
- if (bomData?.formulationList?.length) {
1443
- formulationList = formulationList.concat(bomData.formulationList);
1444
- }
1445
- }
1446
- if (hasAnyProjectType(["oci", "clojure"], options)) {
1447
- if (!hasAnyProjectType(["oci"], options, false)) {
1448
- thoughtLog(
1449
- "**CLOJURE**: Looking for Clojure projects. Should I warn the user that the purl namespace 'clojars' isn't widely supported by tools like Dependency-Track?",
1450
- );
1451
- }
1452
- setProjectTypeActivityContext("clojure", path);
1453
- bomData = await withOwnParent(() => createClojureBom(path, options));
1454
- if (bomData?.bomJson?.components?.length) {
1455
- thoughtLog(
1456
- `I found ${bomData.bomJson.components.length} clojure packages.`,
1457
- );
1458
- if (DEBUG_MODE) {
1459
- console.log(
1460
- `Found ${bomData.bomJson.components.length} clojure packages at ${path}`,
1461
- );
1462
- }
1463
- components = components.concat(bomData.bomJson.components);
1464
- dependencies = mergeDependencies(
1465
- dependencies,
1466
- bomData.bomJson.dependencies,
1467
- );
1468
- if (
1469
- bomData.parentComponent &&
1470
- Object.keys(bomData.parentComponent).length
1471
- ) {
1472
- parentSubComponents.push(bomData.parentComponent);
1473
- }
1474
- }
1475
- }
1476
- if (hasAnyProjectType(["oci", "github"], options)) {
1477
- if (!hasAnyProjectType(["oci"], options, false)) {
1478
- thoughtLog(
1479
- "**GITHUB**: Looking for any github packages and workflows.",
1480
- );
1481
- }
1482
- setProjectTypeActivityContext("github", path);
1483
- bomData = await withOwnParent(() => createGitHubBom(path, options));
1484
- if (bomData?.bomJson?.components?.length) {
1485
- thoughtLog(
1486
- `I found ${bomData.bomJson.components.length} github action packages as well. Should I convert these to formulation instead 🤔`,
1487
- );
1488
- if (DEBUG_MODE) {
1489
- console.log(
1490
- `Found ${bomData.bomJson.components.length} GitHub action packages at ${path}`,
1491
- );
1492
- }
1493
- components = components.concat(bomData.bomJson.components);
1494
- dependencies = mergeDependencies(
1495
- dependencies,
1496
- bomData.bomJson.dependencies,
1497
- );
1498
- if (
1499
- bomData.parentComponent &&
1500
- Object.keys(bomData.parentComponent).length
1501
- ) {
1502
- parentSubComponents.push(bomData.parentComponent);
1503
- }
1504
- }
1505
- }
1506
- if (hasAnyProjectType(["oci", "cloudbuild"], options)) {
1507
- if (!hasAnyProjectType(["oci"], options, false)) {
1508
- thoughtLog(
1509
- "**CLOUDBUILD**: Let's check for CloudBuild configuration files that include package dependencies.",
1510
- );
1511
- }
1512
- setProjectTypeActivityContext("cloudbuild", path);
1513
- bomData = await withOwnParent(() => createCloudBuildBom(path, options));
1514
- if (bomData?.bomJson?.components?.length) {
1515
- thoughtLog(
1516
- `I found ${bomData.bomJson.components.length} cloudbuild packages.`,
1517
- );
1518
- if (DEBUG_MODE) {
1519
- console.log(
1520
- `Found ${bomData.bomJson.components.length} CloudBuild configuration at ${path}`,
1521
- );
1522
- }
1523
- components = components.concat(bomData.bomJson.components);
1524
- dependencies = mergeDependencies(
1525
- dependencies,
1526
- bomData.bomJson.dependencies,
1527
- );
1528
- if (
1529
- bomData.parentComponent &&
1530
- Object.keys(bomData.parentComponent).length
1531
- ) {
1532
- parentSubComponents.push(bomData.parentComponent);
1533
- }
1534
- }
1535
- }
1536
- if (hasAnyProjectType(["oci", "swift"], options)) {
1537
- if (!hasAnyProjectType(["oci"], options, false)) {
1538
- thoughtLog(
1539
- "**SWIFT**: Now checking for Swift projects. We don't support CocoaPods, Objective-C, or pure Xcode projects, so the SBOM will be incomplete.",
1540
- );
1541
- }
1542
- setProjectTypeActivityContext("swift", path);
1543
- bomData = await withOwnParent(() => createSwiftBom(path, options));
1544
- if (bomData?.bomJson?.components?.length) {
1545
- thoughtLog(
1546
- `I found ${bomData.bomJson.components.length} swift packages here.`,
1547
- );
1548
- if (DEBUG_MODE) {
1549
- console.log(
1550
- `Found ${bomData.bomJson.components.length} Swift packages at ${path}`,
1551
- );
1552
- }
1553
- components = components.concat(bomData.bomJson.components);
1554
- dependencies = mergeDependencies(
1555
- dependencies,
1556
- bomData.bomJson.dependencies,
1557
- );
1558
- if (
1559
- bomData.parentComponent &&
1560
- Object.keys(bomData.parentComponent).length
1561
- ) {
1562
- parentSubComponents.push(bomData.parentComponent);
1563
- }
1564
- }
1565
- }
1566
- if (hasAnyProjectType(["oci", "jar", "war", "ear"], options)) {
1567
- if (!hasAnyProjectType(["oci"], options, false)) {
1568
- thoughtLog(
1569
- "**JAR**: Let's check for any bundled jar/war/ear files to improve the SBOM accuracy.",
1570
- );
1571
- }
1572
- setProjectTypeActivityContext("jar", path);
1573
- recordDegradation("jvm.jar.heuristic", {
1574
- ecosystem: "java",
1575
- tool: "jar",
1576
- impact: "versions",
1577
- path,
1578
- detail:
1579
- "Components are being inferred by scanning jar/war/ear files from disk.",
1003
+ ref: parentComponent["bom-ref"],
1004
+ dependsOn: Array.from(parentDependsOn).sort(),
1580
1005
  });
1581
- bomData = await withOwnParent(() => createJarBom(path, options));
1582
- if (bomData?.bomJson?.components?.length) {
1006
+ }
1007
+ }
1008
+ if (hasAnyProjectType(["os"], options, false) && options.bomData) {
1009
+ bomData = options.bomData;
1010
+ if (bomData?.bomJson?.components) {
1011
+ if (DEBUG_MODE) {
1012
+ console.log(`Found ${bomData.bomJson.components.length} OS components`);
1013
+ }
1014
+ if (bomData.bomJson.components.length) {
1583
1015
  thoughtLog(
1584
- `I found ${bomData.bomJson.components.length} jar packages as well.`,
1585
- );
1586
- if (DEBUG_MODE) {
1587
- console.log(
1588
- `Found ${bomData.bomJson.components.length} jar packages at ${path}`,
1589
- );
1590
- }
1591
- components = components.concat(bomData.bomJson.components);
1592
- dependencies = mergeDependencies(
1593
- dependencies,
1594
- bomData.bomJson.dependencies,
1016
+ `I found ${bomData.bomJson.components.length} OS packages 😎.`,
1595
1017
  );
1596
- if (
1597
- bomData.parentComponent &&
1598
- Object.keys(bomData.parentComponent).length
1599
- ) {
1600
- parentSubComponents.push(bomData.parentComponent);
1601
- }
1602
1018
  }
1019
+ components = components.concat(bomData.bomJson.components);
1603
1020
  }
1604
- if (hasAnyProjectType(["oci", "cocoa"], options)) {
1605
- setProjectTypeActivityContext("cocoa", path);
1606
- bomData = await withOwnParent(() => createCocoaBom(path, options));
1607
- if (bomData?.bomJson?.components?.length) {
1608
- if (DEBUG_MODE) {
1609
- console.log(
1610
- `Found ${bomData.bomJson.components.length} Cocoa packages at ${path}`,
1611
- );
1612
- }
1613
- components = components.concat(bomData.bomJson.components);
1614
- dependencies = dependencies.concat(bomData.bomJson.dependencies);
1615
- if (
1616
- bomData.parentComponent &&
1617
- Object.keys(bomData.parentComponent).length
1618
- ) {
1619
- parentSubComponents.push(bomData.parentComponent);
1620
- }
1621
- }
1021
+ }
1022
+ const sharedOptions = options;
1023
+ // The exploded image layers are written once by the extractor and only read
1024
+ // from here on, so repeated walks of them can share one view of the tree.
1025
+ setDirWalkCacheRoot(options.allLayersExplodedDir);
1026
+ // The ecosystems every path is scanned for, in the order their builders run.
1027
+ // Each step reads the per-path copy of options that scanOnePathInner passes
1028
+ // in. One list merged by one loop also keeps type checking of this function
1029
+ // linear: a separate block per ecosystem, each reassigning an untyped
1030
+ // evolving array, makes TypeScript's control-flow analysis exponential in
1031
+ // the number of blocks (issue #4705).
1032
+ /**
1033
+ * @param {string} path Scanned path
1034
+ * @returns {boolean} Whether the path is a Chrome extension directory or its manifest.json
1035
+ */
1036
+ const isChromeExtensionPath = (path) => {
1037
+ if (!safeExistsSync(path)) {
1038
+ return false;
1622
1039
  }
1623
- if (hasAnyProjectType(["oci", "nix"], options)) {
1624
- setProjectTypeActivityContext("nix", path);
1625
- bomData = await withOwnParent(() => createNixBom(path, options));
1626
- if (bomData?.bomJson?.components?.length) {
1627
- if (DEBUG_MODE) {
1628
- console.log(
1629
- `Found ${bomData.bomJson.components.length} Nix flake packages at ${path}`,
1040
+ if (basename(path) === "manifest.json") {
1041
+ return true;
1042
+ }
1043
+ try {
1044
+ return (
1045
+ statSync(path).isDirectory() &&
1046
+ safeExistsSync(join(path, "manifest.json"))
1047
+ );
1048
+ } catch (_err) {
1049
+ return false;
1050
+ }
1051
+ };
1052
+ /**
1053
+ * @param {string} thought Thought log written unless the scan is of a container image
1054
+ * @returns {(path: string, scanOptions: Object) => void} The announce hook
1055
+ */
1056
+ const ociQuiet = (thought) => (_path, scanOptions) => {
1057
+ if (!hasAnyProjectType(["oci"], scanOptions, false)) {
1058
+ thoughtLog(thought);
1059
+ }
1060
+ };
1061
+ /** @type {PathScanStep[]} */
1062
+ const pathScanSteps = [
1063
+ {
1064
+ // Node.js and AI inventory
1065
+ applies: (path, scanOptions) =>
1066
+ hasAnyProjectType(["oci", "js"], scanOptions) ||
1067
+ hasAnyProjectType(["mcp", "ai-skill", "ai"], scanOptions) ||
1068
+ !!getDirectAiInventoryType(path, scanOptions),
1069
+ prepare: (path, scanOptions) =>
1070
+ getDirectAiInventoryType(path, scanOptions),
1071
+ announce: (_path, scanOptions, exactAiInventoryType) => {
1072
+ if (hasAnyProjectType(["oci"], scanOptions, false)) {
1073
+ return;
1074
+ }
1075
+ if (exactAiInventoryType === "mcp") {
1076
+ thoughtLog(
1077
+ "**MCP**: Looking for MCP services, MCP configs, and related AI control-plane artifacts.",
1078
+ );
1079
+ } else if (exactAiInventoryType === "ai") {
1080
+ thoughtLog(
1081
+ "**AI-BOM**: Looking for AI models, direct model metadata, pedigree, quantization, and operator-facing AI context.",
1082
+ );
1083
+ } else if (exactAiInventoryType === "ai-skill") {
1084
+ thoughtLog(
1085
+ "**AI-SKILL**: Looking for AI instruction, skill, and agent-definition files that can influence build or release flows.",
1086
+ );
1087
+ } else {
1088
+ thoughtLog(
1089
+ "**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
1630
1090
  );
1631
1091
  }
1632
- components = components.concat(bomData.bomJson.components);
1633
- dependencies = mergeDependencies(
1634
- dependencies,
1635
- bomData.bomJson.dependencies,
1636
- );
1092
+ },
1093
+ activityType: (exactAiInventoryType) => exactAiInventoryType || "js",
1094
+ build: createNodejsBom,
1095
+ foundThought: (count, _path, exactAiInventoryType) =>
1096
+ exactAiInventoryType
1097
+ ? `I found ${count} ${exactAiInventoryType} component(s). Let's keep looking.`
1098
+ : `I found ${count} npm packages. Let's keep looking.`,
1099
+ debugMessage: (count, path, exactAiInventoryType) =>
1100
+ `Found ${count} ${exactAiInventoryType || "npm"} components at ${path}`,
1101
+ mergeServices: true,
1102
+ hoistSubComponents: "plain",
1103
+ },
1104
+ {
1105
+ applies: (_path, scanOptions) =>
1106
+ hasAnyProjectType(["oci", "java"], scanOptions),
1107
+ announce: ociQuiet(
1108
+ "**JAVA**: Looking for Java projects (e.g., Maven, Gradle, SBT). I hope all configurations—from Java version to individual build settings—are correctly aligned.",
1109
+ ),
1110
+ activityType: "java",
1111
+ build: createJavaBom,
1112
+ foundThought: (count) => `I found ${count} java packages.`,
1113
+ debugMessage: (count, path) => `Found ${count} java packages at ${path}`,
1114
+ hoistSubComponents: "gradle",
1115
+ },
1116
+ {
1117
+ applies: (_path, scanOptions) =>
1118
+ hasAnyProjectType(["oci", "py"], scanOptions),
1119
+ announce: (path, scanOptions) => {
1120
+ ociQuiet(
1121
+ "**PYTHON**: Looking for Python projects with package managers such as pip, poetry, uv, etc. Wish me good luck!",
1122
+ )(path, scanOptions);
1637
1123
  if (
1638
- bomData.parentComponent &&
1639
- Object.keys(bomData.parentComponent).length
1124
+ readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true" &&
1125
+ pathList.length <= 2
1640
1126
  ) {
1641
- parentSubComponents.push(bomData.parentComponent);
1642
- }
1643
- }
1644
- }
1645
- if (hasAnyProjectType(["caxa"], options, false)) {
1646
- setProjectTypeActivityContext("caxa", path);
1647
- bomData = await withOwnParent(() => createCaxaBom(path, options));
1648
- if (bomData?.bomJson?.components?.length) {
1649
- if (DEBUG_MODE) {
1650
- console.log(
1651
- `Found ${bomData.bomJson.components.length} caxa packages at ${path}`,
1127
+ thoughtLog(
1128
+ "I'm running in a non-container environment. Let's hope the correct build tools are available ✌️.",
1652
1129
  );
1653
1130
  }
1654
- components = components.concat(bomData.bomJson.components);
1655
- dependencies = mergeDependencies(
1656
- dependencies,
1657
- bomData.bomJson.dependencies,
1658
- );
1659
- if (
1660
- bomData.parentComponent &&
1661
- Object.keys(bomData.parentComponent).length
1662
- ) {
1663
- parentSubComponents.push(bomData.parentComponent);
1131
+ },
1132
+ activityType: "py",
1133
+ build: createPythonBom,
1134
+ foundThought: (count) => `I found ${count} python packages.`,
1135
+ debugMessage: (count, path) =>
1136
+ `Found ${count} python packages at ${path}`,
1137
+ // uv workspace members are nested under the python project, so hoisting
1138
+ // them keeps them siblings of the other sub-components instead of riding
1139
+ // one level deeper (or being dropped by the same-name collapse) when
1140
+ // another ecosystem's project becomes the parent.
1141
+ hoistSubComponents: "plain",
1142
+ collectFormulation: true,
1143
+ },
1144
+ {
1145
+ applies: (_path, scanOptions) =>
1146
+ hasAnyProjectType(["oci", "go"], scanOptions),
1147
+ announce: ociQuiet(
1148
+ "**GO**: Looking for go projects. I need to be cautious about purl namespaces and potential failures with the 'go list' command.",
1149
+ ),
1150
+ activityType: "go",
1151
+ build: createGoBom,
1152
+ foundThought: (count) => `I found ${count} go packages.`,
1153
+ debugMessage: (count, path) => `Found ${count} go packages at ${path}`,
1154
+ },
1155
+ {
1156
+ applies: (_path, scanOptions) =>
1157
+ hasAnyProjectType(["oci", "rust"], scanOptions),
1158
+ announce: ociQuiet(
1159
+ "**RUST**: Let's search for Cargo/Rust projects. Should I warn the user that we don't support Cargo 'features' and native dependencies, which may lead to both false positives and false negatives? 🤔?",
1160
+ ),
1161
+ activityType: "rust",
1162
+ build: createRustBom,
1163
+ foundThought: (count) => `I found ${count} rust packages.`,
1164
+ debugMessage: (count, path) => `Found ${count} rust packages at ${path}`,
1165
+ hoistSubComponents: "plain",
1166
+ },
1167
+ {
1168
+ applies: (_path, scanOptions) =>
1169
+ hasAnyProjectType(["oci", "php"], scanOptions),
1170
+ announce: ociQuiet(
1171
+ "**PHP**: About to search for Composer-based projects. I hope lock files are available; otherwise, the 'composer install' command might fail for various reasons.",
1172
+ ),
1173
+ activityType: "php",
1174
+ build: createPHPBom,
1175
+ // A lock with no packages can still hold the project's platform data.
1176
+ collectMetadataProperties: true,
1177
+ foundThought: (count) => `I found ${count} php packages.`,
1178
+ debugMessage: (count, path) => `Found ${count} php packages at ${path}`,
1179
+ hoistSubComponents: "plain",
1180
+ },
1181
+ {
1182
+ applies: (_path, scanOptions) =>
1183
+ hasAnyProjectType(["oci", "ruby"], scanOptions),
1184
+ announce: ociQuiet(
1185
+ "**RUBY**: Are there any Ruby projects in this path? There's only one way to know.",
1186
+ ),
1187
+ activityType: "ruby",
1188
+ build: createRubyBom,
1189
+ foundThought: (count) => `We got ${count} ruby packages.`,
1190
+ debugMessage: (count, path) => `Found ${count} ruby packages at ${path}`,
1191
+ dependencyMerge: "mergeWithParent",
1192
+ hoistSubComponents: "plain",
1193
+ },
1194
+ {
1195
+ applies: (_path, scanOptions) =>
1196
+ hasAnyProjectType(["oci", "csharp"], scanOptions),
1197
+ announce: ociQuiet("**CSHARP**: What about csharp and fsharp projects?"),
1198
+ activityType: "csharp",
1199
+ build: createCsharpBom,
1200
+ // Every project the scan read has a dependency entry of its own, so a
1201
+ // project that uses no packages is kept along with its parent.
1202
+ isUseful: (bomData) =>
1203
+ !!(
1204
+ bomData?.bomJson?.components?.length ||
1205
+ bomData?.bomJson?.dependencies?.length
1206
+ ),
1207
+ foundThought: (count) => `There are ${count} csharp packages.`,
1208
+ debugMessage: (count, path) =>
1209
+ `Found ${count} csharp packages at ${path}`,
1210
+ hoistSubComponents: "plain",
1211
+ },
1212
+ {
1213
+ applies: (_path, scanOptions) =>
1214
+ hasAnyProjectType(["oci", "dart"], scanOptions),
1215
+ announce: ociQuiet(
1216
+ "**DART**: Looking for Dart projects. These are rare ones. Should I inform the user that they can pass the types argument via the command-line to speed things up?",
1217
+ ),
1218
+ activityType: "dart",
1219
+ build: createDartBom,
1220
+ foundThought: (count) => `I found ${count} pub packages.`,
1221
+ debugMessage: (count, path) => `Found ${count} pub packages at ${path}`,
1222
+ },
1223
+ {
1224
+ applies: (_path, scanOptions) =>
1225
+ hasAnyProjectType(["oci", "haskell"], scanOptions),
1226
+ announce: ociQuiet(
1227
+ "**HASKELL**: Looking for Haskell projects. They're rarely encountered.",
1228
+ ),
1229
+ activityType: "haskell",
1230
+ build: createHaskellBom,
1231
+ foundThought: (count) => `I found ${count} hackage packages.`,
1232
+ debugMessage: (count, path) =>
1233
+ `Found ${count} hackage packages at ${path}`,
1234
+ },
1235
+ {
1236
+ applies: (_path, scanOptions) =>
1237
+ hasAnyProjectType(["oci", "elixir"], scanOptions),
1238
+ announce: ociQuiet(
1239
+ "**ELIXIR**: Looking for Elixir projects—they're quite rare as well.",
1240
+ ),
1241
+ activityType: "elixir",
1242
+ build: createElixirBom,
1243
+ foundThought: (count) => `I found ${count} mix packages.`,
1244
+ debugMessage: (count, path) => `Found ${count} mix packages at ${path}`,
1245
+ },
1246
+ {
1247
+ applies: (_path, scanOptions) =>
1248
+ hasAnyProjectType(["oci", "c"], scanOptions),
1249
+ announce: ociQuiet(
1250
+ "**C/C++**: Looking for C/C++ projects. Should I warn the user that the generated SBOM might have low accuracy and contain errors?",
1251
+ ),
1252
+ activityType: "c",
1253
+ build: createCppBom,
1254
+ foundThought: (count) => `I found ${count} cpp packages.`,
1255
+ debugMessage: (count, path) => `Found ${count} cpp packages at ${path}`,
1256
+ // presets and compilers describe the build, whatever it depends on
1257
+ collectFormulation: true,
1258
+ },
1259
+ {
1260
+ applies: (_path, scanOptions) =>
1261
+ hasAnyProjectType(["oci", "clojure"], scanOptions),
1262
+ announce: ociQuiet(
1263
+ "**CLOJURE**: Looking for Clojure projects. Should I warn the user that the purl namespace 'clojars' isn't widely supported by tools like Dependency-Track?",
1264
+ ),
1265
+ activityType: "clojure",
1266
+ build: createClojureBom,
1267
+ foundThought: (count) => `I found ${count} clojure packages.`,
1268
+ debugMessage: (count, path) =>
1269
+ `Found ${count} clojure packages at ${path}`,
1270
+ },
1271
+ {
1272
+ applies: (_path, scanOptions) =>
1273
+ hasAnyProjectType(["oci", "github"], scanOptions),
1274
+ announce: ociQuiet(
1275
+ "**GITHUB**: Looking for any github packages and workflows.",
1276
+ ),
1277
+ activityType: "github",
1278
+ build: createGitHubBom,
1279
+ foundThought: (count) =>
1280
+ `I found ${count} github action packages as well. Should I convert these to formulation instead 🤔`,
1281
+ debugMessage: (count, path) =>
1282
+ `Found ${count} GitHub action packages at ${path}`,
1283
+ },
1284
+ {
1285
+ applies: (_path, scanOptions) =>
1286
+ hasAnyProjectType(["oci", "cloudbuild"], scanOptions),
1287
+ announce: ociQuiet(
1288
+ "**CLOUDBUILD**: Let's check for CloudBuild configuration files that include package dependencies.",
1289
+ ),
1290
+ activityType: "cloudbuild",
1291
+ build: createCloudBuildBom,
1292
+ foundThought: (count) => `I found ${count} cloudbuild packages.`,
1293
+ debugMessage: (count, path) =>
1294
+ `Found ${count} CloudBuild configuration at ${path}`,
1295
+ },
1296
+ {
1297
+ applies: (_path, scanOptions) =>
1298
+ hasAnyProjectType(["oci", "swift"], scanOptions),
1299
+ announce: ociQuiet(
1300
+ "**SWIFT**: Now checking for Swift projects. We don't support CocoaPods, Objective-C, or pure Xcode projects, so the SBOM will be incomplete.",
1301
+ ),
1302
+ activityType: "swift",
1303
+ build: createSwiftBom,
1304
+ foundThought: (count) => `I found ${count} swift packages here.`,
1305
+ debugMessage: (count, path) => `Found ${count} Swift packages at ${path}`,
1306
+ },
1307
+ {
1308
+ applies: (_path, scanOptions) =>
1309
+ hasAnyProjectType(["oci", "jar", "war", "ear"], scanOptions),
1310
+ announce: ociQuiet(
1311
+ "**JAR**: Let's check for any bundled jar/war/ear files to improve the SBOM accuracy.",
1312
+ ),
1313
+ activityType: "jar",
1314
+ beforeBuild: (path) => {
1315
+ recordDegradation("jvm.jar.heuristic", {
1316
+ ecosystem: "java",
1317
+ tool: "jar",
1318
+ impact: "versions",
1319
+ path,
1320
+ detail:
1321
+ "Components are being inferred by scanning jar/war/ear files from disk.",
1322
+ });
1323
+ },
1324
+ build: createJarBom,
1325
+ foundThought: (count) => `I found ${count} jar packages as well.`,
1326
+ debugMessage: (count, path) => `Found ${count} jar packages at ${path}`,
1327
+ },
1328
+ {
1329
+ applies: (_path, scanOptions) =>
1330
+ hasAnyProjectType(["oci", "cocoa"], scanOptions),
1331
+ activityType: "cocoa",
1332
+ build: createCocoaBom,
1333
+ debugMessage: (count, path) => `Found ${count} Cocoa packages at ${path}`,
1334
+ dependencyMerge: "concat",
1335
+ },
1336
+ {
1337
+ applies: (_path, scanOptions) =>
1338
+ hasAnyProjectType(["oci", "nix"], scanOptions),
1339
+ activityType: "nix",
1340
+ build: createNixBom,
1341
+ debugMessage: (count, path) =>
1342
+ `Found ${count} Nix flake packages at ${path}`,
1343
+ },
1344
+ {
1345
+ applies: (_path, scanOptions) =>
1346
+ hasAnyProjectType(["caxa"], scanOptions, false),
1347
+ activityType: "caxa",
1348
+ build: createCaxaBom,
1349
+ debugMessage: (count, path) => `Found ${count} caxa packages at ${path}`,
1350
+ },
1351
+ {
1352
+ applies: (_path, scanOptions) =>
1353
+ hasAnyProjectType(["asar"], scanOptions, false),
1354
+ activityType: "asar",
1355
+ build: createAsarBom,
1356
+ debugMessage: (count, path) =>
1357
+ `Found ${count} ASAR component(s) at ${path}`,
1358
+ },
1359
+ {
1360
+ applies: (_path, scanOptions) =>
1361
+ hasAnyProjectType(["vscode-extension"], scanOptions, false),
1362
+ activityType: "vscode-extension",
1363
+ build: createVscodeExtensionBom,
1364
+ debugMessage: (count, path) =>
1365
+ `Found ${count} VS Code extension(s) at ${path}`,
1366
+ },
1367
+ {
1368
+ // A path that is a Chrome extension is always scanned. Any other path
1369
+ // scans the extensions installed on this host, which happens once per
1370
+ // run however many paths there are.
1371
+ applies: (path, scanOptions) =>
1372
+ hasAnyProjectType(["chrome-extension"], scanOptions, false) &&
1373
+ (isChromeExtensionPath(path) ||
1374
+ !sharedOptions.__didScanChromeExtensions),
1375
+ beforeBuild: (path) => {
1376
+ if (!isChromeExtensionPath(path)) {
1377
+ sharedOptions.__didScanChromeExtensions = true;
1664
1378
  }
1379
+ },
1380
+ activityType: "chrome-extension",
1381
+ build: createChromeExtensionBom,
1382
+ debugMessage: (count) =>
1383
+ `Found ${count} Chrome extension(s) on this host`,
1384
+ },
1385
+ {
1386
+ applies: (_path, scanOptions) =>
1387
+ hasAnyProjectType(["terraform"], scanOptions),
1388
+ announce: () => {
1389
+ thoughtLog(
1390
+ "**TERRAFORM**: Looking for Terraform and OpenTofu modules and providers. The registry metadata fetch is opt-in, so I will rely on the lock file, the module manifest and the configuration files.",
1391
+ );
1392
+ },
1393
+ activityType: "terraform",
1394
+ build: createTerraformBom,
1395
+ foundThought: (count) =>
1396
+ `I found ${count} terraform components. Should I also resolve their licenses from the registry when FETCH_LICENSE is enabled?`,
1397
+ debugMessage: (count, path) =>
1398
+ `Found ${count} terraform components at ${path}`,
1399
+ },
1400
+ {
1401
+ // Collect any crypto keys
1402
+ applies: (_path, scanOptions) =>
1403
+ scanOptions.specVersion >= 1.6 &&
1404
+ !!scanOptions.includeCrypto &&
1405
+ isCycloneDxComponentTypeEnabled("cryptographic-asset", scanOptions),
1406
+ announce: ociQuiet(
1407
+ "**CBOM**: Wait, the user wants me to look for cryptographic assets. Let's check thoroughly.",
1408
+ ),
1409
+ activityType: "cbom",
1410
+ build: createCryptoCertsBom,
1411
+ foundThought: (count) => `I found ${count} crypto assets.`,
1412
+ debugMessage: (count, path) => `Found ${count} crypto assets at ${path}`,
1413
+ dependencyMerge: "topLevel",
1414
+ keepParent: false,
1415
+ },
1416
+ ];
1417
+ const scanOnePath = async (path) => {
1418
+ try {
1419
+ return await scanOnePathInner(path);
1420
+ } catch (err) {
1421
+ if (!isDeferredFailOnError(err)) {
1422
+ throw err;
1665
1423
  }
1424
+ // The failing path contributes nothing, and the remaining paths still
1425
+ // do: fail-on-error scopes the loss to the leg that failed.
1426
+ return {
1427
+ components: [],
1428
+ dependencies: [],
1429
+ options: { ...sharedOptions },
1430
+ parentSubComponents: [],
1431
+ metadataProperties: [],
1432
+ formulationList: [],
1433
+ };
1666
1434
  }
1667
- if (hasAnyProjectType(["asar"], options, false)) {
1668
- setProjectTypeActivityContext("asar", path);
1669
- bomData = await withOwnParent(() => createAsarBom(path, options));
1670
- if (bomData?.bomJson?.components?.length) {
1671
- if (DEBUG_MODE) {
1672
- console.log(
1673
- `Found ${bomData.bomJson.components.length} ASAR component(s) at ${path}`,
1674
- );
1675
- }
1676
- components = components.concat(bomData.bomJson.components);
1677
- dependencies = mergeDependencies(
1678
- dependencies,
1679
- bomData.bomJson.dependencies,
1680
- );
1681
- if (
1682
- bomData.parentComponent &&
1683
- Object.keys(bomData.parentComponent).length
1684
- ) {
1685
- parentSubComponents.push(bomData.parentComponent);
1686
- }
1435
+ };
1436
+ const scanOnePathInner = async (path) => {
1437
+ // Each path accumulates into its own lists and its own view of options, so
1438
+ // that legs which record a parent component or a tool cannot overwrite
1439
+ // another path's. The lists are merged below in pathList order.
1440
+ const options = { ...sharedOptions };
1441
+ /** @type {BuilderBomData | undefined} */
1442
+ let bomData;
1443
+ /** @type {Object[]} */
1444
+ let components = [];
1445
+ /** @type {Object[]} */
1446
+ let dependencies = [];
1447
+ /** @type {Object[]} */
1448
+ let parentSubComponents = [];
1449
+ /** @type {Object[]} */
1450
+ let metadataProperties = [];
1451
+ /** @type {Object[]} */
1452
+ let pathFormulationList = [];
1453
+ // Each builder detects the project of its own ecosystem. A builder that sets
1454
+ // options.parentComponent to force its project into its own BOM must not
1455
+ // make that project the next builder's too, or the next ecosystem's project
1456
+ // is lost: every builder starts from the parent the caller gave.
1457
+ const callerParent = options.parentComponent;
1458
+ const withOwnParent = async (build) => {
1459
+ options.parentComponent = callerParent;
1460
+ try {
1461
+ return await build();
1462
+ } finally {
1463
+ options.parentComponent = callerParent;
1687
1464
  }
1465
+ };
1466
+ setActivityContext({
1467
+ projectType: Array.isArray(options.projectType)
1468
+ ? options.projectType.join(",")
1469
+ : options.projectType,
1470
+ sourcePath: path,
1471
+ });
1472
+ recordActivity({
1473
+ kind: "read",
1474
+ reason:
1475
+ "Scanning project path for supported languages and package managers.",
1476
+ status: "completed",
1477
+ target: path,
1478
+ });
1479
+ if (DEBUG_MODE) {
1480
+ console.log("Scanning", path);
1688
1481
  }
1689
- if (hasAnyProjectType(["vscode-extension"], options, false)) {
1690
- setProjectTypeActivityContext("vscode-extension", path);
1691
- bomData = await withOwnParent(() =>
1692
- createVscodeExtensionBom(path, options),
1482
+ if (pathList.length > 2) {
1483
+ thoughtLog(`Let's thoroughly check the path ${path}.`);
1484
+ }
1485
+ for (const step of pathScanSteps) {
1486
+ if (!step.applies(path, options)) {
1487
+ continue;
1488
+ }
1489
+ const context = step.prepare?.(path, options);
1490
+ step.announce?.(path, options, context);
1491
+ setProjectTypeActivityContext(
1492
+ typeof step.activityType === "function"
1493
+ ? step.activityType(context)
1494
+ : step.activityType,
1495
+ path,
1693
1496
  );
1694
- if (bomData?.bomJson?.components?.length) {
1695
- if (DEBUG_MODE) {
1696
- console.log(
1697
- `Found ${bomData.bomJson.components.length} VS Code extension(s) at ${path}`,
1698
- );
1699
- }
1700
- components = components.concat(bomData.bomJson.components);
1701
- dependencies = mergeDependencies(
1702
- dependencies,
1703
- bomData.bomJson.dependencies,
1497
+ step.beforeBuild?.(path);
1498
+ const { build } = step;
1499
+ bomData = /** @type {BuilderBomData | undefined} */ (
1500
+ await withOwnParent(() => build(path, options))
1501
+ );
1502
+ if (
1503
+ step.collectMetadataProperties &&
1504
+ bomData?.metadataProperties?.length
1505
+ ) {
1506
+ metadataProperties = metadataProperties.concat(
1507
+ bomData.metadataProperties,
1704
1508
  );
1705
- if (
1706
- bomData.parentComponent &&
1707
- Object.keys(bomData.parentComponent).length
1708
- ) {
1709
- parentSubComponents.push(bomData.parentComponent);
1710
- }
1711
1509
  }
1712
- }
1713
- if (hasAnyProjectType(["chrome-extension"], options, false)) {
1714
- let isExplicitChromeExtensionPath = false;
1715
- if (safeExistsSync(path)) {
1716
- if (basename(path) === "manifest.json") {
1717
- isExplicitChromeExtensionPath = true;
1718
- } else {
1719
- try {
1720
- isExplicitChromeExtensionPath =
1721
- statSync(path).isDirectory() &&
1722
- safeExistsSync(join(path, "manifest.json"));
1723
- } catch (_err) {
1724
- isExplicitChromeExtensionPath = false;
1725
- }
1510
+ const isUseful = step.isUseful
1511
+ ? step.isUseful(bomData)
1512
+ : !!bomData?.bomJson?.components?.length;
1513
+ if (isUseful) {
1514
+ // a csharp result can be kept for its dependencies alone
1515
+ const count = bomData.bomJson.components?.length || 0;
1516
+ const thought = step.foundThought?.(count, path, context);
1517
+ if (thought) {
1518
+ thoughtLog(thought);
1726
1519
  }
1727
- }
1728
- if (
1729
- isExplicitChromeExtensionPath ||
1730
- !sharedOptions.__didScanChromeExtensions
1731
- ) {
1732
- if (!isExplicitChromeExtensionPath) {
1733
- sharedOptions.__didScanChromeExtensions = true;
1520
+ if (DEBUG_MODE) {
1521
+ console.log(step.debugMessage(count, path, context));
1734
1522
  }
1735
- setProjectTypeActivityContext("chrome-extension", path);
1736
- bomData = await withOwnParent(() =>
1737
- createChromeExtensionBom(path, options),
1738
- );
1739
- if (bomData?.bomJson?.components?.length) {
1740
- if (DEBUG_MODE) {
1741
- console.log(
1742
- `Found ${bomData.bomJson.components.length} Chrome extension(s) on this host`,
1523
+ components = components.concat(bomData.bomJson.components || []);
1524
+ if (step.dependencyMerge === "concat") {
1525
+ dependencies = dependencies.concat(
1526
+ bomData.bomJson.dependencies || [],
1527
+ );
1528
+ } else if (step.dependencyMerge === "topLevel") {
1529
+ // the crypto scan returns its dependencies beside bomJson
1530
+ if (bomData.dependencies?.length) {
1531
+ dependencies = mergeDependencies(
1532
+ dependencies,
1533
+ bomData.dependencies,
1743
1534
  );
1744
1535
  }
1745
- components = components.concat(bomData.bomJson.components);
1536
+ } else if (step.dependencyMerge === "mergeWithParent") {
1537
+ dependencies = mergeDependencies(
1538
+ dependencies,
1539
+ bomData.bomJson.dependencies,
1540
+ bomData.parentComponent,
1541
+ );
1542
+ } else {
1746
1543
  dependencies = mergeDependencies(
1747
1544
  dependencies,
1748
1545
  bomData.bomJson.dependencies,
1749
1546
  );
1750
- if (
1751
- bomData.parentComponent &&
1752
- Object.keys(bomData.parentComponent).length
1753
- ) {
1754
- parentSubComponents.push(bomData.parentComponent);
1755
- }
1756
1547
  }
1757
- }
1758
- }
1759
- if (hasAnyProjectType(["terraform"], options)) {
1760
- thoughtLog(
1761
- "**TERRAFORM**: Looking for Terraform and OpenTofu modules and providers. The registry metadata fetch is opt-in, so I will rely on the lock file, the module manifest and the configuration files.",
1762
- );
1763
- setProjectTypeActivityContext("terraform", path);
1764
- bomData = await withOwnParent(() => createTerraformBom(path, options));
1765
- if (bomData?.bomJson?.components?.length) {
1766
- thoughtLog(
1767
- `I found ${bomData.bomJson.components.length} terraform components. Should I also resolve their licenses from the registry when FETCH_LICENSE is enabled?`,
1768
- );
1769
- if (DEBUG_MODE) {
1770
- console.log(
1771
- `Found ${bomData.bomJson.components.length} terraform components at ${path}`,
1548
+ if (step.mergeServices && bomData?.bomJson?.services?.length) {
1549
+ options.services = mergeServices(
1550
+ options.services || [],
1551
+ bomData.bomJson.services,
1772
1552
  );
1773
1553
  }
1774
- components = components.concat(bomData.bomJson.components);
1775
- dependencies = mergeDependencies(
1776
- dependencies,
1777
- bomData.bomJson.dependencies,
1778
- );
1779
1554
  if (
1555
+ step.keepParent !== false &&
1780
1556
  bomData.parentComponent &&
1781
1557
  Object.keys(bomData.parentComponent).length
1782
1558
  ) {
1783
1559
  parentSubComponents.push(bomData.parentComponent);
1784
1560
  }
1561
+ // Retain metadata.component.components
1562
+ if (
1563
+ step.hoistSubComponents &&
1564
+ bomData.parentComponent?.components?.length
1565
+ ) {
1566
+ let bomSubComponents = bomData.parentComponent.components;
1567
+ // For java, sub-components that are already components are added
1568
+ // to the list of current components and removed from
1569
+ // metadata.component.components; the components are merged later.
1570
+ if (
1571
+ step.hoistSubComponents === "gradle" &&
1572
+ !["false", "0"].includes(
1573
+ readEnvironmentVariable("GRADLE_RESOLVE_FROM_NODE"),
1574
+ )
1575
+ ) {
1576
+ thoughtLog(
1577
+ "Wait, the user wants me to resolve gradle projects from npm.",
1578
+ );
1579
+ const allRefs = components.map((c) => c["bom-ref"]);
1580
+ const duplicateComponents = bomSubComponents.filter((c) =>
1581
+ allRefs.includes(c["bom-ref"]),
1582
+ );
1583
+ components = components.concat(duplicateComponents);
1584
+ const duplicateComponentRefs = duplicateComponents.map(
1585
+ (c) => c["bom-ref"],
1586
+ );
1587
+ bomSubComponents = bomSubComponents.filter(
1588
+ (c) => !duplicateComponentRefs.includes(c["bom-ref"]),
1589
+ );
1590
+ }
1591
+ parentSubComponents = parentSubComponents.concat(bomSubComponents);
1592
+ delete bomData.parentComponent.components;
1593
+ }
1785
1594
  }
1786
- }
1787
- // Collect any crypto keys
1788
- if (
1789
- options.specVersion >= 1.6 &&
1790
- options.includeCrypto &&
1791
- isCycloneDxComponentTypeEnabled("cryptographic-asset", options)
1792
- ) {
1793
- if (!hasAnyProjectType(["oci"], options, false)) {
1794
- thoughtLog(
1795
- "**CBOM**: Wait, the user wants me to look for cryptographic assets. Let's check thoroughly.",
1796
- );
1797
- }
1798
- setProjectTypeActivityContext("cbom", path);
1799
- bomData = await withOwnParent(() => createCryptoCertsBom(path, options));
1800
- if (bomData?.bomJson?.components?.length) {
1801
- thoughtLog(
1802
- `I found ${bomData.bomJson.components.length} crypto assets.`,
1595
+ if (step.collectFormulation && bomData?.formulationList?.length) {
1596
+ pathFormulationList = pathFormulationList.concat(
1597
+ bomData.formulationList,
1803
1598
  );
1804
- if (DEBUG_MODE) {
1805
- console.log(
1806
- `Found ${bomData.bomJson.components.length} crypto assets at ${path}`,
1807
- );
1808
- }
1809
- components = components.concat(bomData.bomJson.components);
1810
- if (bomData.dependencies?.length) {
1811
- dependencies = mergeDependencies(dependencies, bomData.dependencies);
1812
- }
1813
1599
  }
1814
1600
  }
1815
1601
  return {
@@ -1818,6 +1604,7 @@ export async function createMultiXBom(pathList, options) {
1818
1604
  options,
1819
1605
  parentSubComponents,
1820
1606
  metadataProperties,
1607
+ formulationList: pathFormulationList,
1821
1608
  };
1822
1609
  };
1823
1610
  const pathResults = [];
@@ -1884,6 +1671,7 @@ export async function createMultiXBom(pathList, options) {
1884
1671
  metadataProperties = metadataProperties.concat(
1885
1672
  pathResult.metadataProperties,
1886
1673
  );
1674
+ formulationList = formulationList.concat(pathResult.formulationList);
1887
1675
  if (pathResult.options.services?.length) {
1888
1676
  sharedOptions.services = mergeServices(
1889
1677
  sharedOptions.services || [],
@@ -1975,6 +1763,26 @@ export async function createMultiXBom(pathList, options) {
1975
1763
  dependencies,
1976
1764
  metadataProperties,
1977
1765
  );
1766
+ // Language packages an OS package installed are judged by that package.
1767
+ // Duplicates have merged by now, so a component found both where the distro
1768
+ // put it and where pip did is seen whole and left unmarked.
1769
+ markOsInstalledLanguagePackages(
1770
+ multiResult.bomJson?.components,
1771
+ osFileOwners,
1772
+ options.allLayersExplodedDir,
1773
+ );
1774
+ // The default gems of an unpackaged Ruby, such as resolv in a Docker
1775
+ // Official image, ship with that Ruby and are updated by its releases.
1776
+ const defaultGemEdges = linkDefaultGemsToRuntimes(
1777
+ multiResult.bomJson?.components,
1778
+ options.allLayersExplodedDir,
1779
+ );
1780
+ if (defaultGemEdges.length && multiResult.bomJson) {
1781
+ multiResult.bomJson.dependencies = mergeDependencies(
1782
+ multiResult.bomJson.dependencies || [],
1783
+ defaultGemEdges,
1784
+ );
1785
+ }
1978
1786
  if (formulationList.length) {
1979
1787
  multiResult.formulationList = formulationList;
1980
1788
  }
@@ -3051,6 +2859,16 @@ async function createBomForPath(path, options) {
3051
2859
  * @throws {Error} if the request fails
3052
2860
  */
3053
2861
  export async function submitBom(args, bomContents) {
2862
+ // Dependency-Track ingests CycloneDX 1.x only. The CLI and the server refuse
2863
+ // a 2.0 upload before generating; this covers library callers.
2864
+ if (
2865
+ isCycloneDx20SpecVersion(bomContents?.specVersion) ||
2866
+ bomContents?.specFormat !== undefined
2867
+ ) {
2868
+ console.log(DEPENDENCY_TRACK_CDX20_ERROR);
2869
+ args.failOnError && process.exit(1);
2870
+ return undefined;
2871
+ }
3054
2872
  const dependencyTrackApiUrl = getDependencyTrackBomApiUrl(args.serverUrl);
3055
2873
  const serverUrl = dependencyTrackApiUrl?.toString();
3056
2874
  if (!dependencyTrackApiUrl || !serverUrl) {