@cdxgen/cdxgen 13.0.1 → 13.2.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 (286) hide show
  1. package/README.md +30 -16
  2. package/bin/audit.js +6 -0
  3. package/bin/cdxgen.js +141 -7
  4. package/bin/evinse.js +3 -1
  5. package/data/component-tags.json +8 -1
  6. package/data/crypto-oid.json +88 -12
  7. package/data/frameworks-list.json +86 -1
  8. package/data/remediations.json +1167 -0
  9. package/data/rules/ai-bom.yaml +78 -0
  10. package/data/rules/asar-archives.yaml +1 -1
  11. package/data/rules/build-fidelity.yaml +782 -0
  12. package/data/rules/cargo-rust.yaml +132 -0
  13. package/data/rules/mcp-servers.yaml +122 -0
  14. package/data/rules/package-integrity.yaml +13 -13
  15. package/lib/audit/index.js +48 -4
  16. package/lib/audit/scoring.js +2 -8
  17. package/lib/audit/targets.js +6 -1
  18. package/lib/cli/bomAssembly.js +32 -1
  19. package/lib/cli/cliOptions.js +94 -7
  20. package/lib/cli/index.js +268 -49
  21. package/lib/cli/jsBom.js +91 -5
  22. package/lib/cli/jvmBom.js +1515 -1314
  23. package/lib/cli/managedBom.js +249 -28
  24. package/lib/cli/nativeBom.js +986 -10
  25. package/lib/core/activity.js +120 -2
  26. package/lib/core/buildLedger.js +799 -0
  27. package/lib/core/deferredExit.js +141 -0
  28. package/lib/core/env.js +108 -2
  29. package/lib/core/fs.js +60 -0
  30. package/lib/core/logger.js +12 -99
  31. package/lib/core/severity.js +16 -0
  32. package/lib/core/syncFileWriter.js +106 -0
  33. package/lib/core/userAgent.js +151 -0
  34. package/lib/ecosystems/cargoCache.js +364 -0
  35. package/lib/ecosystems/caxa.js +13 -5
  36. package/lib/ecosystems/cppEvidence.js +40 -2
  37. package/lib/ecosystems/ecosystems.js +250 -91
  38. package/lib/ecosystems/golem.js +11 -1
  39. package/lib/ecosystems/gradleutils.js +498 -17
  40. package/lib/ecosystems/kosi.js +1256 -0
  41. package/lib/ecosystems/parsers-crystal.js +179 -0
  42. package/lib/ecosystems/parsers-dotnet.js +69 -39
  43. package/lib/ecosystems/parsers-elm.js +816 -0
  44. package/lib/ecosystems/parsers-gleam.js +1 -1
  45. package/lib/ecosystems/parsers-js.js +26 -0
  46. package/lib/ecosystems/parsers-julia.js +268 -0
  47. package/lib/ecosystems/parsers-jvm.js +9 -0
  48. package/lib/ecosystems/parsers-lua.js +218 -0
  49. package/lib/ecosystems/parsers-misc.js +188 -2
  50. package/lib/ecosystems/parsers-nim.js +221 -0
  51. package/lib/ecosystems/parsers-opam.js +180 -0
  52. package/lib/ecosystems/parsers-perl.js +277 -0
  53. package/lib/ecosystems/parsers-python.js +172 -36
  54. package/lib/ecosystems/parsers-r.js +148 -0
  55. package/lib/ecosystems/parsers-rebar.js +411 -0
  56. package/lib/ecosystems/parsers-rust.js +944 -19
  57. package/lib/ecosystems/parsers-spack.js +204 -0
  58. package/lib/ecosystems/parsers-stack.js +264 -0
  59. package/lib/ecosystems/parsers-terraform.js +194 -0
  60. package/lib/ecosystems/pypiClassifiers.js +326 -0
  61. package/lib/ecosystems/pythonutils.js +49 -1
  62. package/lib/ecosystems/rubyutils.js +13 -0
  63. package/lib/ecosystems/rusi.js +126 -6
  64. package/lib/evinser/evinser.js +141 -13
  65. package/lib/helpers/agenticToolutils.js +925 -0
  66. package/lib/helpers/remote/dependency-track.js +81 -14
  67. package/lib/inventory/agentFormulationParser.js +57 -4
  68. package/lib/inventory/aiCollector.js +504 -10
  69. package/lib/inventory/aiInventory.js +12 -0
  70. package/lib/inventory/analyzer.js +488 -30
  71. package/lib/inventory/astWorker.js +6 -0
  72. package/lib/inventory/auditCategories.js +17 -0
  73. package/lib/inventory/cbomutils.js +30 -0
  74. package/lib/inventory/cdxrs.js +7 -2
  75. package/lib/inventory/communityAiConfigParser.js +215 -2
  76. package/lib/inventory/depsUtils.js +72 -0
  77. package/lib/inventory/dosai.js +508 -22
  78. package/lib/inventory/envcontext.js +215 -24
  79. package/lib/inventory/evidenceUtils.js +13 -0
  80. package/lib/inventory/fetchBatch.js +131 -2
  81. package/lib/inventory/fetchRate.js +74 -11
  82. package/lib/inventory/forgeEnricher.js +3 -2
  83. package/lib/inventory/jvmToolEnv.js +594 -0
  84. package/lib/inventory/mcpConfigParser.js +166 -2
  85. package/lib/inventory/mcpDiscovery.js +11 -0
  86. package/lib/inventory/mcpServerManifest.js +389 -0
  87. package/lib/inventory/mcpbBundle.js +419 -0
  88. package/lib/inventory/osinfo.js +1 -0
  89. package/lib/inventory/plugins.js +3 -0
  90. package/lib/inventory/protobom.js +54 -88
  91. package/lib/inventory/protobomLoader.js +4 -0
  92. package/lib/inventory/rustFormulationParser.js +1 -1
  93. package/lib/inventory/source.js +10 -4
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/toolRequirements.js +903 -0
  97. package/lib/managers/binary.js +24 -0
  98. package/lib/managers/docker.js +26 -5
  99. package/lib/parsers/huggingfaceUtils.js +15 -7
  100. package/lib/parsers/onnx.js +294 -0
  101. package/lib/parsers/safetensors.js +114 -0
  102. package/lib/server/server.js +1 -1
  103. package/lib/stages/postgen/annotationBuilder.js +97 -0
  104. package/lib/stages/postgen/auditBom.js +76 -79
  105. package/lib/stages/postgen/introspection/annotate.js +320 -0
  106. package/lib/stages/postgen/introspection/commandFacts.js +293 -0
  107. package/lib/stages/postgen/introspection/emit.js +192 -0
  108. package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
  109. package/lib/stages/postgen/introspection/markers.js +250 -0
  110. package/lib/stages/postgen/introspection/reflect.js +1214 -0
  111. package/lib/stages/postgen/introspection/report.js +1208 -0
  112. package/lib/stages/postgen/introspection/score.js +1067 -0
  113. package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
  114. package/lib/stages/postgen/parentEdge.js +156 -0
  115. package/lib/stages/postgen/postgen.js +284 -3
  116. package/lib/stages/postgen/ruleEngine.js +17 -0
  117. package/lib/stages/pregen/envAudit.js +42 -2
  118. package/lib/stages/pregen/pregen.js +1171 -6
  119. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  120. package/lib/validator/bomValidator.js +11 -2
  121. package/package.json +26 -26
  122. package/types/lib/audit/index.d.ts.map +1 -1
  123. package/types/lib/audit/scoring.d.ts +2 -8
  124. package/types/lib/audit/scoring.d.ts.map +1 -1
  125. package/types/lib/audit/targets.d.ts.map +1 -1
  126. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  127. package/types/lib/cli/cliOptions.d.ts +2 -1
  128. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  129. package/types/lib/cli/index.d.ts +10 -2
  130. package/types/lib/cli/index.d.ts.map +1 -1
  131. package/types/lib/cli/jsBom.d.ts +23 -0
  132. package/types/lib/cli/jsBom.d.ts.map +1 -1
  133. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  134. package/types/lib/cli/managedBom.d.ts +33 -0
  135. package/types/lib/cli/managedBom.d.ts.map +1 -1
  136. package/types/lib/cli/nativeBom.d.ts +88 -8
  137. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  138. package/types/lib/core/activity.d.ts.map +1 -1
  139. package/types/lib/core/buildLedger.d.ts +272 -0
  140. package/types/lib/core/buildLedger.d.ts.map +1 -0
  141. package/types/lib/core/deferredExit.d.ts +84 -0
  142. package/types/lib/core/deferredExit.d.ts.map +1 -0
  143. package/types/lib/core/env.d.ts +43 -1
  144. package/types/lib/core/env.d.ts.map +1 -1
  145. package/types/lib/core/fs.d.ts +21 -0
  146. package/types/lib/core/fs.d.ts.map +1 -1
  147. package/types/lib/core/logger.d.ts +2 -11
  148. package/types/lib/core/logger.d.ts.map +1 -1
  149. package/types/lib/core/severity.d.ts +11 -0
  150. package/types/lib/core/severity.d.ts.map +1 -0
  151. package/types/lib/core/syncFileWriter.d.ts +33 -0
  152. package/types/lib/core/syncFileWriter.d.ts.map +1 -0
  153. package/types/lib/core/userAgent.d.ts +56 -0
  154. package/types/lib/core/userAgent.d.ts.map +1 -0
  155. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  156. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  157. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  158. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  159. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  160. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  161. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  162. package/types/lib/ecosystems/gradleutils.d.ts +53 -0
  163. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  164. package/types/lib/ecosystems/kosi.d.ts +73 -0
  165. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  166. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  167. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  168. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  169. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  170. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  171. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  172. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  173. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  174. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  176. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  177. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  178. package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
  179. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  180. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  181. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  182. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  183. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  184. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  185. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  187. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  188. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  189. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  190. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  191. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  192. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  193. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  194. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  195. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  196. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  197. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  198. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  199. package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
  200. package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
  201. package/types/lib/ecosystems/pythonutils.d.ts +21 -1
  202. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  203. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  205. package/types/lib/evinser/evinser.d.ts +34 -1
  206. package/types/lib/evinser/evinser.d.ts.map +1 -1
  207. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  208. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  209. package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
  210. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  211. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  212. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  213. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  214. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +12 -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/communityAiConfigParser.d.ts.map +1 -1
  219. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  220. package/types/lib/inventory/dosai.d.ts +43 -0
  221. package/types/lib/inventory/dosai.d.ts.map +1 -1
  222. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  223. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  224. package/types/lib/inventory/fetchBatch.d.ts +43 -0
  225. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  226. package/types/lib/inventory/fetchRate.d.ts +9 -6
  227. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  228. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  229. package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
  230. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
  231. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  232. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  233. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  234. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  235. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  236. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  237. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  238. package/types/lib/inventory/plugins.d.ts.map +1 -1
  239. package/types/lib/inventory/protobom.d.ts +18 -13
  240. package/types/lib/inventory/protobom.d.ts.map +1 -1
  241. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  242. package/types/lib/inventory/source.d.ts.map +1 -1
  243. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  244. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  245. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  246. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  247. package/types/lib/inventory/toolRequirements.d.ts +216 -0
  248. package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
  249. package/types/lib/managers/binary.d.ts.map +1 -1
  250. package/types/lib/managers/docker.d.ts.map +1 -1
  251. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  252. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  253. package/types/lib/parsers/onnx.d.ts +28 -0
  254. package/types/lib/parsers/onnx.d.ts.map +1 -0
  255. package/types/lib/parsers/safetensors.d.ts +22 -0
  256. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  257. package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
  258. package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
  259. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  260. package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
  261. package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
  262. package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
  263. package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
  264. package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
  265. package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
  266. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
  267. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
  268. package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
  269. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
  270. package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
  271. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
  272. package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
  273. package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
  275. package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
  276. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
  277. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
  278. package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
  279. package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
  280. package/types/lib/stages/postgen/postgen.d.ts +35 -2
  281. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  282. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  283. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  284. package/types/lib/stages/pregen/pregen.d.ts +131 -0
  285. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  286. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -0,0 +1,132 @@
1
+ # Cargo and Rust Rules
2
+ # Categories: cargo-security, cargo-compliance
3
+ # Evaluates the cdx:cargo:* properties cdxgen emits for Rust projects, with the
4
+ # native-library rules keyed on the `links` evidence that only `--deep` can
5
+ # produce.
6
+
7
+ - id: CARGO-SEC-001
8
+ name: "Statically linked native library has no resolvable version"
9
+ description: "A -sys crate compiles a C library into the artifact, but nothing in the build states which version, so no advisory can be matched against it"
10
+ severity: medium
11
+ category: cargo-security
12
+ dry-run-support: full
13
+ condition: |
14
+ components[
15
+ $prop($, 'cdx:cargo:linkage') = 'static'
16
+ and ($not($exists(version)) or version = '')
17
+ ]
18
+ location: |
19
+ { "bomRef": $."bom-ref", "purl": purl }
20
+ message: "Native library '{{ name }}' is compiled into the artifact by '{{ $prop($, 'cdx:cargo:providedBy') }}' with no resolvable version"
21
+ mitigation: "Pin the library version explicitly, or build against a crate release whose version encodes the upstream version as build metadata. Until the version is known, the bundled library has to be reviewed by hand: a vulnerability scanner matching on purl cannot see it"
22
+ evidence: |
23
+ {
24
+ "nativeLibrary": $prop($, 'cdx:cargo:nativeLibrary'),
25
+ "providedBy": $prop($, 'cdx:cargo:providedBy'),
26
+ "linkageEvidence": $prop($, 'cdx:cargo:linkageEvidence'),
27
+ "versionCandidates": $prop($, 'cdx:cargo:upstreamVersionCandidates')
28
+ }
29
+
30
+ - id: CARGO-SEC-002
31
+ name: "Native C library is vendored into the Rust artifact"
32
+ description: "The resolver enabled a vendoring feature, so the library's own advisories and license obligations travel with the binary rather than with the host"
33
+ severity: low
34
+ category: cargo-security
35
+ dry-run-support: full
36
+ condition: |
37
+ components[
38
+ $prop($, 'cdx:cargo:linkage') = 'static'
39
+ and $exists(version)
40
+ and version != ''
41
+ ]
42
+ location: |
43
+ { "bomRef": $."bom-ref", "purl": purl }
44
+ message: "Native library '{{ name }}' version '{{ version }}' is compiled into the artifact by '{{ $prop($, 'cdx:cargo:providedBy') }}'"
45
+ mitigation: "Track this library's advisories directly; the cargo dependency graph will not report them. Confirm that updating the -sys crate is enough to pick up a library fix, and that the vendored build is rebuilt when the library is patched"
46
+ evidence: |
47
+ {
48
+ "nativeLibrary": $prop($, 'cdx:cargo:nativeLibrary'),
49
+ "providedBy": $prop($, 'cdx:cargo:providedBy'),
50
+ "linkageEvidence": $prop($, 'cdx:cargo:linkageEvidence'),
51
+ "upstreamVersionSource": $prop($, 'cdx:cargo:upstreamVersionSource')
52
+ }
53
+
54
+ - id: CARGO-SEC-003
55
+ name: "Rust artifact links a native library the environment must provide"
56
+ description: "A -sys crate links a C library dynamically, so the deployed artifact depends on a library version this BOM cannot state"
57
+ severity: low
58
+ category: cargo-security
59
+ dry-run-support: full
60
+ condition: |
61
+ components[$prop($, 'cdx:cargo:linkage') = 'dynamic']
62
+ location: |
63
+ { "bomRef": $."bom-ref", "purl": purl }
64
+ message: "Native library '{{ name }}' is linked dynamically by '{{ $prop($, 'cdx:cargo:providedBy') }}' and has to be present and patched on the deployment host"
65
+ mitigation: "Record the library version the deployment environment provides, through a container or host inventory. Where the artifact must be self-contained, enable the crate's vendoring feature so the version becomes part of this BOM"
66
+ evidence: |
67
+ {
68
+ "nativeLibrary": $prop($, 'cdx:cargo:nativeLibrary'),
69
+ "providedBy": $prop($, 'cdx:cargo:providedBy')
70
+ }
71
+
72
+ - id: CARGO-COMP-001
73
+ name: "Vendored library version is ambiguous between shipped variants"
74
+ description: "The crate ships several variants of the library and the enabled features do not say which one is compiled, so the version was left unstated"
75
+ severity: medium
76
+ category: cargo-compliance
77
+ dry-run-support: full
78
+ condition: |
79
+ components[
80
+ $prop($, 'cdx:cargo:upstreamVersionCandidates') != null
81
+ ]
82
+ location: |
83
+ { "bomRef": $."bom-ref", "purl": purl }
84
+ message: "Native library '{{ name }}' could be any of '{{ $prop($, 'cdx:cargo:upstreamVersionCandidates') }}' depending on which variant '{{ $prop($, 'cdx:cargo:providedBy') }}' compiles"
85
+ mitigation: "Read the crate's build script to see which variant the enabled features select, then record the version. The variants can carry different licenses, so a license review cannot be completed from this BOM alone"
86
+ evidence: |
87
+ {
88
+ "versionCandidates": $prop($, 'cdx:cargo:upstreamVersionCandidates'),
89
+ "providedBy": $prop($, 'cdx:cargo:providedBy'),
90
+ "resolvedFeatures": $prop($, 'cdx:cargo:resolvedFeatures')
91
+ }
92
+
93
+ - id: CARGO-SEC-004
94
+ name: "Crate resolved from a registry other than crates.io"
95
+ description: "A crate came from an alternative or private registry, which can publish any name and version, including ones that also exist on crates.io"
96
+ severity: medium
97
+ category: cargo-security
98
+ dry-run-support: full
99
+ condition: |
100
+ $append(components, formulation.components)[
101
+ $prop($, 'cdx:cargo:alternateRegistry') = 'true'
102
+ ]
103
+ location: |
104
+ { "bomRef": $."bom-ref", "purl": purl }
105
+ message: "Crate '{{ name }}' was resolved from '{{ $prop($, 'cdx:cargo:sourceUrl') }}' rather than crates.io"
106
+ mitigation: "Confirm the registry is one your organisation operates and that the crate name is not also claimed on crates.io, which would let a resolver change pull a different crate. Verify the registry authenticates publishers and serves checksums"
107
+ evidence: |
108
+ {
109
+ "sourceKind": $prop($, 'cdx:cargo:sourceKind'),
110
+ "sourceUrl": $prop($, 'cdx:cargo:sourceUrl')
111
+ }
112
+
113
+ - id: CARGO-SEC-005
114
+ name: "Procedural macro crate executes during compilation"
115
+ description: "A proc-macro crate runs arbitrary code in the compiler on the build host, before any of the project's own code runs"
116
+ severity: low
117
+ category: cargo-security
118
+ dry-run-support: full
119
+ condition: |
120
+ $append(components, formulation.components)[
121
+ $prop($, 'cdx:cargo:procMacro') = 'true'
122
+ ]
123
+ location: |
124
+ { "bomRef": $."bom-ref", "purl": purl }
125
+ message: "Crate '{{ name }}' is a procedural macro and runs in the compiler on every build"
126
+ mitigation: "Treat this crate's compromise as equivalent to build-host compromise. Review its publisher and release history, pin it, and confirm the build host has no credentials a compile-time macro should not reach"
127
+ evidence: |
128
+ {
129
+ "dependencyKind": $prop($, 'cdx:cargo:dependencyKind'),
130
+ "publisher": $prop($, 'cdx:cargo:publisher'),
131
+ "resolvedFeatures": $prop($, 'cdx:cargo:resolvedFeatures')
132
+ }
@@ -386,3 +386,125 @@
386
386
  "configuredServiceCount": $prop($, 'cdx:mcp:configuredServiceCount'),
387
387
  "configuredServiceNames": $prop($, 'cdx:mcp:configuredServiceNames')
388
388
  }
389
+
390
+ - id: MCP-009
391
+ name: "MCP configuration relies on deprecated protocol features"
392
+ description: "The MCP 2026-07-28 specification deprecates the legacy HTTP+SSE transport, dynamic client registration, sampling, logging, and roots. Configurations that still depend on these features need a migration plan before the deprecation window closes."
393
+ severity: medium
394
+ category: mcp-server
395
+ dry-run-support: full
396
+ standards:
397
+ owasp-ai-top-10:
398
+ - "LLM05: Supply Chain Vulnerabilities"
399
+ - "LLM03:2025 Supply Chain"
400
+ owasp-agentic-top-10-2026:
401
+ - "Agentic Supply Chain"
402
+ eu-ai-act:
403
+ - "Technical documentation and traceability"
404
+ eu-cra:
405
+ - "Technical documentation for software components"
406
+ nist-ai-rmf:
407
+ - "Govern"
408
+ - "Map"
409
+ nist-ssdf:
410
+ - "Plan migration away from deprecated protocol features"
411
+ condition: |
412
+ $auditServices($)[
413
+ $nullSafeProp($, 'cdx:mcp:deprecatedFeatures') != ''
414
+ ]
415
+ location: |
416
+ {
417
+ "bomRef": $."bom-ref",
418
+ "file": $prop($, 'internal:SrcFile')
419
+ }
420
+ message: "MCP service '{{ name }}' uses deprecated protocol features"
421
+ mitigation: "Migrate off legacy SSE transport, dynamic client registration, sampling, logging, and roots to the MCP 2026-07-28 replacements within the deprecation window."
422
+ evidence: |
423
+ {
424
+ "protocolVersion": $prop($, 'cdx:mcp:protocolVersion'),
425
+ "deprecatedFeatures": $prop($, 'cdx:mcp:deprecatedFeatures'),
426
+ "transport": $prop($, 'cdx:mcp:transport')
427
+ }
428
+
429
+ - id: MCP-010
430
+ name: "MCP bundle declares secret configuration fields"
431
+ description: "MCPB/DXT bundles that declare secret-typed user_config fields have those values injected into the bundled server's environment on launch. Declaring such a field is the correct way to build a bundle, so this is an inventory disclosure to aid credential-handling review rather than a defect."
432
+ severity: low
433
+ category: mcp-server
434
+ dry-run-support: full
435
+ standards:
436
+ owasp-ai-top-10:
437
+ - "LLM05: Supply Chain Vulnerabilities"
438
+ - "LLM03:2025 Supply Chain"
439
+ owasp-agentic-top-10-2026:
440
+ - "Agentic Supply Chain"
441
+ eu-ai-act:
442
+ - "Technical documentation and traceability"
443
+ eu-cra:
444
+ - "Software supply chain visibility"
445
+ nist-ai-rmf:
446
+ - "Govern"
447
+ nist-ssdf:
448
+ - "Review secret handling in installable AI extensions"
449
+ condition: |
450
+ components[
451
+ $prop($, 'cdx:file:kind') = 'mcp-bundle'
452
+ and $prop($, 'cdx:mcpb:injectsSecretEnv') = 'true'
453
+ ]
454
+ location: |
455
+ {
456
+ "bomRef": $."bom-ref",
457
+ "file": $prop($, 'internal:SrcFile')
458
+ }
459
+ message: "MCP bundle '{{ name }}' declares secret configuration fields injected as environment variables"
460
+ mitigation: "Review the declared secret fields and prefer runtime secret stores or references over values captured through the extension manifest."
461
+ evidence: |
462
+ {
463
+ "serverType": $prop($, 'cdx:mcpb:serverType'),
464
+ "secretFieldCount": $prop($, 'cdx:mcpb:secretFieldCount'),
465
+ "platformBinaryCount": $prop($, 'cdx:mcpb:platformBinaryCount')
466
+ }
467
+
468
+ - id: MCP-011
469
+ name: "MCP bundle ships precompiled platform binaries"
470
+ description: "MCPB/DXT bundles that ship precompiled per-platform binaries execute vendor-provided native code on the host with no build-from-source step. These binaries deserve provenance and signing review before a bundle is installed."
471
+ severity: medium
472
+ category: mcp-server
473
+ dry-run-support: full
474
+ attack:
475
+ tactics: [TA0002]
476
+ techniques: [T1195.002]
477
+ standards:
478
+ owasp-ai-top-10:
479
+ - "LLM05: Supply Chain Vulnerabilities"
480
+ - "LLM03:2025 Supply Chain"
481
+ owasp-agentic-top-10-2026:
482
+ - "Agentic Supply Chain"
483
+ eu-ai-act:
484
+ - "Technical documentation and traceability"
485
+ - "Accuracy, robustness and cybersecurity"
486
+ eu-cra:
487
+ - "Software supply chain visibility"
488
+ - "Protection against untrusted code execution"
489
+ nist-ai-rmf:
490
+ - "Map"
491
+ - "Manage"
492
+ nist-ssdf:
493
+ - "Verify provenance of precompiled binaries before execution"
494
+ condition: |
495
+ components[
496
+ $prop($, 'cdx:file:kind') = 'mcp-bundle'
497
+ and $number($nullSafeProp($, 'cdx:mcpb:platformBinaryCount')) > 0
498
+ ]
499
+ location: |
500
+ {
501
+ "bomRef": $."bom-ref",
502
+ "file": $prop($, 'internal:SrcFile')
503
+ }
504
+ message: "MCP bundle '{{ name }}' ships precompiled platform binaries"
505
+ mitigation: "Verify the provenance and signatures of the bundled platform binaries, or prefer a bundle that builds from source, before installing."
506
+ evidence: |
507
+ {
508
+ "serverType": $prop($, 'cdx:mcpb:serverType'),
509
+ "platformBinaryCount": $prop($, 'cdx:mcpb:platformBinaryCount')
510
+ }
@@ -250,7 +250,7 @@
250
250
  and $prop($, 'cdx:cargo:hasNativeBuild') = 'true'
251
251
  and $count($$.components[
252
252
  $prop($, 'cdx:github:action:ecosystem') = 'cargo'
253
- and $contains($prop($, 'cdx:github:action:role'), 'toolchain')
253
+ and $contains($nullSafeProp($, 'cdx:github:action:role'), 'toolchain')
254
254
  and $prop($, 'cdx:github:action:versionPinningType') != 'sha'
255
255
  ]) > 0
256
256
  ]
@@ -267,7 +267,7 @@
267
267
  "buildScriptCapabilities": $prop($, 'cdx:cargo:buildScriptCapabilities'),
268
268
  "toolchainActions": $$.components[
269
269
  $prop($, 'cdx:github:action:ecosystem') = 'cargo'
270
- and $contains($prop($, 'cdx:github:action:role'), 'toolchain')
270
+ and $contains($nullSafeProp($, 'cdx:github:action:role'), 'toolchain')
271
271
  and $prop($, 'cdx:github:action:versionPinningType') != 'sha'
272
272
  ].$prop($, 'cdx:github:action:uses')
273
273
  }
@@ -283,17 +283,17 @@
283
283
  $prop($, 'cdx:rust:buildTool') = 'cargo'
284
284
  and $prop($, 'cdx:cargo:hasNativeBuild') = 'true'
285
285
  and (
286
- $contains($prop($, 'cdx:cargo:buildScriptCapabilities'), 'process-execution')
287
- or $contains($prop($, 'cdx:cargo:buildScriptCapabilities'), 'network-access')
288
- or $contains($prop($, 'cdx:cargo:nativeBuildIndicators'), '-sys')
286
+ $contains($nullSafeProp($, 'cdx:cargo:buildScriptCapabilities'), 'process-execution')
287
+ or $contains($nullSafeProp($, 'cdx:cargo:buildScriptCapabilities'), 'network-access')
288
+ or $contains($nullSafeProp($, 'cdx:cargo:nativeBuildIndicators'), '-sys')
289
289
  )
290
290
  and $count($$.components[
291
291
  $prop($, 'cdx:github:step:usesCargo') = 'true'
292
292
  and (
293
- $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'build')
294
- or $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'test')
295
- or $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'package')
296
- or $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'publish')
293
+ $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'build')
294
+ or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'test')
295
+ or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'package')
296
+ or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'publish')
297
297
  )
298
298
  ]) > 0
299
299
  ]
@@ -311,10 +311,10 @@
311
311
  "cargoCommands": $$.components[
312
312
  $prop($, 'cdx:github:step:usesCargo') = 'true'
313
313
  and (
314
- $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'build')
315
- or $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'test')
316
- or $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'package')
317
- or $contains($prop($, 'cdx:github:step:cargoSubcommands'), 'publish')
314
+ $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'build')
315
+ or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'test')
316
+ or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'package')
317
+ or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'publish')
318
318
  )
319
319
  ].$prop($, 'cdx:github:step:command')
320
320
  }
@@ -6,9 +6,11 @@ import process from "node:process";
6
6
  import { createBom } from "../cli/index.js";
7
7
  import {
8
8
  isDryRun,
9
+ isSecureMode,
9
10
  readEnvironmentVariable,
10
11
  recordActivity,
11
12
  } from "../core/activity.js";
13
+ import { getRemediationCatalog } from "../core/buildLedger.js";
12
14
  import {
13
15
  getTmpDir,
14
16
  safeExistsSync,
@@ -53,6 +55,12 @@ import {
53
55
  isHbomLikeBom,
54
56
  isObomLikeBom,
55
57
  } from "../stages/postgen/auditBom.js";
58
+ import {
59
+ isIntrospectionEnabled,
60
+ reflectOnRun,
61
+ } from "../stages/postgen/introspection/reflect.js";
62
+ import { buildIntrospectionJson } from "../stages/postgen/introspection/report.js";
63
+ import { scoreReflection } from "../stages/postgen/introspection/score.js";
56
64
  import { postProcess } from "../stages/postgen/postgen.js";
57
65
  import { formatTargetLabel } from "./progress.js";
58
66
  import { renderAuditReport } from "./reporters.js";
@@ -259,7 +267,7 @@ export async function runDirectBomAuditFromBoms(inputBoms, options = {}) {
259
267
  ...directAuditOptions,
260
268
  getBomWithOras,
261
269
  });
262
- results.push({
270
+ const result = {
263
271
  auditOptions: directAuditOptions,
264
272
  bomFormat: getCycloneDxFormat(inputBom.bomJson),
265
273
  findings,
@@ -268,7 +276,43 @@ export async function runDirectBomAuditFromBoms(inputBoms, options = {}) {
268
276
  specVersion: inputBom.bomJson?.specVersion,
269
277
  status: "audited",
270
278
  summary: summarizeDirectAuditFindings(findings),
271
- });
279
+ };
280
+ if (isIntrospectionEnabled(options)) {
281
+ // The audited BOM was not generated by this run, so there is no ledger
282
+ // of its own; the verdict rests on BOM structure alone and says so.
283
+ try {
284
+ const reflection = await reflectOnRun(inputBom.bomJson, options, {
285
+ ledgerEvents: [],
286
+ ledgerSource: "none",
287
+ projectPath: inputBom.source || "",
288
+ });
289
+ reflection.scoring = scoreReflection(
290
+ reflection,
291
+ getRemediationCatalog(),
292
+ {
293
+ secureMode: isSecureMode,
294
+ inContainer:
295
+ readEnvironmentVariable("CDXGEN_IN_CONTAINER") === "true",
296
+ },
297
+ );
298
+ reflection.bom = {
299
+ serialNumber: inputBom.bomJson?.serialNumber,
300
+ componentCount: Array.isArray(inputBom.bomJson?.components)
301
+ ? inputBom.bomJson.components.length
302
+ : 0,
303
+ };
304
+ result.introspection = buildIntrospectionJson(
305
+ reflection,
306
+ reflection.scoring,
307
+ options,
308
+ );
309
+ } catch (err) {
310
+ console.warn(
311
+ `Build introspection failed to complete and was skipped: ${err?.message}`,
312
+ );
313
+ }
314
+ }
315
+ results.push(result);
272
316
  }
273
317
  const summary = {
274
318
  findingsBySeverity: {
@@ -1744,7 +1788,7 @@ async function recheckFindingsAgainstDefaultBranch(
1744
1788
  if (!bomNSData?.bomJson) {
1745
1789
  return undefined;
1746
1790
  }
1747
- const processedBomNSData = postProcess(
1791
+ const processedBomNSData = await postProcess(
1748
1792
  bomNSData,
1749
1793
  childOptions,
1750
1794
  defaultSourceSelection.scanDir,
@@ -1948,7 +1992,7 @@ export async function auditTarget(target, options) {
1948
1992
  options,
1949
1993
  );
1950
1994
  }
1951
- const processedBomNSData = postProcess(
1995
+ const processedBomNSData = await postProcess(
1952
1996
  bomNSData,
1953
1997
  childOptions,
1954
1998
  sourceSelection.scanDir,
@@ -1,17 +1,11 @@
1
1
  import { thoughtLog } from "../core/logger.js";
2
+ import { SEVERITY_ORDER } from "../core/severity.js";
2
3
  import {
3
4
  hasRegistryProvenanceEvidenceProperties,
4
5
  hasTrustedPublishingProperties,
5
6
  } from "../inventory/provenanceUtils.js";
6
7
 
7
- /** Map of severity names to numeric ordering used for threshold comparisons. */
8
- export const SEVERITY_ORDER = {
9
- none: -1,
10
- low: 0,
11
- medium: 1,
12
- high: 2,
13
- critical: 3,
14
- };
8
+ export { SEVERITY_ORDER };
15
9
 
16
10
  const BASE_FINDING_WEIGHT = {
17
11
  low: 4,
@@ -465,8 +465,13 @@ export async function enrichInputBomsWithRegistryMetadata(inputBoms) {
465
465
  process.env.CDXGEN_FETCH_PKG_METADATA = "true";
466
466
  try {
467
467
  const enrichedCandidates = [
468
+ // The audit scores publisher drift, ownership and release cadence, none
469
+ // of which the local Cargo registry records: it knows when a version was
470
+ // published but not by whom. This path pays the registry's rate limit
471
+ // deliberately rather than audit against metadata that cannot answer the
472
+ // questions the rules ask.
468
473
  ...(cargoCandidates.length
469
- ? await getCratesMetadata(cargoCandidates)
474
+ ? await getCratesMetadata(cargoCandidates, { preferLocalCache: false })
470
475
  : []),
471
476
  ...(npmCandidates.length ? await getNpmMetadata(npmCandidates) : []),
472
477
  ...(pypiCandidates.length
@@ -200,6 +200,8 @@ export const determineParentComponent = (options) => {
200
200
  * @param {object|undefined} detectedParent Parent the generator derived
201
201
  * @param {object[]} components The component list, appended to in place
202
202
  * @param {object[]} dependencies Edges the generator produced
203
+ * @param {object} options CLI options
204
+ * @param {string} ptype Package type
203
205
  * @returns {object[]} Edges to emit, re-anchored when an override applies
204
206
  */
205
207
  function anchorDetectedParent(
@@ -207,6 +209,8 @@ function anchorDetectedParent(
207
209
  detectedParent,
208
210
  components,
209
211
  dependencies,
212
+ options,
213
+ ptype,
210
214
  ) {
211
215
  const detectedRef = detectedParent?.["bom-ref"];
212
216
  const overrideRef = overrideParent?.["bom-ref"];
@@ -225,7 +229,20 @@ function anchorDetectedParent(
225
229
  // `metadata.component` is cleaned of. As a regular component it must go
226
230
  // through the same conversion or schema validation rejects the document
227
231
  // (issue #4320). The copy keeps the caller's context object untouched.
228
- components.push(cleanParentComponent({ ...detectedParent }));
232
+ //
233
+ // Its workspace members go through `listComponents`, the same conversion
234
+ // `addMetadata` gives them when the detected parent is the one reaching
235
+ // `metadata.component`. Without it they keep the raw parser shape
236
+ // (`homepage: { url }`) that the nested Component schema rejects
237
+ // (issue #4326).
238
+ components.push(
239
+ cleanParentComponent({
240
+ ...detectedParent,
241
+ components: detectedParent.components?.length
242
+ ? listComponents(options, {}, detectedParent.components, ptype)
243
+ : undefined,
244
+ }),
245
+ );
229
246
  }
230
247
  // The caller owns the array it passed, and postProcess merges these edges
231
248
  // across generators, so the re-anchoring is returned rather than spliced in.
@@ -1080,7 +1097,19 @@ export function addComponent(
1080
1097
  component.modelCard = pkg.modelCard || undefined;
1081
1098
  component.data = pkg.data || undefined;
1082
1099
  }
1100
+ if (options.specVersion >= 1.7) {
1101
+ // An external component is provided by the deployment environment rather
1102
+ // than bundled with the product. The schema defaults it to false, so it
1103
+ // is only worth emitting when true.
1104
+ component.isExternal = pkg.isExternal ? true : undefined;
1105
+ }
1083
1106
  component["type"] = determinePackageType(pkg);
1107
+ // `version` is optional in every spec version cdxgen emits. An empty string
1108
+ // is not a version, and a consumer reading one has to guess whether it means
1109
+ // "unknown" or "the empty release"; leaving the field out says it plainly.
1110
+ if (!component.version) {
1111
+ delete component.version;
1112
+ }
1084
1113
  if (pkg["bom-ref"]) {
1085
1114
  component["bom-ref"] = pkg["bom-ref"];
1086
1115
  } else if (purlString) {
@@ -1446,6 +1475,8 @@ export const buildBomNSData = (options, pkgInfo, ptype, context) => {
1446
1475
  context.parentComponent,
1447
1476
  components,
1448
1477
  context.dependencies || [],
1478
+ options,
1479
+ ptype,
1449
1480
  );
1450
1481
  if (
1451
1482
  components &&
@@ -88,6 +88,21 @@ const POST_BUILD_SUPPORTED_PROJECT_TYPES = new Set([
88
88
  // Helpers
89
89
  // ---------------------------------------------------------------------------
90
90
 
91
+ /**
92
+ * The formulation privacy notice, shared by every path that turns formulation
93
+ * collection on automatically: the warning about emails and secrets is
94
+ * identical whichever caller adds the section.
95
+ *
96
+ * @returns {OptionsWarning} The notice.
97
+ */
98
+ function formulationPrivacyNotice() {
99
+ return {
100
+ level: "info",
101
+ message:
102
+ "NOTE: Automatically collecting formulation information. The section may include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution or LLM training.",
103
+ };
104
+ }
105
+
91
106
  /**
92
107
  * Check whether a value was explicitly provided by the user (i.e., is not
93
108
  * the yargs default). Yargs always populates default values, so we cannot
@@ -175,7 +190,39 @@ function resolveOutputTarget(args, { filePath, isRemoteOrPurl }) {
175
190
  : resolve(join(filePath, args.output));
176
191
  }
177
192
 
178
- export function buildInitialOptions(args, { filePath, isRemoteOrPurl }) {
193
+ /**
194
+ * Resolve the introspection opt-in from the flag and the environment.
195
+ *
196
+ * The parser maps `CDXGEN_INTROSPECT` onto the same boolean the flag sets, so
197
+ * a value outside the documented opt-in vocabulary — an exported but empty
198
+ * variable most of all — arrives indistinguishable from `--no-introspect`.
199
+ * The command line is therefore the authority on refusal, and the environment
200
+ * speaks only through `true` and `1`. An unmentioned flag stays undefined so a
201
+ * profile can still enable it.
202
+ *
203
+ * @param {Object} args Parsed CLI arguments.
204
+ * @param {string[]} argv Raw command-line arguments the parser was given.
205
+ * @returns {boolean|undefined} The requested state, or undefined when nothing asked.
206
+ */
207
+ function resolveIntrospectOption(args, argv) {
208
+ const refused =
209
+ args.introspect === false &&
210
+ argv.some((argument) =>
211
+ ["--no-introspect", "--introspect=false"].includes(argument),
212
+ );
213
+ if (refused) {
214
+ return false;
215
+ }
216
+ return args.introspect === true ||
217
+ ["true", "1"].includes(readEnvironmentVariable("CDXGEN_INTROSPECT"))
218
+ ? true
219
+ : undefined;
220
+ }
221
+
222
+ export function buildInitialOptions(
223
+ args,
224
+ { filePath, isRemoteOrPurl, argv = process.argv },
225
+ ) {
179
226
  const res = Object.assign({}, args, {
180
227
  projectType: args.type,
181
228
  multiProject: args.recurse,
@@ -188,6 +235,10 @@ export function buildInitialOptions(args, { filePath, isRemoteOrPurl }) {
188
235
  include: args.include || args.includeRegex,
189
236
  noIgnore: args.noIgnore,
190
237
  gitAiNotesRef: args.gitAiNotesRef || "refs/notes/ai",
238
+ // Introspection: the flag or the documented environment opt-in. An
239
+ // explicit --no-introspect stays `false` and beats both, while an
240
+ // unmentioned flag stays undefined so a profile can still enable it.
241
+ introspect: resolveIntrospectOption(args, argv),
191
242
  // License normalization and enrichment are on by default; the only escape
192
243
  // hatch is the corresponding environment variable.
193
244
  licenseEnhance:
@@ -318,6 +369,20 @@ export function applyPostConstructionOverrides(options, context) {
318
369
  options.installDeps = false;
319
370
  }
320
371
 
372
+ // --- Introspection CI gate threshold -----------------------------------
373
+ if (options.introspectFailBelow !== undefined) {
374
+ const threshold = Number(options.introspectFailBelow);
375
+ if (!Number.isFinite(threshold) || threshold < 0 || threshold > 100) {
376
+ warnings.push({
377
+ level: "error",
378
+ message:
379
+ "--introspect-fail-below requires a number between 0 and 100, where absent means the gate never fails.",
380
+ });
381
+ } else {
382
+ options.introspectFailBelow = threshold;
383
+ }
384
+ }
385
+
321
386
  // When a standard is requested, the minimum spec version is 1.6
322
387
  if (options.standard) {
323
388
  if (!userSetSpecVersion) {
@@ -381,6 +446,8 @@ export function applyAdvancedOptions(options, context = {}) {
381
446
  const secureMode = context.isSecureMode ?? isSecureMode;
382
447
  const warnings = [];
383
448
  const isHbomOnly = isHbomOnlyProjectTypes(options.projectType);
449
+ const includeFormulationBeforeProfiles = options.includeFormulation === true;
450
+ const introspectExplicitlyDisabled = options.introspect === false;
384
451
 
385
452
  // --- Profile expansion -------------------------------------------------
386
453
  switch (options.profile) {
@@ -412,6 +479,14 @@ export function applyAdvancedOptions(options, context = {}) {
412
479
  case "license-compliance":
413
480
  process.env.FETCH_LICENSE = "true";
414
481
  break;
482
+ case "introspect":
483
+ options.introspect = true;
484
+ options.introspectAnnotate = options.introspectAnnotate !== false;
485
+ // The ledger's own record of what ran; formulation is where a consumer
486
+ // looks for the build environment the SBOM was produced in.
487
+ options.includeFormulation = !isHbomOnly;
488
+ options.evidence = true;
489
+ break;
415
490
  case "ml-tiny":
416
491
  process.env.FETCH_LICENSE = "true";
417
492
  options.deep = false;
@@ -471,17 +546,29 @@ export function applyAdvancedOptions(options, context = {}) {
471
546
  }
472
547
  }
473
548
 
549
+ // An explicit --no-introspect negates the introspect profile's headline
550
+ // capability while keeping the bundle's other effects: the user asked for
551
+ // the profile minus introspection.
552
+ if (options.profile === "introspect" && introspectExplicitlyDisabled) {
553
+ options.introspect = false;
554
+ warnings.push({
555
+ level: "info",
556
+ message:
557
+ "NOTE: Introspection stays off because --no-introspect was passed explicitly; the other introspect profile settings still apply.",
558
+ });
559
+ }
560
+
474
561
  // --- BOM audit → auto-formulation --------------------------------------
475
- if (options.bomAudit) {
562
+ const introspectProfileAddsFormulation =
563
+ options.profile === "introspect" && !includeFormulationBeforeProfiles;
564
+ if (options.bomAudit || introspectProfileAddsFormulation) {
476
565
  if (isHbomOnly) {
477
566
  // HBOM-only: skip formulation
478
567
  } else if (!options.includeFormulation) {
479
- warnings.push({
480
- level: "info",
481
- message:
482
- "NOTE: Automatically collecting formulation information. The section may include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution or LLM training.",
483
- });
568
+ warnings.push(formulationPrivacyNotice());
484
569
  options.includeFormulation = true;
570
+ } else if (introspectProfileAddsFormulation) {
571
+ warnings.push(formulationPrivacyNotice());
485
572
  }
486
573
  }
487
574