@cdxgen/cdxgen 13.6.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 (264) hide show
  1. package/README.md +15 -8
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +256 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +25 -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 +18 -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/pqc-migration.json +239 -0
  15. package/data/remediations.json +52 -0
  16. package/data/rules/cbom-security.yaml +4 -3
  17. package/data/rules/package-integrity.yaml +28 -0
  18. package/lib/audit/index.js +25 -3
  19. package/lib/cli/bomAssembly.js +80 -1
  20. package/lib/cli/cliOptions.js +121 -17
  21. package/lib/cli/index.js +609 -898
  22. package/lib/cli/jsBom.js +3 -0
  23. package/lib/cli/jvmBom.js +365 -40
  24. package/lib/cli/managedBom.js +19 -3
  25. package/lib/cli/nativeBom.js +10 -0
  26. package/lib/core/env.js +26 -0
  27. package/lib/core/fs.js +126 -3
  28. package/lib/core/globPatterns.js +218 -0
  29. package/lib/core/processTree.js +99 -12
  30. package/lib/core/projectConfig.js +332 -25
  31. package/lib/core/toolRegistry.js +15 -1
  32. package/lib/ecosystems/denoutils.js +2 -2
  33. package/lib/ecosystems/golem.js +3 -8
  34. package/lib/ecosystems/gradleutils.js +1082 -6
  35. package/lib/ecosystems/jsEvidence.js +90 -38
  36. package/lib/ecosystems/kosi.js +3 -5
  37. package/lib/ecosystems/npmVersionResolution.js +376 -0
  38. package/lib/ecosystems/parsers-python.js +89 -28
  39. package/lib/ecosystems/parsers-rust.js +117 -15
  40. package/lib/ecosystems/remote/huggingface.js +22 -0
  41. package/lib/ecosystems/rusi.js +2 -4
  42. package/lib/ecosystems/scalasem.js +2 -1
  43. package/lib/evinser/evinser.js +196 -34
  44. package/lib/evinser/scalasem.js +5 -1
  45. package/lib/helpers/agenticToolutils.js +112 -1
  46. package/lib/helpers/bomSigner.js +43 -9
  47. package/lib/helpers/jssSigner.js +1276 -0
  48. package/lib/inventory/agentBlueprint.js +193 -0
  49. package/lib/inventory/agentToolNames.js +145 -0
  50. package/lib/inventory/aiCollector.js +153 -7
  51. package/lib/inventory/aiInventory.js +44 -8
  52. package/lib/inventory/aiProviders.js +324 -0
  53. package/lib/inventory/bomUtils.js +8 -1
  54. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  55. package/lib/inventory/citations.js +31 -2
  56. package/lib/inventory/communityAiConfigParser.js +978 -80
  57. package/lib/inventory/cyclonedx20.js +152 -0
  58. package/lib/inventory/cyclonedx20Common.js +286 -0
  59. package/lib/inventory/cyclonedx20Derived.js +159 -0
  60. package/lib/inventory/cyclonedx20Parties.js +552 -0
  61. package/lib/inventory/cyclonedx20Read.js +768 -0
  62. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  63. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  64. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  65. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  66. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  67. package/lib/inventory/cyclonedx20Render.js +766 -0
  68. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  69. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  70. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  71. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  72. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  73. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  74. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  75. package/lib/inventory/cyclonedx20Tables.js +594 -0
  76. package/lib/inventory/deps.js +106 -13
  77. package/lib/inventory/dosai.js +236 -1
  78. package/lib/inventory/dosaiParsers.js +3 -6
  79. package/lib/inventory/dotnetRuntime.js +164 -14
  80. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  81. package/lib/inventory/envcontext.js +132 -38
  82. package/lib/inventory/evidenceUtils.js +2 -1
  83. package/lib/inventory/formulationParsers.js +95 -13
  84. package/lib/inventory/hbom.js +45 -6
  85. package/lib/inventory/hbomLoader.js +166 -0
  86. package/lib/inventory/mcpConfigParser.js +5 -0
  87. package/lib/inventory/occurrenceKey.js +90 -0
  88. package/lib/inventory/pqcMigration.js +426 -0
  89. package/lib/inventory/rustFormulationParser.js +21 -10
  90. package/lib/inventory/source.js +65 -6
  91. package/lib/parsers/huggingfaceManifest.js +9 -1
  92. package/lib/parsers/huggingfaceUtils.js +18 -0
  93. package/lib/parsers/kiroPermissions.js +166 -0
  94. package/lib/server/server.js +36 -34
  95. package/lib/stages/postgen/postgen.js +41 -1
  96. package/lib/stages/postgen/specVersionCompat.js +76 -0
  97. package/lib/validator/bomValidator.js +8 -1
  98. package/lib/validator/complianceEngine.js +30 -3
  99. package/lib/validator/complianceRules.js +1416 -4
  100. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  101. package/lib/validator/index.js +48 -7
  102. package/lib/validator/reporters/json.js +6 -0
  103. package/package.json +9 -9
  104. package/types/bin/repl.d.ts.map +1 -1
  105. package/types/lib/audit/index.d.ts +6 -2
  106. package/types/lib/audit/index.d.ts.map +1 -1
  107. package/types/lib/cli/bomAssembly.d.ts +16 -0
  108. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  109. package/types/lib/cli/cliOptions.d.ts +6 -0
  110. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  111. package/types/lib/cli/index.d.ts.map +1 -1
  112. package/types/lib/cli/jsBom.d.ts +2 -1
  113. package/types/lib/cli/jsBom.d.ts.map +1 -1
  114. package/types/lib/cli/jvmBom.d.ts +15 -0
  115. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  116. package/types/lib/cli/managedBom.d.ts.map +1 -1
  117. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  118. package/types/lib/core/boundedFile.d.ts +30 -0
  119. package/types/lib/core/boundedFile.d.ts.map +1 -1
  120. package/types/lib/core/env.d.ts +10 -0
  121. package/types/lib/core/env.d.ts.map +1 -1
  122. package/types/lib/core/fs.d.ts +4 -0
  123. package/types/lib/core/fs.d.ts.map +1 -1
  124. package/types/lib/core/globPatterns.d.ts +70 -0
  125. package/types/lib/core/globPatterns.d.ts.map +1 -0
  126. package/types/lib/core/paths.d.ts +9 -0
  127. package/types/lib/core/paths.d.ts.map +1 -1
  128. package/types/lib/core/processTree.d.ts +16 -5
  129. package/types/lib/core/processTree.d.ts.map +1 -1
  130. package/types/lib/core/projectConfig.d.ts +47 -8
  131. package/types/lib/core/projectConfig.d.ts.map +1 -1
  132. package/types/lib/core/toolRegistry.d.ts.map +1 -1
  133. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  134. package/types/lib/ecosystems/gradleutils.d.ts +248 -8
  135. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  136. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  137. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  139. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  140. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  141. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  142. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  143. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  144. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  145. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  146. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  147. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  148. package/types/lib/evinser/evinser.d.ts +61 -9
  149. package/types/lib/evinser/evinser.d.ts.map +1 -1
  150. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  151. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  152. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  153. package/types/lib/helpers/bomSigner.d.ts +20 -3
  154. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  155. package/types/lib/helpers/jssSigner.d.ts +288 -0
  156. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  157. package/types/lib/helpers/vsixutils.d.ts +303 -19
  158. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  159. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  160. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  161. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  162. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  163. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  164. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  165. package/types/lib/inventory/aiProviders.d.ts +85 -0
  166. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  167. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  168. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  169. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  170. package/types/lib/inventory/citations.d.ts +5 -0
  171. package/types/lib/inventory/citations.d.ts.map +1 -1
  172. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  173. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  174. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  175. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  176. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  177. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  178. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  179. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  180. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  181. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  182. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  183. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  184. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  185. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  186. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  187. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  188. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  189. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  190. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  191. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  192. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  193. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  194. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  195. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  196. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  197. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  198. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  199. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  200. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  201. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  202. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  203. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  204. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  205. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  206. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  207. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  208. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  209. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  210. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  211. package/types/lib/inventory/deps.d.ts +35 -2
  212. package/types/lib/inventory/deps.d.ts.map +1 -1
  213. package/types/lib/inventory/dosai.d.ts +19 -0
  214. package/types/lib/inventory/dosai.d.ts.map +1 -1
  215. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  216. package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
  217. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
  218. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  219. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  220. package/types/lib/inventory/envcontext.d.ts +22 -6
  221. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  222. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  224. package/types/lib/inventory/hbom.d.ts +11 -0
  225. package/types/lib/inventory/hbom.d.ts.map +1 -1
  226. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  227. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  228. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  229. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  230. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  231. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  232. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  233. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  234. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  235. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  236. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  237. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  238. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  239. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  240. package/types/lib/inventory/source.d.ts +15 -0
  241. package/types/lib/inventory/source.d.ts.map +1 -1
  242. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  243. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  244. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  245. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  246. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  247. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  248. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  249. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  250. package/types/lib/server/server.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  252. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  253. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  254. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  255. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  256. package/types/lib/validator/bomValidator.d.ts +5 -1
  257. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  258. package/types/lib/validator/complianceEngine.d.ts +4 -4
  259. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  260. package/types/lib/validator/complianceRules.d.ts +14 -2
  261. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  262. package/types/lib/validator/index.d.ts +10 -0
  263. package/types/lib/validator/index.d.ts.map +1 -1
  264. package/types/lib/validator/reporters/json.d.ts.map +1 -1
package/lib/cli/jsBom.js CHANGED
@@ -1073,6 +1073,7 @@ export async function createNodejsBom(path, options) {
1073
1073
  allImports,
1074
1074
  allExports,
1075
1075
  options.deep,
1076
+ { srcDir: path },
1076
1077
  );
1077
1078
  return buildBomNSData(options, pkgList, "npm", {
1078
1079
  allImports,
@@ -1106,6 +1107,7 @@ export async function createNodejsBom(path, options) {
1106
1107
  allImports,
1107
1108
  allExports,
1108
1109
  options.deep,
1110
+ { srcDir: path, dependencies },
1109
1111
  );
1110
1112
  pkgList = propagateRequiredScopeFromDependencies(pkgList, dependencies);
1111
1113
  pkgList = markNpmTypesPackagesAsExcluded(pkgList);
@@ -1551,6 +1553,7 @@ export async function createNodejsBom(path, options) {
1551
1553
  allImports,
1552
1554
  allExports,
1553
1555
  options.deep,
1556
+ { srcDir: path, dependencies, parentComponent },
1554
1557
  );
1555
1558
  if (mcpInventory.components?.length) {
1556
1559
  pkgList = trimComponents(pkgList.concat(mcpInventory.components));
package/lib/cli/jvmBom.js CHANGED
@@ -41,11 +41,14 @@ import {
41
41
  } from "../core/projectTooling.js";
42
42
  import { extractJarArchive, getMvnMetadata } from "../ecosystems/ecosystems.js";
43
43
  import {
44
+ applyGradleVerificationMetadata,
44
45
  buildGradleCommandArguments,
45
46
  buildObjectForGradleModule,
46
47
  collectGradleDependencies,
47
48
  excludeGradleBuildTooling,
48
49
  executeParallelGradleProperties,
50
+ findGradleLockfiles,
51
+ findGradleVerificationMetadata,
49
52
  findMillDaemons,
50
53
  getGradleCommand,
51
54
  getMavenCommand,
@@ -53,14 +56,18 @@ import {
53
56
  getMillVersion,
54
57
  gradleArgsFromEnv,
55
58
  gradleDependencyTasks,
59
+ gradleVerificationMainArtifact,
56
60
  hasGradleInvocationFailure,
57
61
  isProjectToolingAllowed,
58
62
  loadGradleModulesForReports,
59
63
  parseGradleDep,
60
64
  parseGradleInfoLogsForUrls,
65
+ parseGradleLockfiles,
61
66
  parseGradleProperties,
62
67
  parseGradleResolvedDistributions,
68
+ parseGradleVerificationMetadata,
63
69
  parseGradleVersionCatalog,
70
+ readGradleRootProjectName,
64
71
  reconcileGradleComponents,
65
72
  recordGradleInvocationFailure,
66
73
  splitOutputByGradleProjects,
@@ -645,6 +652,33 @@ export async function createJavaBom(path, options) {
645
652
  let jarNSMapping = {};
646
653
  let pkgList = [];
647
654
  let dependencies = [];
655
+ // The committed gradle/verification-metadata.xml of this build, if any,
656
+ // and the metadata parsed from it exactly once. These live at function
657
+ // scope because the fallback runs inside the Gradle block below while the
658
+ // enrichment runs after getMvnMetadata, outside the try that guards the
659
+ // per-tool sections, so a deferred failure keeps the enrichment too.
660
+ let gradleVerificationFile;
661
+ let gradleVerificationRootDir;
662
+ let gradleVerificationData;
663
+ let gradleVerificationParsed = false;
664
+ const gradleVerification = () => {
665
+ if (!gradleVerificationParsed) {
666
+ gradleVerificationParsed = true;
667
+ if (gradleVerificationFile) {
668
+ gradleVerificationData = parseGradleVerificationMetadata(
669
+ gradleVerificationFile,
670
+ { rootDir: gradleVerificationRootDir },
671
+ );
672
+ if (gradleVerificationData) {
673
+ const { configuration } = gradleVerificationData;
674
+ thoughtLog(
675
+ `${gradleVerificationFile} records ${configuration.trustedKeyCount} trusted key(s), ${configuration.trustedArtifactRuleCount} trust rule(s), ${configuration.ignoredKeyCount} ignored key(s) and ${configuration.keyServerCount} key server(s).`,
676
+ );
677
+ }
678
+ }
679
+ }
680
+ return gradleVerificationData;
681
+ };
648
682
  // cyclone-dx-maven plugin creates a component for the app under metadata
649
683
  // This is subsequently referred to in the dependencies list
650
684
  let parentComponent = {};
@@ -1436,15 +1470,51 @@ export async function createJavaBom(path, options) {
1436
1470
  "Hmm, there is a gradle.properties file. Do we need any private modules or custom JVM arguments for this project 🤔?",
1437
1471
  );
1438
1472
  }
1439
- // Execute gradle properties
1440
- if (
1441
- gradleFiles?.length &&
1442
- isPackageManagerAllowed(
1443
- "gradle",
1444
- ["maven", "bazel", "sbt", "mill"],
1445
- options,
1446
- )
1447
- ) {
1473
+ // The package-manager gate the Gradle invocations below share: a scan
1474
+ // that prefers another JVM build tool over Gradle reads no Gradle
1475
+ // lockfiles either.
1476
+ const gradleManagerAllowed = isPackageManagerAllowed(
1477
+ "gradle",
1478
+ ["maven", "bazel", "sbt", "mill"],
1479
+ options,
1480
+ );
1481
+ // The committed Gradle dependency lockfiles record the resolved versions
1482
+ // of every locked configuration, so they are an inventory source that
1483
+ // needs no Gradle run. They are looked for only in a Gradle build: a
1484
+ // Maven-rooted scan does not pick up a nested sample's lockfile.
1485
+ const gradleLockFiles =
1486
+ gradleFiles?.length && gradleManagerAllowed
1487
+ ? findGradleLockfiles(gradleRootPath, options)
1488
+ : [];
1489
+ // The committed gradle/verification-metadata.xml records the checksums
1490
+ // and PGP key ids Gradle verified when `gradle
1491
+ // --write-verification-metadata` last ran. Like the lockfiles, it is an
1492
+ // inventory source that needs no Gradle run, and it is looked for only in
1493
+ // a Gradle build.
1494
+ gradleVerificationFile =
1495
+ gradleFiles?.length && gradleManagerAllowed
1496
+ ? findGradleVerificationMetadata(gradleRootPath)
1497
+ : undefined;
1498
+ gradleVerificationRootDir = gradleRootPath;
1499
+ // `gradle properties` evaluates the project's build scripts and `gradle
1500
+ // dependencies` resolves the graph, so both belong to the
1501
+ // dependency-tooling surface. The properties task runs whenever
1502
+ // `isProjectToolingAllowed` lets it, as it names the root project and
1503
+ // the subprojects; only the gradle-prefer-lockfile preference holds it
1504
+ // back. The dependencies task also needs `options.installDeps`. When
1505
+ // either check would stop the dependencies task, or when the preference
1506
+ // asks for it, the lockfiles supply the components instead.
1507
+ const gradleToolingAllowed = isProjectToolingAllowed(options);
1508
+ const gradlePreferLockfile = isFeatureEnabled(
1509
+ options,
1510
+ "gradle-prefer-lockfile",
1511
+ );
1512
+ const gradleSkipProperties =
1513
+ gradlePreferLockfile && gradleLockFiles.length > 0;
1514
+ const gradleUseLockfiles =
1515
+ gradleLockFiles.length > 0 &&
1516
+ (gradlePreferLockfile || !gradleToolingAllowed || !options.installDeps);
1517
+ if (gradleFiles?.length && gradleManagerAllowed) {
1448
1518
  const wrapperInfo = readGradleWrapperVersion(gradleRootPath);
1449
1519
  if (wrapperInfo) {
1450
1520
  try {
@@ -1506,6 +1576,177 @@ export async function createJavaBom(path, options) {
1506
1576
  // ignore
1507
1577
  }
1508
1578
  }
1579
+ }
1580
+ // A module of this build that another one consumes by its coordinates is
1581
+ // a project, not a dependency, so the fallback sources below drop it.
1582
+ const dropGradleModuleComponents = (pkgs) => {
1583
+ const moduleRefs = new Set(
1584
+ [parentComponent, ...(parentComponent?.components || [])]
1585
+ .map((c) => c?.["bom-ref"])
1586
+ .filter(Boolean),
1587
+ );
1588
+ return pkgs.filter((pkg) => !moduleRefs.has(pkg["bom-ref"]));
1589
+ };
1590
+ // Without `gradle properties` nothing named the root project. The
1591
+ // settings script names it, and the directory name stands in when it
1592
+ // does not, so the BOM still has a primary component.
1593
+ const ensureGradleParentComponent = () => {
1594
+ if (!parentComponent || !Object.keys(parentComponent).length) {
1595
+ const rootProjectName = readGradleRootProjectName(gradleRootPath);
1596
+ parentComponent = createDefaultParentComponent(
1597
+ gradleRootPath,
1598
+ "maven",
1599
+ rootProjectName && !("project-name" in options)
1600
+ ? {
1601
+ ...options,
1602
+ "project-name": rootProjectName,
1603
+ projectName: rootProjectName,
1604
+ }
1605
+ : options,
1606
+ );
1607
+ }
1608
+ };
1609
+ // The lockfiles stand in for the Gradle dependency resolution this scan
1610
+ // is not making, prefers not to make, or that produced nothing. They
1611
+ // record resolved versions but no graph, so the components carry no
1612
+ // dependency edges and the scan records what that costs. Returns the
1613
+ // number of components added.
1614
+ const addGradleLockfileComponents = () => {
1615
+ const lockfilePkgs = dropGradleModuleComponents(
1616
+ parseGradleLockfiles(gradleLockFiles, {
1617
+ rootDir: gradleRootPath,
1618
+ }),
1619
+ );
1620
+ if (!lockfilePkgs.length) {
1621
+ return 0;
1622
+ }
1623
+ pkgList = pkgList.concat(lockfilePkgs);
1624
+ recordDegradation("jvm.gradle.lockfile-only", {
1625
+ ecosystem: "java",
1626
+ tool: "gradle",
1627
+ impact: "transitive-deps",
1628
+ detail:
1629
+ "The components came from Gradle dependency lockfiles, which record no dependency graph between them.",
1630
+ });
1631
+ ensureGradleParentComponent();
1632
+ return lockfilePkgs.length;
1633
+ };
1634
+ // The version catalog and, on request, the verification file declare
1635
+ // coordinates without resolving them, so both are used only when Gradle
1636
+ // and the lockfiles produced no dependency information at all. The call
1637
+ // inside the Gradle run block happens before its failure paths, so a
1638
+ // build whose static sources resolved it is not degraded twice and not
1639
+ // failed with --fail-on-error over a BOM that has components. The
1640
+ // verification fallback stays off unless the
1641
+ // gradle-verification-fallback feature flag asks for it: the file
1642
+ // records what the build resolved when --write-verification-metadata
1643
+ // ran, which may be stale, and its components can only be optional.
1644
+ // Both belong to a Gradle build, so a scan without Gradle build files,
1645
+ // or one that prefers another JVM build tool, reads neither.
1646
+ const addGradleStaticFallbackComponents = () => {
1647
+ if (!gradleFiles?.length || !gradleManagerAllowed || pkgList.length) {
1648
+ return;
1649
+ }
1650
+ const catalogFiles = getAllFiles(
1651
+ path,
1652
+ `${options.multiProject ? "**/" : ""}gradle/libs.versions.toml`,
1653
+ options,
1654
+ );
1655
+ for (const catalogFile of catalogFiles) {
1656
+ if (DEBUG_MODE) {
1657
+ console.log(`Parsing ${catalogFile}`);
1658
+ }
1659
+ const dlist = parseGradleVersionCatalog(catalogFile);
1660
+ if (dlist?.length) {
1661
+ pkgList = pkgList.concat(dlist);
1662
+ }
1663
+ }
1664
+ if (
1665
+ pkgList.length ||
1666
+ !gradleVerificationFile ||
1667
+ !isFeatureEnabled(options, "gradle-verification-fallback")
1668
+ ) {
1669
+ return;
1670
+ }
1671
+ const verificationMetadata = gradleVerification();
1672
+ if (!verificationMetadata) {
1673
+ return;
1674
+ }
1675
+ const verificationPkgs = [];
1676
+ for (const component of verificationMetadata.components.values()) {
1677
+ // A Gradle plugin marker coordinate publishes only a POM that
1678
+ // points at the real plugin, so it is not a dependency of the
1679
+ // build, and a coordinate without a main artifact is a parent or a
1680
+ // BOM rather than a component of this BOM.
1681
+ if (component.name.endsWith(".gradle.plugin")) {
1682
+ continue;
1683
+ }
1684
+ if (!gradleVerificationMainArtifact(component, false)) {
1685
+ continue;
1686
+ }
1687
+ const purl = mavenPurl(
1688
+ component.group,
1689
+ component.name,
1690
+ component.version,
1691
+ {
1692
+ type: "jar",
1693
+ },
1694
+ );
1695
+ if (!purl) {
1696
+ continue;
1697
+ }
1698
+ verificationPkgs.push({
1699
+ group: component.group,
1700
+ name: component.name,
1701
+ version: component.version,
1702
+ qualifiers: { type: "jar" },
1703
+ type: "library",
1704
+ // A component with no scope counts as required, and the file
1705
+ // cannot tell a runtime dependency from build tooling, so the
1706
+ // components are optional.
1707
+ scope: "optional",
1708
+ purl,
1709
+ "bom-ref": decodeURIComponent(purl),
1710
+ // The digests are merged in at the end of the scan, from the file
1711
+ // or from the artifact itself when the local cache holds it.
1712
+ properties: [
1713
+ { name: "internal:SrcFile", value: gradleVerificationFile },
1714
+ { name: "cdx:gradle:verificationMetadata", value: "true" },
1715
+ ],
1716
+ evidence: {
1717
+ identity: {
1718
+ field: "purl",
1719
+ confidence: 1,
1720
+ concludedValue: purl,
1721
+ methods: [
1722
+ {
1723
+ technique: "manifest-analysis",
1724
+ confidence: 1,
1725
+ value: gradleVerificationFile,
1726
+ },
1727
+ ],
1728
+ },
1729
+ },
1730
+ });
1731
+ }
1732
+ const addedPkgs = dropGradleModuleComponents(verificationPkgs);
1733
+ if (addedPkgs.length) {
1734
+ pkgList = pkgList.concat(addedPkgs);
1735
+ recordDegradation("jvm.gradle.verification-metadata-only", {
1736
+ ecosystem: "java",
1737
+ tool: "gradle",
1738
+ impact: "transitive-deps",
1739
+ detail:
1740
+ "The components came from gradle/verification-metadata.xml, which records no dependency graph between them.",
1741
+ });
1742
+ ensureGradleParentComponent();
1743
+ thoughtLog(
1744
+ `Obtained ${addedPkgs.length} components from ${gradleVerificationFile}. It says nothing about which module depends on which, and nothing about scopes.`,
1745
+ );
1746
+ }
1747
+ };
1748
+ // Execute gradle properties
1749
+ if (gradleFiles?.length && gradleManagerAllowed && !gradleSkipProperties) {
1509
1750
  let includedBuilds = [];
1510
1751
  let allProjectsStr = [];
1511
1752
  if (readEnvironmentVariable("GRADLE_INCLUDED_BUILDS")) {
@@ -1529,10 +1770,6 @@ export async function createJavaBom(path, options) {
1529
1770
  if (resolveGradleDistribution && gradleInitArgs.length) {
1530
1771
  gradleInitArgs.push("-PcdxgenResolveDistribution=true");
1531
1772
  }
1532
- // `gradle properties` evaluates the project's build scripts, so it is
1533
- // part of the dependency-tooling surface gated by --no-install-deps
1534
- // (and the pre-build lifecycle).
1535
- const gradleToolingAllowed = isProjectToolingAllowed(options);
1536
1773
  if (gradleToolingAllowed) {
1537
1774
  parallelPropTaskOut = executeParallelGradleProperties(
1538
1775
  gradleRootPath,
@@ -1712,14 +1949,39 @@ export async function createJavaBom(path, options) {
1712
1949
  });
1713
1950
  }
1714
1951
  }
1952
+ if (gradleUseLockfiles) {
1953
+ const lockfileCount = addGradleLockfileComponents();
1954
+ if (lockfileCount) {
1955
+ console.log(
1956
+ `Using the dependencies locked by ${gradleLockFiles.length} Gradle lockfile(s) instead of executing 'gradle dependencies'.`,
1957
+ );
1958
+ thoughtLog(
1959
+ `Obtained ${lockfileCount} components from ${gradleLockFiles.length} Gradle lockfile(s). A lockfile is flat, so it says nothing about which module depends on which.`,
1960
+ );
1961
+ // --deep reads the jar namespaces of the versions the local Gradle
1962
+ // cache holds, as it does after a Gradle run; a cold cache yields
1963
+ // nothing.
1964
+ if (options.resolveClass || options.deep) {
1965
+ const tmpjarNSMapping = await collectJarNS(GRADLE_CACHE_DIR);
1966
+ if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
1967
+ jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
1968
+ }
1969
+ }
1970
+ } else {
1971
+ thoughtLog(
1972
+ "The Gradle lockfiles yielded no components, so the remaining static sources are tried.",
1973
+ );
1974
+ }
1975
+ }
1976
+ // Components that came from somewhere other than this Gradle run, such
1977
+ // as a Maven build in the same root, so the decisions below look only at
1978
+ // what Gradle itself produced.
1979
+ const pkgCountBeforeGradleRun = pkgList.length;
1715
1980
  if (
1716
1981
  gradleFiles?.length &&
1717
1982
  options.installDeps &&
1718
- isPackageManagerAllowed(
1719
- "gradle",
1720
- ["maven", "bazel", "sbt", "mill"],
1721
- options,
1722
- )
1983
+ gradleManagerAllowed &&
1984
+ !gradleUseLockfiles
1723
1985
  ) {
1724
1986
  allProjects.push(parentComponent);
1725
1987
  const gradleCmd = getGradleCommand(gradleRootPath, null, options);
@@ -1848,6 +2110,23 @@ export async function createJavaBom(path, options) {
1848
2110
  }
1849
2111
  reconcileGradleComponents(pkgList);
1850
2112
  }
2113
+ const gradleRunPkgCount = pkgList.length - pkgCountBeforeGradleRun;
2114
+ if (!gradleRunPkgCount && gradleLockFiles.length) {
2115
+ // Gradle produced no components of its own. The lockfiles are the
2116
+ // next source before the failure paths below: Gradle may have run
2117
+ // fine over configurations the project never locked, or failed
2118
+ // outright while the committed lockfiles still describe the build.
2119
+ const lockfileCount = addGradleLockfileComponents();
2120
+ if (lockfileCount) {
2121
+ thoughtLog(
2122
+ `Gradle produced no components, so the ${lockfileCount} components of ${gradleLockFiles.length} Gradle lockfile(s) were used instead.`,
2123
+ );
2124
+ }
2125
+ }
2126
+ // A catalog or verification fallback can supply these components when
2127
+ // Gradle produced none, so they pass through the same distribution and
2128
+ // subproject handling below.
2129
+ addGradleStaticFallbackComponents();
1851
2130
  if (pkgList.length) {
1852
2131
  const fileToUrlMap = parseGradleInfoLogsForUrls(sstdout);
1853
2132
  // When the `resolve-gradle-distribution` feature flag is enabled, the init script
@@ -1894,11 +2173,21 @@ export async function createJavaBom(path, options) {
1894
2173
  }
1895
2174
  }
1896
2175
  thoughtLog(
1897
- `Obtained ${pkgList.length} components by executing the '${basename(gradleCmd)}' command.`,
2176
+ gradleRunPkgCount
2177
+ ? `Obtained ${pkgList.length} components by executing the '${basename(gradleCmd)}' command.`
2178
+ : `Obtained ${pkgList.length} components from the static Gradle sources after '${basename(gradleCmd)}' produced none.`,
1898
2179
  );
1899
2180
  if (DEBUG_MODE) {
1900
2181
  console.log("Obtained", pkgList.length, "from this gradle project.");
1901
2182
  }
2183
+ if (gradleRunPkgCount && gradleLockFiles.length) {
2184
+ // Gradle resolved against the lockfiles itself, so the versions it
2185
+ // reports already are the locked ones and parsing the files again
2186
+ // would add nothing.
2187
+ thoughtLog(
2188
+ `Gradle enforced the ${gradleLockFiles.length} Gradle lockfile(s) of this build, so they are not parsed separately.`,
2189
+ );
2190
+ }
1902
2191
  } else if (hasGradleInvocationFailure()) {
1903
2192
  // The invocation failure is already ranked, and an empty
1904
2193
  // configuration is what that failure looks like from here: reporting
@@ -1964,29 +2253,14 @@ export async function createJavaBom(path, options) {
1964
2253
  jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
1965
2254
  }
1966
2255
  }
1967
-
1968
- // A version catalog declares coordinates without resolving them, so it
1969
- // is used only when Gradle produced no dependency information at all.
1970
- // The emitted components carry cdx:gradle:catalog=true to mark them as
1971
- // declared rather than resolved.
1972
- if (!pkgList.length) {
1973
- const catalogFiles = getAllFiles(
1974
- path,
1975
- `${options.multiProject ? "**/" : ""}gradle/libs.versions.toml`,
1976
- options,
1977
- );
1978
- for (const catalogFile of catalogFiles) {
1979
- if (DEBUG_MODE) {
1980
- console.log(`Parsing ${catalogFile}`);
1981
- }
1982
- const dlist = parseGradleVersionCatalog(catalogFile);
1983
- if (dlist?.length) {
1984
- pkgList = pkgList.concat(dlist);
1985
- }
1986
- }
1987
- }
1988
2256
  }
1989
2257
 
2258
+ // Outside the installDeps block on purpose: a scan with dependency
2259
+ // installation disabled reaches the static sources through the lockfile
2260
+ // path instead of the Gradle run, and a scan whose Gradle run was
2261
+ // skipped entirely needs them here.
2262
+ addGradleStaticFallbackComponents();
2263
+
1990
2264
  // Bazel
1991
2265
  // Look for the BUILD file only in the root directory
1992
2266
  // NOTE: This can match BUILD files used by perl, so could lead to errors in some projects
@@ -2907,6 +3181,57 @@ export async function createJavaBom(path, options) {
2907
3181
  pkgList = await getMvnMetadata(pkgList, jarNSMapping, false, {
2908
3182
  skipPurls: ownComponentRefs(parentComponent),
2909
3183
  });
3184
+ // The verification file is the last enrichment, after getMvnMetadata has
3185
+ // hashed the jars of the local cache: a digest of the artifact itself is
3186
+ // the stronger claim, so the recorded digests only fill the algorithms a
3187
+ // component still lacks, and a differing digest of the same algorithm is
3188
+ // flagged, never applied. Whatever source found the component keeps the
3189
+ // identity evidence of that source.
3190
+ if (gradleVerificationFile) {
3191
+ const verificationMetadata = gradleVerification();
3192
+ if (verificationMetadata) {
3193
+ const enrichedCount = applyGradleVerificationMetadata(
3194
+ pkgList,
3195
+ verificationMetadata,
3196
+ gradleVerificationFile,
3197
+ );
3198
+ if (enrichedCount) {
3199
+ thoughtLog(
3200
+ `Enriched ${enrichedCount} component(s) with the checksums that ${gradleVerificationFile} records.`,
3201
+ );
3202
+ }
3203
+ // Whether Gradle verified metadata and signatures is a fact about the
3204
+ // whole build, so it lands on the primary component. Only the flags
3205
+ // the file actually writes are emitted.
3206
+ const { configuration } = verificationMetadata;
3207
+ if (parentComponent && Object.keys(parentComponent).length) {
3208
+ const parentProperties = parentComponent.properties || [];
3209
+ if (
3210
+ configuration.verifyMetadata !== undefined &&
3211
+ !parentProperties.some((p) => p.name === "cdx:gradle:verifyMetadata")
3212
+ ) {
3213
+ parentProperties.push({
3214
+ name: "cdx:gradle:verifyMetadata",
3215
+ value: String(configuration.verifyMetadata),
3216
+ });
3217
+ }
3218
+ if (
3219
+ configuration.verifySignatures !== undefined &&
3220
+ !parentProperties.some(
3221
+ (p) => p.name === "cdx:gradle:verifySignatures",
3222
+ )
3223
+ ) {
3224
+ parentProperties.push({
3225
+ name: "cdx:gradle:verifySignatures",
3226
+ value: String(configuration.verifySignatures),
3227
+ });
3228
+ }
3229
+ if (parentProperties.length) {
3230
+ parentComponent.properties = parentProperties;
3231
+ }
3232
+ }
3233
+ }
3234
+ }
2910
3235
  return buildBomNSData(options, pkgList, "maven", {
2911
3236
  src: path,
2912
3237
  nsMapping: jarNSMapping,
@@ -147,6 +147,7 @@ import {
147
147
  dosaiFailureReported,
148
148
  dosaiMethodsSectionsArgument,
149
149
  dosaiReportFileOmittedSections,
150
+ dosaiSlicesFileIsFresh,
150
151
  dosaiToolComponents,
151
152
  isDosaiDotnetLanguage,
152
153
  normalizeDosaiServiceMap,
@@ -2505,9 +2506,13 @@ export async function createCsharpBom(path, options) {
2505
2506
  // Anything else needs only the sections cdxgen reads, so dosai
2506
2507
  // leaves the rest out (Metadata.OmittedSections names it).
2507
2508
  const willRunEvinse = runsEvidenceAnalysis(options);
2508
- // Reuse an existing slice as-is, with one exception: a trimmed file
2509
+ // Reuse an existing slice as-is, with two exceptions. A trimmed file
2509
2510
  // (an earlier --deep run left it behind) is regenerated full before
2510
- // an evidence run uses it, so dosai still runs once per scan.
2511
+ // an evidence run uses it, so dosai still runs once per scan. A slice
2512
+ // older than the sources it describes is regenerated for every run:
2513
+ // it holds the occurrences of the sources as they were when it was
2514
+ // written, so reusing one keeps occurrences of call sites that no
2515
+ // longer exist (issue 4675).
2511
2516
  let reuseExisting = safeExistsSync(slicesFile);
2512
2517
  if (reuseExisting && willRunEvinse) {
2513
2518
  const omitted = dosaiReportFileOmittedSections(slicesFile);
@@ -2520,6 +2525,17 @@ export async function createCsharpBom(path, options) {
2520
2525
  reuseExisting = false;
2521
2526
  }
2522
2527
  }
2528
+ if (
2529
+ reuseExisting &&
2530
+ !dosaiSlicesFileIsFresh(slicesFile, path, options)
2531
+ ) {
2532
+ if (DEBUG_MODE) {
2533
+ console.log(
2534
+ `The existing dosai methods slice "${slicesFile}" is older than the sources it describes; regenerating it.`,
2535
+ );
2536
+ }
2537
+ reuseExisting = false;
2538
+ }
2523
2539
  // Create the slices file if it doesn't exist
2524
2540
  if (!reuseExisting) {
2525
2541
  thoughtLog(
@@ -2551,7 +2567,7 @@ export async function createCsharpBom(path, options) {
2551
2567
  }
2552
2568
  } else if (DEBUG_MODE) {
2553
2569
  console.log(
2554
- `Reusing the existing dosai methods slice "${slicesFile}" without checking whether it honours the current --exclude patterns. Delete the file to force a fresh run.`,
2570
+ `Reusing the existing dosai methods slice "${slicesFile}", which is newer than the sources it describes. The slice is still not checked against the current --exclude patterns; delete the file to force a fresh run.`,
2555
2571
  );
2556
2572
  }
2557
2573
  pkgList = addEvidenceForDotnet(pkgList, slicesFile, {
@@ -1429,6 +1429,16 @@ export async function createDartBom(path, options) {
1429
1429
  pkgList = pkgList.concat(retMap.pkgList);
1430
1430
  }
1431
1431
  if (retMap?.rootList?.length) {
1432
+ // A pubspec.lock read without its pubspec.yaml still names the
1433
+ // project's direct dependencies; the project is then named after the
1434
+ // directory that holds the lock file.
1435
+ if (!parentComponent) {
1436
+ parentComponent = createDefaultParentComponent(
1437
+ dirname(f),
1438
+ "generic",
1439
+ options,
1440
+ );
1441
+ }
1432
1442
  const thisParentDependsOn = [
1433
1443
  {
1434
1444
  ref: parentComponent["bom-ref"],
package/lib/core/env.js CHANGED
@@ -541,6 +541,32 @@ export const PROJECT_TYPE_ALIASES = {
541
541
  "ai-provenance": ["ai-provenance", "ai-authorship", "aicode", "ai-codegen"],
542
542
  };
543
543
 
544
+ /**
545
+ * Project types, with their aliases, that inventory the host cdxgen runs on
546
+ * rather than the scan path: installed packages, users, shell history, IDE
547
+ * and browser extensions, or the Maven, Gradle, sbt, Cargo and Helm caches in
548
+ * the user's home directory. Only the operator may select them; neither a
549
+ * server request nor a config file in the scanned directory can.
550
+ *
551
+ * @type {Set<string>}
552
+ */
553
+ export const HOST_SCOPE_PROJECT_TYPES = new Set(
554
+ [
555
+ "os",
556
+ "hbom",
557
+ "cargo-cache",
558
+ "helm-index",
559
+ "gradle-index",
560
+ "sbt-index",
561
+ "maven-index",
562
+ "vscode-extension",
563
+ "chrome-extension",
564
+ "agentic",
565
+ ]
566
+ .flatMap((key) => PROJECT_TYPE_ALIASES[key] || [key])
567
+ .concat(["maven-repo"]),
568
+ );
569
+
544
570
  /** Map of base package manager to the array of accepted alias strings. */
545
571
  export const PACKAGE_MANAGER_ALIASES = {
546
572
  scala: ["sbt"],