@cdxgen/cdxgen 13.1.0 → 13.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +32 -15
  2. package/bin/cdxgen.js +243 -122
  3. package/bin/evinse.js +7 -0
  4. package/bin/sign.js +88 -14
  5. package/bin/tracebom.js +64 -4
  6. package/bin/validate.js +29 -7
  7. package/bin/verify.js +79 -39
  8. package/data/crypto-oid.json +88 -12
  9. package/data/remediations.json +47 -0
  10. package/data/rules/ai-bom.yaml +78 -0
  11. package/data/rules/cargo-rust.yaml +132 -0
  12. package/data/rules/mcp-servers.yaml +122 -0
  13. package/lib/audit/targets.js +6 -1
  14. package/lib/cli/bomAssembly.js +117 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +235 -5
  17. package/lib/cli/jsBom.js +108 -26
  18. package/lib/cli/jvmBom.js +68 -12
  19. package/lib/cli/managedBom.js +264 -41
  20. package/lib/cli/nativeBom.js +923 -15
  21. package/lib/core/activity.js +120 -2
  22. package/lib/core/env.js +44 -7
  23. package/lib/core/fs.js +174 -6
  24. package/lib/core/metadataProperties.js +97 -0
  25. package/lib/core/projectConfig.js +31 -4
  26. package/lib/core/userAgent.js +151 -0
  27. package/lib/ecosystems/bunutils.js +269 -101
  28. package/lib/ecosystems/cargoCache.js +364 -0
  29. package/lib/ecosystems/caxa.js +447 -7
  30. package/lib/ecosystems/cmakeResolver.js +66 -0
  31. package/lib/ecosystems/cppEvidence.js +104 -33
  32. package/lib/ecosystems/ecosystems.js +260 -99
  33. package/lib/ecosystems/golem.js +11 -1
  34. package/lib/ecosystems/gradleutils.js +185 -9
  35. package/lib/ecosystems/jsEvidence.js +19 -20
  36. package/lib/ecosystems/kosi.js +1263 -0
  37. package/lib/ecosystems/npmutils.js +110 -9
  38. package/lib/ecosystems/parsers-crystal.js +179 -0
  39. package/lib/ecosystems/parsers-dotnet.js +120 -49
  40. package/lib/ecosystems/parsers-elm.js +816 -0
  41. package/lib/ecosystems/parsers-gleam.js +1 -1
  42. package/lib/ecosystems/parsers-go.js +47 -9
  43. package/lib/ecosystems/parsers-js.js +171 -50
  44. package/lib/ecosystems/parsers-julia.js +268 -0
  45. package/lib/ecosystems/parsers-lua.js +218 -0
  46. package/lib/ecosystems/parsers-misc.js +919 -161
  47. package/lib/ecosystems/parsers-nim.js +221 -0
  48. package/lib/ecosystems/parsers-opam.js +180 -0
  49. package/lib/ecosystems/parsers-perl.js +277 -0
  50. package/lib/ecosystems/parsers-python.js +27 -15
  51. package/lib/ecosystems/parsers-r.js +148 -0
  52. package/lib/ecosystems/parsers-rebar.js +411 -0
  53. package/lib/ecosystems/parsers-rust.js +954 -33
  54. package/lib/ecosystems/parsers-spack.js +204 -0
  55. package/lib/ecosystems/parsers-stack.js +264 -0
  56. package/lib/ecosystems/parsers-terraform.js +194 -0
  57. package/lib/ecosystems/rubyutils.js +1426 -166
  58. package/lib/ecosystems/rusi.js +158 -7
  59. package/lib/evinser/evinser.js +547 -129
  60. package/lib/evinser/swiftsem.js +1914 -302
  61. package/lib/helpers/agenticToolutils.js +925 -0
  62. package/lib/helpers/bomSigner.js +652 -221
  63. package/lib/inventory/agentFormulationParser.js +57 -4
  64. package/lib/inventory/aiCollector.js +504 -10
  65. package/lib/inventory/aiInventory.js +12 -0
  66. package/lib/inventory/analyzer.js +488 -30
  67. package/lib/inventory/astWorker.js +6 -0
  68. package/lib/inventory/atomUtils.js +568 -31
  69. package/lib/inventory/auditCategories.js +8 -0
  70. package/lib/inventory/cbomutils.js +30 -0
  71. package/lib/inventory/cdxrs.js +110 -14
  72. package/lib/inventory/communityAiConfigParser.js +215 -2
  73. package/lib/inventory/depsUtils.js +183 -11
  74. package/lib/inventory/display.js +4 -5
  75. package/lib/inventory/dosai.js +627 -13
  76. package/lib/inventory/dynamic.js +39 -1
  77. package/lib/inventory/envcontext.js +9 -6
  78. package/lib/inventory/evidenceUtils.js +26 -5
  79. package/lib/inventory/fetchBatch.js +190 -19
  80. package/lib/inventory/fetchRate.js +74 -11
  81. package/lib/inventory/forgeEnricher.js +3 -2
  82. package/lib/inventory/jvmToolEnv.js +6 -5
  83. package/lib/inventory/mcpConfigParser.js +166 -2
  84. package/lib/inventory/mcpDiscovery.js +11 -0
  85. package/lib/inventory/mcpServerManifest.js +389 -0
  86. package/lib/inventory/mcpbBundle.js +419 -0
  87. package/lib/inventory/plugins.js +3 -0
  88. package/lib/inventory/protobom.js +62 -89
  89. package/lib/inventory/protobomLoader.js +4 -0
  90. package/lib/inventory/purl.js +152 -3
  91. package/lib/inventory/rustFormulationParser.js +1 -1
  92. package/lib/inventory/sbomDocument.js +2 -70
  93. package/lib/inventory/source.js +47 -23
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/traceRunner.js +210 -21
  97. package/lib/managers/binary.js +115 -24
  98. package/lib/managers/docker.js +440 -20
  99. package/lib/managers/oci.js +39 -1
  100. package/lib/parsers/cmakeLists.js +452 -0
  101. package/lib/parsers/huggingfaceUtils.js +15 -7
  102. package/lib/parsers/largeJson.js +454 -0
  103. package/lib/parsers/npmrc.js +19 -1
  104. package/lib/parsers/onnx.js +294 -0
  105. package/lib/parsers/safetensors.js +114 -0
  106. package/lib/stages/postgen/introspection/markers.js +29 -6
  107. package/lib/stages/postgen/postgen.js +237 -16
  108. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  109. package/lib/validator/bomValidator.js +6 -16
  110. package/lib/validator/index.js +48 -10
  111. package/lib/validator/reporters/console.js +3 -0
  112. package/lib/validator/reporters/json.js +1 -0
  113. package/lib/validator/reporters/sarif.js +7 -0
  114. package/package.json +27 -27
  115. package/types/lib/audit/targets.d.ts.map +1 -1
  116. package/types/lib/cli/bomAssembly.d.ts +2 -1
  117. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  118. package/types/lib/cli/cliOptions.d.ts +33 -1
  119. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  120. package/types/lib/cli/index.d.ts +2 -2
  121. package/types/lib/cli/index.d.ts.map +1 -1
  122. package/types/lib/cli/jsBom.d.ts +23 -0
  123. package/types/lib/cli/jsBom.d.ts.map +1 -1
  124. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  125. package/types/lib/cli/managedBom.d.ts +33 -0
  126. package/types/lib/cli/managedBom.d.ts.map +1 -1
  127. package/types/lib/cli/nativeBom.d.ts +88 -8
  128. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  129. package/types/lib/core/activity.d.ts.map +1 -1
  130. package/types/lib/core/env.d.ts +21 -0
  131. package/types/lib/core/env.d.ts.map +1 -1
  132. package/types/lib/core/fs.d.ts +55 -0
  133. package/types/lib/core/fs.d.ts.map +1 -1
  134. package/types/lib/core/metadataProperties.d.ts +39 -0
  135. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  136. package/types/lib/core/projectConfig.d.ts +6 -2
  137. package/types/lib/core/projectConfig.d.ts.map +1 -1
  138. package/types/lib/core/userAgent.d.ts +56 -0
  139. package/types/lib/core/userAgent.d.ts.map +1 -0
  140. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  141. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  143. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  144. package/types/lib/ecosystems/caxa.d.ts +14 -0
  145. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  146. package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
  147. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  148. package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
  149. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  150. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  151. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  152. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  153. package/types/lib/ecosystems/gradleutils.d.ts +40 -4
  154. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  155. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  156. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  157. package/types/lib/ecosystems/kosi.d.ts +86 -0
  158. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  159. package/types/lib/ecosystems/npmutils.d.ts +42 -0
  160. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  161. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  162. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  163. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  164. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  165. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  166. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  167. package/types/lib/ecosystems/parsers-go.d.ts +10 -0
  168. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  169. package/types/lib/ecosystems/parsers-js.d.ts +10 -4
  170. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  171. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  172. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  173. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  174. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
  176. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  177. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  178. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  179. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  180. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  181. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  182. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  183. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  184. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  185. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  187. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  188. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  189. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  191. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  192. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  193. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  194. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  195. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  196. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  197. package/types/lib/ecosystems/rubyutils.d.ts +143 -2
  198. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  199. package/types/lib/ecosystems/rusi.d.ts +13 -0
  200. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  201. package/types/lib/evinser/evinser.d.ts +81 -2
  202. package/types/lib/evinser/evinser.d.ts.map +1 -1
  203. package/types/lib/evinser/swiftsem.d.ts +280 -15
  204. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  205. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  206. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  207. package/types/lib/helpers/bomSigner.d.ts +106 -13
  208. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  209. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  210. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  211. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  212. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  213. package/types/lib/inventory/atomUtils.d.ts +145 -3
  214. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +4 -0
  216. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  217. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  218. package/types/lib/inventory/cdxrs.d.ts +8 -3
  219. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  220. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  221. package/types/lib/inventory/depsUtils.d.ts +15 -0
  222. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/display.d.ts.map +1 -1
  224. package/types/lib/inventory/dosai.d.ts +87 -1
  225. package/types/lib/inventory/dosai.d.ts.map +1 -1
  226. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  227. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  228. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  229. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  230. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  231. package/types/lib/inventory/fetchRate.d.ts +9 -6
  232. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  233. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  234. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  235. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  236. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  237. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  238. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  239. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  240. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  241. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  242. package/types/lib/inventory/plugins.d.ts.map +1 -1
  243. package/types/lib/inventory/protobom.d.ts +18 -13
  244. package/types/lib/inventory/protobom.d.ts.map +1 -1
  245. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  246. package/types/lib/inventory/purl.d.ts +71 -2
  247. package/types/lib/inventory/purl.d.ts.map +1 -1
  248. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  249. package/types/lib/inventory/source.d.ts.map +1 -1
  250. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  251. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  252. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  253. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  254. package/types/lib/inventory/traceRunner.d.ts +47 -1
  255. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  256. package/types/lib/managers/binary.d.ts +30 -1
  257. package/types/lib/managers/binary.d.ts.map +1 -1
  258. package/types/lib/managers/docker.d.ts +29 -0
  259. package/types/lib/managers/docker.d.ts.map +1 -1
  260. package/types/lib/managers/oci.d.ts +14 -0
  261. package/types/lib/managers/oci.d.ts.map +1 -1
  262. package/types/lib/parsers/cmakeLists.d.ts +76 -0
  263. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  264. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  265. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  266. package/types/lib/parsers/largeJson.d.ts +52 -0
  267. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  268. package/types/lib/parsers/npmrc.d.ts +5 -0
  269. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  270. package/types/lib/parsers/onnx.d.ts +28 -0
  271. package/types/lib/parsers/onnx.d.ts.map +1 -0
  272. package/types/lib/parsers/safetensors.d.ts +22 -0
  273. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  275. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  276. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  277. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  278. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  279. package/types/lib/validator/index.d.ts +10 -1
  280. package/types/lib/validator/index.d.ts.map +1 -1
  281. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  282. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  283. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
@@ -22,6 +22,7 @@ import {
22
22
  } from "../../core/fs.js";
23
23
  import { thoughtLog } from "../../core/logger.js";
24
24
  import { dirNameStr } from "../../core/paths.js";
25
+ import { sanitizeBomPropertyValue } from "../../core/propertySanitizer.js";
25
26
  import { CDXGEN_TOOL_GROUP, CDXGEN_TOOL_NAME } from "../../core/state.js";
26
27
  import {
27
28
  AI_INVENTORY_PROJECT_TYPES,
@@ -565,11 +566,21 @@ export async function postProcess(bomNSData, options, filePath) {
565
566
  bomNSData.bomJson = applyMetadata(bomNSData.bomJson, options);
566
567
  bomNSData.bomJson = applyLicenseEnhancement(bomNSData.bomJson, options);
567
568
  bomNSData.bomJson = applyContainerInventoryMetadata(bomNSData.bomJson);
569
+ const buildOnlyComponents = extractBuildOnlyComponents(
570
+ bomNSData.bomJson,
571
+ options,
572
+ );
568
573
  bomNSData.bomJson = applyFormulation(
569
574
  bomNSData.bomJson,
570
575
  options,
571
576
  filePath,
572
- bomNSData.formulationList,
577
+ buildOnlyComponents.length
578
+ ? [...(bomNSData.formulationList || []), ...buildOnlyComponents]
579
+ : bomNSData.formulationList,
580
+ );
581
+ bomNSData.bomJson = applyBuildOnlyFormulation(
582
+ bomNSData.bomJson,
583
+ buildOnlyComponents,
573
584
  );
574
585
  bomNSData.bomJson = applyReleaseNotes(bomNSData.bomJson, options, filePath);
575
586
  bomNSData.bomJson = applyComponentTypeFilter(bomNSData.bomJson, options);
@@ -731,24 +742,18 @@ function applyReleaseNotes(bomJson, options, filePath) {
731
742
  */
732
743
  export function applyMetadata(bomJson, options) {
733
744
  if (!bomJson?.components) {
734
- return bomJson;
745
+ return applyCustomMetadataProperties(bomJson, options);
735
746
  }
736
747
  const bomPkgTypes = new Set();
737
748
  const bomPkgNamespaces = new Set();
738
749
  const bomSrcFiles = new Set();
739
- for (const comp of bomJson.components) {
740
- if (comp.purl) {
741
- try {
742
- const purlObj = Purl.parse(comp.purl);
743
- if (purlObj?.type) {
744
- bomPkgTypes.add(purlObj.type);
745
- }
746
- if (purlObj?.namespace) {
747
- bomPkgNamespaces.add(purlObj.namespace);
748
- }
749
- } catch (_e) {
750
- // ignore
751
- }
750
+ // Collect the manifests a component was identified from, rewriting any
751
+ // absolute path to a relative one as a side effect. Shared by the components
752
+ // and by metadata.component, which a collector such as the ruby one describes
753
+ // from the project's own gemspec (discussion 4410).
754
+ const collectSrcFiles = (comp) => {
755
+ if (!comp) {
756
+ return;
752
757
  }
753
758
  if (comp.properties) {
754
759
  for (const aprop of comp.properties) {
@@ -780,6 +785,10 @@ export function applyMetadata(bomJson, options) {
780
785
  Array.isArray(aidentityEvidence.methods)
781
786
  ) {
782
787
  for (const amethod of aidentityEvidence.methods) {
788
+ // `value` is optional: an attestation, for one, names no file.
789
+ if (typeof amethod?.value !== "string") {
790
+ continue;
791
+ }
783
792
  const rdir = relativeDir(amethod.value, options);
784
793
  if (
785
794
  comp.type !== "file" &&
@@ -796,7 +805,24 @@ export function applyMetadata(bomJson, options) {
796
805
  }
797
806
  }
798
807
  }
808
+ };
809
+ for (const comp of bomJson.components) {
810
+ if (comp.purl) {
811
+ try {
812
+ const purlObj = Purl.parse(comp.purl);
813
+ if (purlObj?.type) {
814
+ bomPkgTypes.add(purlObj.type);
815
+ }
816
+ if (purlObj?.namespace) {
817
+ bomPkgNamespaces.add(purlObj.namespace);
818
+ }
819
+ } catch (_e) {
820
+ // ignore
821
+ }
822
+ }
823
+ collectSrcFiles(comp);
799
824
  }
825
+ collectSrcFiles(bomJson.metadata?.component);
800
826
  if (!bomJson.metadata.properties) {
801
827
  bomJson.metadata.properties = [];
802
828
  }
@@ -852,6 +878,43 @@ export function applyMetadata(bomJson, options) {
852
878
  thoughtLog("BOM lacks package manifest details. Please help us improve!");
853
879
  }
854
880
  }
881
+ return applyCustomMetadataProperties(bomJson, options);
882
+ }
883
+
884
+ /**
885
+ * Add the metadata properties the user asked for on the command line, in the
886
+ * environment, or in a config file (discussion 4391), after whatever the scan
887
+ * itself discovered. They never replace a discovered property: the scan's own
888
+ * names, such as `cdx:bom:componentSrcFiles`, keep their values. A BOM without
889
+ * components still gets them.
890
+ *
891
+ * @param {Object} bomJson BOM JSON Object
892
+ * @param {Object} options CLI options, with `metadataProperties` as
893
+ * `[{name, value}]`
894
+ *
895
+ * @returns {Object} BOM JSON Object
896
+ */
897
+ function applyCustomMetadataProperties(bomJson, options) {
898
+ if (!bomJson?.metadata || !options?.metadataProperties?.length) {
899
+ return bomJson;
900
+ }
901
+ bomJson.metadata.properties = bomJson.metadata.properties || [];
902
+ for (const aprop of options.metadataProperties) {
903
+ if (!aprop?.name?.length || aprop.value === undefined) {
904
+ continue;
905
+ }
906
+ const value = sanitizeBomPropertyValue(aprop.name, `${aprop.value}`);
907
+ if (!value?.length) {
908
+ continue;
909
+ }
910
+ if (
911
+ !bomJson.metadata.properties.some(
912
+ (p) => p.name === aprop.name && p.value === value,
913
+ )
914
+ ) {
915
+ bomJson.metadata.properties.push({ name: aprop.name, value });
916
+ }
917
+ }
855
918
  return bomJson;
856
919
  }
857
920
 
@@ -964,6 +1027,154 @@ function getIdentityTechniques(comp) {
964
1027
  return techniques;
965
1028
  }
966
1029
 
1030
+ /**
1031
+ * Ensure the components moved out of the assembly have landed in the
1032
+ * formulation section.
1033
+ *
1034
+ * With `--include-formulation` they were handed to the full formulation pass.
1035
+ * Without it there is no formulation section, so a minimal one is created for
1036
+ * them alone: moving a component out of `components[]` and then dropping it
1037
+ * would lose it from the document.
1038
+ *
1039
+ * @param {Object} bomJson BOM JSON object
1040
+ * @param {Object[]} buildOnlyComponents Components removed from `components[]`
1041
+ *
1042
+ * @returns {Object} The same `bomJson`
1043
+ */
1044
+ function applyBuildOnlyFormulation(bomJson, buildOnlyComponents) {
1045
+ if (!buildOnlyComponents?.length || !bomJson) {
1046
+ return bomJson;
1047
+ }
1048
+ const alreadyPlaced = (bomJson.formulation || []).some((formula) =>
1049
+ formula?.components?.some((comp) =>
1050
+ buildOnlyComponents.some((moved) => moved["bom-ref"] === comp["bom-ref"]),
1051
+ ),
1052
+ );
1053
+ if (alreadyPlaced) {
1054
+ return bomJson;
1055
+ }
1056
+ if (!bomJson.formulation) {
1057
+ bomJson.formulation = [];
1058
+ }
1059
+ bomJson.formulation.push({
1060
+ "bom-ref": `${bomJson.metadata?.component?.["bom-ref"] || "cdxgen"}#build-dependencies`,
1061
+ components: buildOnlyComponents,
1062
+ });
1063
+ return bomJson;
1064
+ }
1065
+
1066
+ /**
1067
+ * Report whether a component exists only to produce the build, rather than
1068
+ * being part of the delivered assembly. A cargo build dependency and a
1069
+ * proc-macro crate are both marked with `cdx:cargo:hostOnly`.
1070
+ *
1071
+ * @param {Object} comp Component
1072
+ * @returns {boolean} True for a build-time-only component
1073
+ */
1074
+ function isBuildOnlyComponent(comp) {
1075
+ return !!comp?.properties?.some(
1076
+ (property) =>
1077
+ property.name === "cdx:cargo:hostOnly" && property.value === "true",
1078
+ );
1079
+ }
1080
+
1081
+ /**
1082
+ * Move the build-time-only components out of `components[]`, which describes
1083
+ * the delivered assembly, and hand them to the formulation section, which is
1084
+ * where CycloneDX describes how the assembly was produced.
1085
+ *
1086
+ * Their bom-refs stay valid because the formulation components live in the same
1087
+ * document, so the dependency graph is left intact. `formulation` exists from
1088
+ * CycloneDX 1.5, so an older document keeps them in `components[]` with their
1089
+ * `cdx:cargo:hostOnly` marker rather than losing them.
1090
+ *
1091
+ * @param {Object} bomJson BOM JSON object
1092
+ * @param {Object} options CLI options
1093
+ *
1094
+ * @returns {Object[]} The components removed from `components[]`
1095
+ */
1096
+ export function extractBuildOnlyComponents(bomJson, options) {
1097
+ if (options?.specVersion < 1.5 || !bomJson?.components?.length) {
1098
+ return [];
1099
+ }
1100
+ const buildOnlyComponents = [];
1101
+ const retainedComponents = [];
1102
+ for (const comp of bomJson.components) {
1103
+ if (isBuildOnlyComponent(comp)) {
1104
+ buildOnlyComponents.push(comp);
1105
+ } else {
1106
+ retainedComponents.push(comp);
1107
+ }
1108
+ }
1109
+ if (buildOnlyComponents.length) {
1110
+ bomJson.components = retainedComponents;
1111
+ }
1112
+ return buildOnlyComponents;
1113
+ }
1114
+
1115
+ /**
1116
+ * Report whether a component carries evidence that it is actually used: an
1117
+ * occurrence in the analyzed sources, or a frame in a callstack.
1118
+ *
1119
+ * @param {Object} comp Component
1120
+ * @returns {boolean} True when usage evidence is attached
1121
+ */
1122
+ function hasUsageEvidence(comp) {
1123
+ if (comp?.evidence?.occurrences?.length) {
1124
+ return true;
1125
+ }
1126
+ return !!comp?.evidence?.callstack?.frames?.length;
1127
+ }
1128
+
1129
+ /**
1130
+ * Re-apply the `--required-only` filter once the evidence stage has attached
1131
+ * occurrence and callstack data.
1132
+ *
1133
+ * A manifest can only say that a dependency is optional; the evidence says
1134
+ * whether it is reached. A component the analyzers observed in the sources is
1135
+ * promoted to `required` so it survives the filter, while an optional component
1136
+ * with no observed usage is dropped as before. A component scoped `excluded` -
1137
+ * a dev or test dependency - keeps that scope: an occurrence inside the test
1138
+ * sources is not evidence that it ships.
1139
+ *
1140
+ * @param {Object} bomJson BOM JSON object carrying evidence
1141
+ * @param {Object} options CLI options
1142
+ *
1143
+ * @returns {Object} Filtered BOM JSON
1144
+ */
1145
+ export function applyEvidenceBasedFilter(bomJson, options) {
1146
+ if (!options?.requiredOnly || !bomJson?.components?.length) {
1147
+ return bomJson;
1148
+ }
1149
+ if (!(options.evidence || options.includeCrypto)) {
1150
+ return bomJson;
1151
+ }
1152
+ for (const comp of bomJson.components) {
1153
+ if (!hasUsageEvidence(comp)) {
1154
+ continue;
1155
+ }
1156
+ if (!comp.properties) {
1157
+ comp.properties = [];
1158
+ }
1159
+ const observed = comp.evidence?.occurrences?.length
1160
+ ? "occurrence"
1161
+ : "callstack";
1162
+ if (
1163
+ !comp.properties.some(
1164
+ (property) => property.name === "cdx:evidence:usage",
1165
+ )
1166
+ ) {
1167
+ comp.properties.push({ name: "cdx:evidence:usage", value: observed });
1168
+ }
1169
+ if (comp.scope === "optional") {
1170
+ comp.scope = "required";
1171
+ }
1172
+ }
1173
+ // `evidenceFilterStage` tells filterBom that the evidence is in hand, so the
1174
+ // scope filter it deferred earlier must run now.
1175
+ return filterBom(bomJson, { ...options, evidenceFilterStage: true });
1176
+ }
1177
+
967
1178
  /**
968
1179
  * Filter BOM based on options
969
1180
  *
@@ -983,6 +1194,15 @@ export function filterBom(bomJson, options) {
983
1194
  const allowedComponentTypes = new Set(
984
1195
  normalizeCycloneDxComponentTypeFilter(options?.componentType),
985
1196
  );
1197
+ // Evidence generation runs after this stage, so dropping the optional
1198
+ // components here would deny the analyzers the chance to prove that one is
1199
+ // used. The scope filter is re-applied by applyEvidenceBasedFilter once the
1200
+ // occurrence and callstack evidence is attached.
1201
+ const deferRequiredOnly = !!(
1202
+ options.requiredOnly &&
1203
+ !options.evidenceFilterStage &&
1204
+ (options.evidence || options.includeCrypto)
1205
+ );
986
1206
  for (const comp of bomJson.components) {
987
1207
  if (shouldExcludeInventoryType(comp, options)) {
988
1208
  filtered = true;
@@ -1025,6 +1245,7 @@ export function filterBom(bomJson, options) {
1025
1245
  }
1026
1246
  if (
1027
1247
  options.requiredOnly &&
1248
+ !deferRequiredOnly &&
1028
1249
  comp.scope &&
1029
1250
  ["optional", "excluded"].includes(comp.scope)
1030
1251
  ) {
@@ -1335,7 +1556,7 @@ export function annotate(bomJson, options) {
1335
1556
  for (const dep of bomJson.dependencies) {
1336
1557
  const newRef = `${bomLinkPrefix}${stripBomLink(bomJson.serialNumber, bomJson.version, dep.ref)}`;
1337
1558
  const newDependsOn = [];
1338
- for (const adon of dep.dependsOn) {
1559
+ for (const adon of dep.dependsOn || []) {
1339
1560
  newDependsOn.push(
1340
1561
  `${bomLinkPrefix}${stripBomLink(bomJson.serialNumber, bomJson.version, adon)}`,
1341
1562
  );