@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
@@ -7,10 +7,12 @@ import { Purl } from "@cdxgen/cdx-purl";
7
7
  import {
8
8
  DEBUG_MODE,
9
9
  isDryRun,
10
+ isSecureMode,
10
11
  readEnvironmentVariable,
11
12
  resetActivityContext,
12
13
  setActivityContext,
13
14
  } from "../../core/activity.js";
15
+ import { getRemediationCatalog } from "../../core/buildLedger.js";
14
16
  import { hasAnyProjectType } from "../../core/env.js";
15
17
  import {
16
18
  getTimestamp,
@@ -44,6 +46,23 @@ import { enhanceBom } from "../../inventory/licenseEnhancer.js";
44
46
  import { applyMcpPinningState } from "../../inventory/mcpPinning.js";
45
47
  import { buildReleaseNotesFromGit } from "../../inventory/source.js";
46
48
  import { extractTags, findBomType, textualMetadata } from "./annotator.js";
49
+ import {
50
+ applyFormulationRunIdMarker,
51
+ applyIntrospectionToBom,
52
+ hasIntrospectionVerdict,
53
+ } from "./introspection/annotate.js";
54
+ import { collectCommandFacts } from "./introspection/commandFacts.js";
55
+ import {
56
+ blockRemediationsForDryRun,
57
+ emitIntrospectionReports,
58
+ printIntrospectionSummary,
59
+ } from "./introspection/emit.js";
60
+ import {
61
+ isIntrospectionEnabled,
62
+ reflectOnRun,
63
+ } from "./introspection/reflect.js";
64
+ import { scoreReflection } from "./introspection/score.js";
65
+ import { repairParentDependencyEdge } from "./parentEdge.js";
47
66
  import { sortBomCollections } from "./sortBom.js";
48
67
  import {
49
68
  applyComponentTypeFilter,
@@ -435,6 +454,94 @@ function buildAuditFindingsCitation(bomJson) {
435
454
  });
436
455
  }
437
456
 
457
+ /**
458
+ * Run the build-introspection reflection over the final BOM, score it, write
459
+ * the reports, and carry the verdict inside the BOM as metadata properties and
460
+ * annotations. Introspection is opt-in; when disabled this is a no-op, and
461
+ * when enabled a reflection failure warns instead of failing the completed
462
+ * BOM.
463
+ *
464
+ * The CI gate is decided here but enforced by the CLI after the BOM is
465
+ * written, so a below-threshold score never costs the user their output.
466
+ *
467
+ * @param {Object} bomNSData BOM with namespaces object
468
+ * @param {Object} options CLI options
469
+ * @param {string} [filePath] Source path that was scanned
470
+ *
471
+ * @returns {Promise<Object>} The same bomNSData, with `reflection` (carrying its `scoring`) attached when introspection ran
472
+ */
473
+ async function applyIntrospection(bomNSData, options, filePath) {
474
+ if (!isIntrospectionEnabled(options) || !bomNSData?.bomJson) {
475
+ return bomNSData;
476
+ }
477
+ // Evidence collection re-processes a finished BOM through a fresh wrapper,
478
+ // but that pass carries neither the scanned path nor the generation's
479
+ // ecosystem context: its ledger events belong to the evinse probes and its
480
+ // marker scan finds nothing, so it would grade a project it never saw. The
481
+ // verdict describes how the BOM was generated, so it stays with the pass
482
+ // that generated it — the document itself is what carries that across.
483
+ if (bomNSData.reflection || hasIntrospectionVerdict(bomNSData.bomJson)) {
484
+ return bomNSData;
485
+ }
486
+ try {
487
+ const reflection = await reflectOnRun(bomNSData.bomJson, options, {
488
+ projectPath: filePath || options.filePath || "",
489
+ });
490
+ reflection.scoring = scoreReflection(reflection, getRemediationCatalog(), {
491
+ secureMode: isSecureMode,
492
+ inContainer: readEnvironmentVariable("CDXGEN_IN_CONTAINER") === "true",
493
+ commandFacts: collectCommandFacts(filePath || options.filePath || ""),
494
+ });
495
+ // Report consumers correlate a report with the BOM it describes through
496
+ // these two facts, so they travel on the reflection document itself.
497
+ reflection.bom = {
498
+ serialNumber: bomNSData.bomJson?.serialNumber,
499
+ componentCount: Array.isArray(bomNSData.bomJson?.components)
500
+ ? bomNSData.bomJson.components.length
501
+ : 0,
502
+ };
503
+ bomNSData.reflection = reflection;
504
+ if (isDryRun) {
505
+ blockRemediationsForDryRun(reflection.scoring);
506
+ }
507
+ // A document at a spec version that predates annotations still carries
508
+ // the metadata properties; only the annotations are version-gated.
509
+ const annotationsSupported =
510
+ normalizeCycloneDxSpecVersion(
511
+ options?.specVersion || bomNSData.bomJson?.specVersion,
512
+ ) >= 1.5;
513
+ applyFormulationRunIdMarker(bomNSData.bomJson, reflection.runId);
514
+ applyIntrospectionToBom(bomNSData.bomJson, reflection, reflection.scoring, {
515
+ annotations: annotationsSupported,
516
+ });
517
+ const delivery = emitIntrospectionReports(
518
+ reflection,
519
+ reflection.scoring,
520
+ options,
521
+ );
522
+ printIntrospectionSummary(reflection.scoring, delivery);
523
+ const threshold = options?.introspectFailBelow;
524
+ if (typeof threshold === "number" && Number.isFinite(threshold)) {
525
+ const score = reflection.scoring.overallScore;
526
+ bomNSData.introspectionGate = {
527
+ threshold,
528
+ score,
529
+ passed: score >= threshold,
530
+ };
531
+ }
532
+ if (!reflection.ledgerComplete) {
533
+ console.warn(
534
+ "Build introspection: the ledger is incomplete (buffer truncated or recorder event); treat the verdict with care and re-run with CDXGEN_INTROSPECT_LEDGER set.",
535
+ );
536
+ }
537
+ } catch (err) {
538
+ console.warn(
539
+ `Build introspection failed to complete and was skipped: ${err?.message}`,
540
+ );
541
+ }
542
+ return bomNSData;
543
+ }
544
+
438
545
  /**
439
546
  * Filter and enhance BOM post generation.
440
547
  *
@@ -442,9 +549,9 @@ function buildAuditFindingsCitation(bomJson) {
442
549
  * @param {Object} options CLI options
443
550
  * @param {string} [filePath] Source path used for formulation and metadata context
444
551
  *
445
- * @returns {Object} Modified bomNSData
552
+ * @returns {Promise<Object>} Modified bomNSData
446
553
  */
447
- export function postProcess(bomNSData, options, filePath) {
554
+ export async function postProcess(bomNSData, options, filePath) {
448
555
  let jsonPayload = bomNSData.bomJson;
449
556
  if (
450
557
  typeof bomNSData.bomJson === "string" ||
@@ -458,14 +565,27 @@ export function postProcess(bomNSData, options, filePath) {
458
565
  bomNSData.bomJson = applyMetadata(bomNSData.bomJson, options);
459
566
  bomNSData.bomJson = applyLicenseEnhancement(bomNSData.bomJson, options);
460
567
  bomNSData.bomJson = applyContainerInventoryMetadata(bomNSData.bomJson);
568
+ const buildOnlyComponents = extractBuildOnlyComponents(
569
+ bomNSData.bomJson,
570
+ options,
571
+ );
461
572
  bomNSData.bomJson = applyFormulation(
462
573
  bomNSData.bomJson,
463
574
  options,
464
575
  filePath,
465
- bomNSData.formulationList,
576
+ buildOnlyComponents.length
577
+ ? [...(bomNSData.formulationList || []), ...buildOnlyComponents]
578
+ : bomNSData.formulationList,
579
+ );
580
+ bomNSData.bomJson = applyBuildOnlyFormulation(
581
+ bomNSData.bomJson,
582
+ buildOnlyComponents,
466
583
  );
467
584
  bomNSData.bomJson = applyReleaseNotes(bomNSData.bomJson, options, filePath);
468
585
  bomNSData.bomJson = applyComponentTypeFilter(bomNSData.bomJson, options);
586
+ // Filtering can leave the tree without a way in, so this runs on the final
587
+ // component set and before annotations, which report on reachability.
588
+ bomNSData.bomJson = repairParentDependencyEdge(bomNSData.bomJson);
469
589
  bomNSData.bomJson = applySpecVersionCompatibility(bomNSData.bomJson, options);
470
590
  bomNSData.bomJson = validateTlpClassification(bomNSData.bomJson, options);
471
591
  // Support for automatic annotations
@@ -495,6 +615,9 @@ export function postProcess(bomNSData, options, filePath) {
495
615
  cleanupEnv(options);
496
616
  cleanupTmpDir();
497
617
  sortBomCollections(bomNSData.bomJson);
618
+ // Reflection reads the finished BOM, so it runs after sorting; it returns
619
+ // its verdict alongside the BOM instead of mutating it.
620
+ await applyIntrospection(bomNSData, options, filePath);
498
621
  return bomNSData;
499
622
  }
500
623
 
@@ -851,6 +974,154 @@ function getIdentityTechniques(comp) {
851
974
  return techniques;
852
975
  }
853
976
 
977
+ /**
978
+ * Ensure the components moved out of the assembly have landed in the
979
+ * formulation section.
980
+ *
981
+ * With `--include-formulation` they were handed to the full formulation pass.
982
+ * Without it there is no formulation section, so a minimal one is created for
983
+ * them alone: moving a component out of `components[]` and then dropping it
984
+ * would lose it from the document.
985
+ *
986
+ * @param {Object} bomJson BOM JSON object
987
+ * @param {Object[]} buildOnlyComponents Components removed from `components[]`
988
+ *
989
+ * @returns {Object} The same `bomJson`
990
+ */
991
+ function applyBuildOnlyFormulation(bomJson, buildOnlyComponents) {
992
+ if (!buildOnlyComponents?.length || !bomJson) {
993
+ return bomJson;
994
+ }
995
+ const alreadyPlaced = (bomJson.formulation || []).some((formula) =>
996
+ formula?.components?.some((comp) =>
997
+ buildOnlyComponents.some((moved) => moved["bom-ref"] === comp["bom-ref"]),
998
+ ),
999
+ );
1000
+ if (alreadyPlaced) {
1001
+ return bomJson;
1002
+ }
1003
+ if (!bomJson.formulation) {
1004
+ bomJson.formulation = [];
1005
+ }
1006
+ bomJson.formulation.push({
1007
+ "bom-ref": `${bomJson.metadata?.component?.["bom-ref"] || "cdxgen"}#build-dependencies`,
1008
+ components: buildOnlyComponents,
1009
+ });
1010
+ return bomJson;
1011
+ }
1012
+
1013
+ /**
1014
+ * Report whether a component exists only to produce the build, rather than
1015
+ * being part of the delivered assembly. A cargo build dependency and a
1016
+ * proc-macro crate are both marked with `cdx:cargo:hostOnly`.
1017
+ *
1018
+ * @param {Object} comp Component
1019
+ * @returns {boolean} True for a build-time-only component
1020
+ */
1021
+ function isBuildOnlyComponent(comp) {
1022
+ return !!comp?.properties?.some(
1023
+ (property) =>
1024
+ property.name === "cdx:cargo:hostOnly" && property.value === "true",
1025
+ );
1026
+ }
1027
+
1028
+ /**
1029
+ * Move the build-time-only components out of `components[]`, which describes
1030
+ * the delivered assembly, and hand them to the formulation section, which is
1031
+ * where CycloneDX describes how the assembly was produced.
1032
+ *
1033
+ * Their bom-refs stay valid because the formulation components live in the same
1034
+ * document, so the dependency graph is left intact. `formulation` exists from
1035
+ * CycloneDX 1.5, so an older document keeps them in `components[]` with their
1036
+ * `cdx:cargo:hostOnly` marker rather than losing them.
1037
+ *
1038
+ * @param {Object} bomJson BOM JSON object
1039
+ * @param {Object} options CLI options
1040
+ *
1041
+ * @returns {Object[]} The components removed from `components[]`
1042
+ */
1043
+ export function extractBuildOnlyComponents(bomJson, options) {
1044
+ if (options?.specVersion < 1.5 || !bomJson?.components?.length) {
1045
+ return [];
1046
+ }
1047
+ const buildOnlyComponents = [];
1048
+ const retainedComponents = [];
1049
+ for (const comp of bomJson.components) {
1050
+ if (isBuildOnlyComponent(comp)) {
1051
+ buildOnlyComponents.push(comp);
1052
+ } else {
1053
+ retainedComponents.push(comp);
1054
+ }
1055
+ }
1056
+ if (buildOnlyComponents.length) {
1057
+ bomJson.components = retainedComponents;
1058
+ }
1059
+ return buildOnlyComponents;
1060
+ }
1061
+
1062
+ /**
1063
+ * Report whether a component carries evidence that it is actually used: an
1064
+ * occurrence in the analyzed sources, or a frame in a callstack.
1065
+ *
1066
+ * @param {Object} comp Component
1067
+ * @returns {boolean} True when usage evidence is attached
1068
+ */
1069
+ function hasUsageEvidence(comp) {
1070
+ if (comp?.evidence?.occurrences?.length) {
1071
+ return true;
1072
+ }
1073
+ return !!comp?.evidence?.callstack?.frames?.length;
1074
+ }
1075
+
1076
+ /**
1077
+ * Re-apply the `--required-only` filter once the evidence stage has attached
1078
+ * occurrence and callstack data.
1079
+ *
1080
+ * A manifest can only say that a dependency is optional; the evidence says
1081
+ * whether it is reached. A component the analyzers observed in the sources is
1082
+ * promoted to `required` so it survives the filter, while an optional component
1083
+ * with no observed usage is dropped as before. A component scoped `excluded` -
1084
+ * a dev or test dependency - keeps that scope: an occurrence inside the test
1085
+ * sources is not evidence that it ships.
1086
+ *
1087
+ * @param {Object} bomJson BOM JSON object carrying evidence
1088
+ * @param {Object} options CLI options
1089
+ *
1090
+ * @returns {Object} Filtered BOM JSON
1091
+ */
1092
+ export function applyEvidenceBasedFilter(bomJson, options) {
1093
+ if (!options?.requiredOnly || !bomJson?.components?.length) {
1094
+ return bomJson;
1095
+ }
1096
+ if (!(options.evidence || options.includeCrypto)) {
1097
+ return bomJson;
1098
+ }
1099
+ for (const comp of bomJson.components) {
1100
+ if (!hasUsageEvidence(comp)) {
1101
+ continue;
1102
+ }
1103
+ if (!comp.properties) {
1104
+ comp.properties = [];
1105
+ }
1106
+ const observed = comp.evidence?.occurrences?.length
1107
+ ? "occurrence"
1108
+ : "callstack";
1109
+ if (
1110
+ !comp.properties.some(
1111
+ (property) => property.name === "cdx:evidence:usage",
1112
+ )
1113
+ ) {
1114
+ comp.properties.push({ name: "cdx:evidence:usage", value: observed });
1115
+ }
1116
+ if (comp.scope === "optional") {
1117
+ comp.scope = "required";
1118
+ }
1119
+ }
1120
+ // `evidenceFilterStage` tells filterBom that the evidence is in hand, so the
1121
+ // scope filter it deferred earlier must run now.
1122
+ return filterBom(bomJson, { ...options, evidenceFilterStage: true });
1123
+ }
1124
+
854
1125
  /**
855
1126
  * Filter BOM based on options
856
1127
  *
@@ -870,6 +1141,15 @@ export function filterBom(bomJson, options) {
870
1141
  const allowedComponentTypes = new Set(
871
1142
  normalizeCycloneDxComponentTypeFilter(options?.componentType),
872
1143
  );
1144
+ // Evidence generation runs after this stage, so dropping the optional
1145
+ // components here would deny the analyzers the chance to prove that one is
1146
+ // used. The scope filter is re-applied by applyEvidenceBasedFilter once the
1147
+ // occurrence and callstack evidence is attached.
1148
+ const deferRequiredOnly = !!(
1149
+ options.requiredOnly &&
1150
+ !options.evidenceFilterStage &&
1151
+ (options.evidence || options.includeCrypto)
1152
+ );
873
1153
  for (const comp of bomJson.components) {
874
1154
  if (shouldExcludeInventoryType(comp, options)) {
875
1155
  filtered = true;
@@ -912,6 +1192,7 @@ export function filterBom(bomJson, options) {
912
1192
  }
913
1193
  if (
914
1194
  options.requiredOnly &&
1195
+ !deferRequiredOnly &&
915
1196
  comp.scope &&
916
1197
  ["optional", "excluded"].includes(comp.scope)
917
1198
  ) {
@@ -494,6 +494,23 @@ function registerCdxHelpers(expression, helpers = {}) {
494
494
  expression.registerFunction("formulationComponents", (bomJson) =>
495
495
  getFormulationComponents(bomJson),
496
496
  );
497
+ // Formulation-derived rules need to know whether the formulation section
498
+ // describes the run being reflected (whose probes the ledger already
499
+ // counted) or a foreign BOM's record. The caller that read the evidence
500
+ // passes its origin verdict through the options; without one, rules gated
501
+ // on a specific origin stay silent.
502
+ expression.registerFunction(
503
+ "formulationOrigin",
504
+ () => helpers?.formulationEvidence?.origin || "unknown",
505
+ );
506
+ // The toolchain mismatch a foreign BOM's formulation proves: the recorded
507
+ // version of this runtime's own family differs from the runtime in use, so
508
+ // the record describes another machine. Computed by the caller, which owns
509
+ // the runtime facts.
510
+ expression.registerFunction(
511
+ "formulationRuntimeMismatch",
512
+ () => helpers?.formulationEvidence?.runtimeMismatch,
513
+ );
497
514
  return expression;
498
515
  }
499
516
 
@@ -47,8 +47,44 @@ const RISKY_PRESENCE_VARS = [
47
47
  // SSH_AUTH_SOCK (ends with _SOCK) and __CF_USER_TEXT_ENCODING (ends with _ENCODING)
48
48
  // are NOT flagged as false positives.
49
49
  // `cred` is kept alongside `credential(?:s)?` to also catch short-form names like MY_CRED.
50
+ // `session` must be followed by an id qualifier (SESSION_ID, SESSIONID, SESSION-ID):
51
+ // a bare _SESSION suffix names a session/desktop type (DESKTOP_SESSION, GDM_SESSION),
52
+ // not a secret-bearing session identifier.
50
53
  const CREDENTIAL_VAR_PATTERN =
51
- /_(?:token|key|secret|pass(?:word)?|credential(?:s)?|cred|user|email|auth|session)$/i;
54
+ /_(?:token|key|secret|pass(?:word)?|credential(?:s)?|cred|user|email|auth|session[_-]?id)$/i;
55
+
56
+ // OS/desktop-layer variables whose names match CREDENTIAL_VAR_PATTERN but whose
57
+ // values are public session or identity identifiers, never secrets. Each entry
58
+ // is set automatically by the operating system or desktop environment, so
59
+ // flagging it would warn on nearly every desktop/developer machine:
60
+ // - GNOME_DESKTOP_SESSION_ID: deprecated gnome-session marker, always the
61
+ // literal string "this-is-deprecated" (kept by GNOME ≤ 3.28 and RHEL 8).
62
+ // - KDE_SESSION_ID: numeric KDE session identifier (legacy ksmserver-era KDE).
63
+ // - SHELL_SESSION_ID: UUID assigned to each Konsole (KDE terminal) tab/session.
64
+ // - TERM_SESSION_ID: window/tab/process indices plus a UUID, set by Apple
65
+ // Terminal (documented in /etc/zshrc_Apple_Terminal) and iTerm2.
66
+ // - ITERM_SESSION_ID: iTerm2 per-pane identifier in the same format.
67
+ // - XDG_SESSION_ID: numeric logind audit-session id set by pam_systemd on
68
+ // every systemd login.
69
+ // - SUDO_USER / DOAS_USER: login name of the invoking user, set by sudo/doas.
70
+ // - XDG_ACTIVATION_TOKEN: single-use Wayland xdg-activation focus token set
71
+ // by GNOME/GLib and KDE launchers; only grants a newly launched app the
72
+ // right to focus its own window.
73
+ // Native Windows variables (USERNAME, SESSIONNAME, USERDOMAIN, …) use
74
+ // non-underscored names and cannot match the pattern, so none appear here.
75
+ // SSH_USER_AUTH is deliberately NOT exempted: sshd only sets it with the
76
+ // non-default `ExposeAuthInfo yes` and it points at key-fingerprint data.
77
+ const CREDENTIAL_VAR_EXCEPTIONS = new Set([
78
+ "GNOME_DESKTOP_SESSION_ID",
79
+ "KDE_SESSION_ID",
80
+ "SHELL_SESSION_ID",
81
+ "TERM_SESSION_ID",
82
+ "ITERM_SESSION_ID",
83
+ "XDG_SESSION_ID",
84
+ "SUDO_USER",
85
+ "DOAS_USER",
86
+ "XDG_ACTIVATION_TOKEN",
87
+ ]);
52
88
 
53
89
  // Proxy variables — NO_PROXY is a bypass-list and should not trigger a finding on its own.
54
90
  const PROXY_VARS = ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"];
@@ -283,7 +319,11 @@ export function auditEnvironment(env = process.env, options = {}) {
283
319
 
284
320
  // Credential exposure — detect any env var whose name follows a credential-naming convention.
285
321
  for (const [varName, varValue] of Object.entries(env)) {
286
- if (varValue && CREDENTIAL_VAR_PATTERN.test(varName)) {
322
+ if (
323
+ varValue &&
324
+ CREDENTIAL_VAR_PATTERN.test(varName) &&
325
+ !CREDENTIAL_VAR_EXCEPTIONS.has(varName)
326
+ ) {
287
327
  recordEnvironmentRead(varName, { source: envSource });
288
328
  findings.push({
289
329
  type: "credential-exposure",