@cdxgen/cdxgen 13.1.0 → 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 (186) hide show
  1. package/README.md +8 -7
  2. package/bin/cdxgen.js +16 -3
  3. package/bin/evinse.js +2 -0
  4. package/data/crypto-oid.json +88 -12
  5. package/data/remediations.json +23 -0
  6. package/data/rules/ai-bom.yaml +78 -0
  7. package/data/rules/cargo-rust.yaml +132 -0
  8. package/data/rules/mcp-servers.yaml +122 -0
  9. package/lib/audit/targets.js +6 -1
  10. package/lib/cli/bomAssembly.js +12 -0
  11. package/lib/cli/index.js +192 -1
  12. package/lib/cli/jsBom.js +59 -2
  13. package/lib/cli/jvmBom.js +22 -0
  14. package/lib/cli/managedBom.js +152 -0
  15. package/lib/cli/nativeBom.js +798 -1
  16. package/lib/core/activity.js +120 -2
  17. package/lib/core/env.js +39 -1
  18. package/lib/core/userAgent.js +151 -0
  19. package/lib/ecosystems/cargoCache.js +364 -0
  20. package/lib/ecosystems/caxa.js +13 -5
  21. package/lib/ecosystems/cppEvidence.js +40 -2
  22. package/lib/ecosystems/ecosystems.js +241 -91
  23. package/lib/ecosystems/golem.js +11 -1
  24. package/lib/ecosystems/gradleutils.js +143 -2
  25. package/lib/ecosystems/kosi.js +1256 -0
  26. package/lib/ecosystems/parsers-crystal.js +179 -0
  27. package/lib/ecosystems/parsers-dotnet.js +69 -39
  28. package/lib/ecosystems/parsers-elm.js +816 -0
  29. package/lib/ecosystems/parsers-gleam.js +1 -1
  30. package/lib/ecosystems/parsers-julia.js +268 -0
  31. package/lib/ecosystems/parsers-lua.js +218 -0
  32. package/lib/ecosystems/parsers-misc.js +172 -0
  33. package/lib/ecosystems/parsers-nim.js +221 -0
  34. package/lib/ecosystems/parsers-opam.js +180 -0
  35. package/lib/ecosystems/parsers-perl.js +277 -0
  36. package/lib/ecosystems/parsers-python.js +1 -1
  37. package/lib/ecosystems/parsers-r.js +148 -0
  38. package/lib/ecosystems/parsers-rebar.js +411 -0
  39. package/lib/ecosystems/parsers-rust.js +932 -17
  40. package/lib/ecosystems/parsers-spack.js +204 -0
  41. package/lib/ecosystems/parsers-stack.js +264 -0
  42. package/lib/ecosystems/parsers-terraform.js +194 -0
  43. package/lib/ecosystems/rusi.js +126 -6
  44. package/lib/evinser/evinser.js +111 -1
  45. package/lib/helpers/agenticToolutils.js +925 -0
  46. package/lib/inventory/agentFormulationParser.js +57 -4
  47. package/lib/inventory/aiCollector.js +504 -10
  48. package/lib/inventory/aiInventory.js +12 -0
  49. package/lib/inventory/analyzer.js +488 -30
  50. package/lib/inventory/astWorker.js +6 -0
  51. package/lib/inventory/auditCategories.js +8 -0
  52. package/lib/inventory/cbomutils.js +30 -0
  53. package/lib/inventory/cdxrs.js +7 -2
  54. package/lib/inventory/communityAiConfigParser.js +215 -2
  55. package/lib/inventory/dosai.js +77 -0
  56. package/lib/inventory/envcontext.js +9 -6
  57. package/lib/inventory/evidenceUtils.js +13 -0
  58. package/lib/inventory/fetchBatch.js +74 -2
  59. package/lib/inventory/fetchRate.js +74 -11
  60. package/lib/inventory/forgeEnricher.js +3 -2
  61. package/lib/inventory/jvmToolEnv.js +6 -5
  62. package/lib/inventory/mcpConfigParser.js +166 -2
  63. package/lib/inventory/mcpDiscovery.js +11 -0
  64. package/lib/inventory/mcpServerManifest.js +389 -0
  65. package/lib/inventory/mcpbBundle.js +419 -0
  66. package/lib/inventory/plugins.js +3 -0
  67. package/lib/inventory/protobom.js +54 -88
  68. package/lib/inventory/protobomLoader.js +4 -0
  69. package/lib/inventory/rustFormulationParser.js +1 -1
  70. package/lib/inventory/source.js +10 -4
  71. package/lib/inventory/stylePackageRefs.js +41 -0
  72. package/lib/inventory/svelteUtils.js +524 -0
  73. package/lib/parsers/huggingfaceUtils.js +15 -7
  74. package/lib/parsers/onnx.js +294 -0
  75. package/lib/parsers/safetensors.js +114 -0
  76. package/lib/stages/postgen/introspection/markers.js +29 -6
  77. package/lib/stages/postgen/postgen.js +169 -1
  78. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  79. package/package.json +25 -25
  80. package/types/lib/audit/targets.d.ts.map +1 -1
  81. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  82. package/types/lib/cli/index.d.ts +2 -2
  83. package/types/lib/cli/index.d.ts.map +1 -1
  84. package/types/lib/cli/jsBom.d.ts +23 -0
  85. package/types/lib/cli/jsBom.d.ts.map +1 -1
  86. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  87. package/types/lib/cli/managedBom.d.ts +33 -0
  88. package/types/lib/cli/managedBom.d.ts.map +1 -1
  89. package/types/lib/cli/nativeBom.d.ts +88 -8
  90. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  91. package/types/lib/core/activity.d.ts.map +1 -1
  92. package/types/lib/core/env.d.ts +21 -0
  93. package/types/lib/core/env.d.ts.map +1 -1
  94. package/types/lib/core/userAgent.d.ts +56 -0
  95. package/types/lib/core/userAgent.d.ts.map +1 -0
  96. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  97. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  98. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  99. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  100. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  101. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  102. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  103. package/types/lib/ecosystems/gradleutils.d.ts +20 -0
  104. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  105. package/types/lib/ecosystems/kosi.d.ts +73 -0
  106. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  107. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  108. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  109. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  110. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  111. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  112. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  113. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  114. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  115. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  116. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  117. package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
  118. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  119. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  120. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  121. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  122. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  123. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  124. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  125. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  126. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  127. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  128. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  129. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  130. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  131. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  132. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  133. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  134. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  135. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  136. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  137. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  138. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  139. package/types/lib/evinser/evinser.d.ts +29 -0
  140. package/types/lib/evinser/evinser.d.ts.map +1 -1
  141. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  142. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  143. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  144. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  145. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  146. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  147. package/types/lib/inventory/auditCategories.d.ts +4 -0
  148. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  149. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  150. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  151. package/types/lib/inventory/dosai.d.ts +23 -0
  152. package/types/lib/inventory/dosai.d.ts.map +1 -1
  153. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  154. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  155. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  156. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  157. package/types/lib/inventory/fetchRate.d.ts +9 -6
  158. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  159. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  160. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  161. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  162. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  163. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  164. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  165. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  166. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  167. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  168. package/types/lib/inventory/plugins.d.ts.map +1 -1
  169. package/types/lib/inventory/protobom.d.ts +18 -13
  170. package/types/lib/inventory/protobom.d.ts.map +1 -1
  171. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  172. package/types/lib/inventory/source.d.ts.map +1 -1
  173. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  174. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  175. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  176. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  177. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  178. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  179. package/types/lib/parsers/onnx.d.ts +28 -0
  180. package/types/lib/parsers/onnx.d.ts.map +1 -0
  181. package/types/lib/parsers/safetensors.d.ts +22 -0
  182. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  183. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  184. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  185. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  186. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
@@ -9,11 +9,16 @@ import {
9
9
  safeRmSync,
10
10
  safeSpawnSync,
11
11
  } from "../core/fs.js";
12
+ import { dirNameStr } from "../core/paths.js";
12
13
  import { applyAlgorithmProperties } from "../inventory/cryptoAlgorithmFamily.js";
13
14
  import { resolvePluginBinary } from "../inventory/plugins.js";
14
15
 
15
16
  const RUST_LANGUAGES = new Set(["rust", "rs", "rust-lang"]);
16
17
 
18
+ const RUSI_CRYPTO_OIDS = JSON.parse(
19
+ readFileSync(join(dirNameStr, "data", "crypto-oid.json"), "utf-8"),
20
+ );
21
+
17
22
  /**
18
23
  * Resolves the rusi binary.
19
24
  *
@@ -82,6 +87,75 @@ function createPurlAliasMap(components = []) {
82
87
  return purlAliasMap;
83
88
  }
84
89
 
90
+ /**
91
+ * Path roots that never name a dependency crate.
92
+ */
93
+ const RUST_NON_CRATE_PATH_ROOTS = new Set([
94
+ "crate",
95
+ "self",
96
+ "super",
97
+ "std",
98
+ "core",
99
+ "alloc",
100
+ "proc_macro",
101
+ "test",
102
+ ]);
103
+
104
+ /**
105
+ * Index the cargo components by the identifier a Rust path spells them with.
106
+ *
107
+ * A crate named `cfg-if` is written `cfg_if` in source, and a lock file can
108
+ * carry several versions of the same crate, so a name maps to every matching
109
+ * purl.
110
+ *
111
+ * @param {Object[]} components Components from the BOM
112
+ * @returns {Map<string, string[]>} Crate identifier to purls
113
+ */
114
+ function createCrateNameIndex(components = []) {
115
+ const crateNameIndex = new Map();
116
+ for (const component of components) {
117
+ if (!component?.purl?.startsWith("pkg:cargo/") || !component.name) {
118
+ continue;
119
+ }
120
+ const crateIdentifier = component.name.toLowerCase().replaceAll("-", "_");
121
+ const purls = crateNameIndex.get(crateIdentifier) || [];
122
+ if (!purls.includes(component.purl)) {
123
+ purls.push(component.purl);
124
+ }
125
+ crateNameIndex.set(crateIdentifier, purls);
126
+ }
127
+ return crateNameIndex;
128
+ }
129
+
130
+ /**
131
+ * Resolve the dependency crates a Rust path refers to.
132
+ *
133
+ * rusi reports an import against the package that declares it, so the imported
134
+ * crate has to be recovered from the path itself. This is what turns an
135
+ * `use hex::encode;` into usage evidence for the `hex` component.
136
+ *
137
+ * @param {string} importPath Rust path, such as `hex::encode`
138
+ * @param {Map<string, string[]>} crateNameIndex Index from {@link createCrateNameIndex}
139
+ * @param {string} [ownerPurl] purl of the package that declares the path
140
+ * @returns {string[]} Matching component purls
141
+ */
142
+ function resolveImportedCratePurls(importPath, crateNameIndex, ownerPurl) {
143
+ if (!importPath || !crateNameIndex?.size) {
144
+ return [];
145
+ }
146
+ const pathRoot = String(importPath)
147
+ .replace(/^::/, "")
148
+ .split("::")[0]
149
+ .trim()
150
+ .toLowerCase();
151
+ if (!pathRoot || RUST_NON_CRATE_PATH_ROOTS.has(pathRoot)) {
152
+ return [];
153
+ }
154
+ return (crateNameIndex.get(pathRoot) || []).filter(
155
+ (purl) => purl !== ownerPurl,
156
+ );
157
+ }
158
+
85
159
  function resolveComponentPurl(purl, purlAliasMap) {
86
160
  if (!purl) {
87
161
  return undefined;
@@ -182,19 +256,37 @@ function mergeCryptoComponent(componentsByRef, component, item) {
182
256
  return existing;
183
257
  }
184
258
 
259
+ // The schema requires an oid for algorithm assets, so an algorithm the OID
260
+ // table cannot name is skipped rather than emitted invalid. rusi reports
261
+ // display-cased names ("SHA-256") while the table is lowercase ("sha-256").
262
+ function resolveCryptoAlgorithmMetadata(name) {
263
+ if (!name) {
264
+ return undefined;
265
+ }
266
+ const lowered = String(name).toLowerCase();
267
+ return (
268
+ RUSI_CRYPTO_OIDS[name] ||
269
+ RUSI_CRYPTO_OIDS[lowered] ||
270
+ RUSI_CRYPTO_OIDS[lowered.replace(/[\s_]+/g, "-")]
271
+ );
272
+ }
273
+
185
274
  function cryptoAlgorithmComponent(asset) {
275
+ const algorithmName = asset.algorithm || asset.kind || "unknown";
276
+ const algorithmMetadata = resolveCryptoAlgorithmMetadata(algorithmName);
277
+ if (!algorithmMetadata?.oid) {
278
+ return undefined;
279
+ }
186
280
  const component = {
187
281
  type: "cryptographic-asset",
188
- name: asset.algorithm || asset.kind || "unknown",
189
- "bom-ref": cryptoBomRef(
190
- "algorithm",
191
- asset.algorithm || asset.kind,
192
- asset.provider || "unknown",
193
- ),
282
+ name: algorithmName,
283
+ "bom-ref": cryptoBomRef("algorithm", algorithmName, algorithmMetadata.oid),
194
284
  description:
285
+ algorithmMetadata.description ||
195
286
  "Cryptographic algorithm/component detected by rusi source analysis",
196
287
  cryptoProperties: {
197
288
  assetType: "algorithm",
289
+ oid: algorithmMetadata.oid,
198
290
  algorithmProperties: { primitive: asset.kind || "unknown" },
199
291
  },
200
292
  properties: [],
@@ -361,6 +453,7 @@ function addImportEvidence(
361
453
  purlAliasMap,
362
454
  purlLocationMap,
363
455
  componentPropertiesMap,
456
+ crateNameIndex,
364
457
  ) {
365
458
  for (const importUsage of rusiReport.imports || []) {
366
459
  const purl = resolveComponentPurl(importUsage.purl, purlAliasMap);
@@ -380,6 +473,31 @@ function addImportEvidence(
380
473
  "cdx:rusi:importAlias",
381
474
  importUsage.alias,
382
475
  );
476
+ // The import is also evidence about the crate being imported, which is the
477
+ // component a consumer filters on.
478
+ for (const cratePurl of resolveImportedCratePurls(
479
+ importUsage.path,
480
+ crateNameIndex,
481
+ purl,
482
+ )) {
483
+ addSetValue(
484
+ purlLocationMap,
485
+ cratePurl,
486
+ positionLocation(importUsage.position),
487
+ );
488
+ addPropertyValue(
489
+ componentPropertiesMap,
490
+ cratePurl,
491
+ "cdx:rusi:importedBy",
492
+ purl,
493
+ );
494
+ addPropertyValue(
495
+ componentPropertiesMap,
496
+ cratePurl,
497
+ "cdx:rusi:importPath",
498
+ importUsage.path,
499
+ );
500
+ }
383
501
  }
384
502
  }
385
503
 
@@ -761,6 +879,7 @@ export function analyzeRusiProject(src, options = {}) {
761
879
  */
762
880
  export function collectRusiEvidence(rusiReport = {}, components = []) {
763
881
  const purlAliasMap = createPurlAliasMap(components);
882
+ const crateNameIndex = createCrateNameIndex(components);
764
883
  const purlLocationMap = {};
765
884
  const dataFlowFrames = {};
766
885
  const componentPropertiesMap = {};
@@ -774,6 +893,7 @@ export function collectRusiEvidence(rusiReport = {}, components = []) {
774
893
  purlAliasMap,
775
894
  purlLocationMap,
776
895
  componentPropertiesMap,
896
+ crateNameIndex,
777
897
  );
778
898
  addUsageEvidence(
779
899
  rusiReport,
@@ -25,6 +25,15 @@ import {
25
25
  getGradleCommand,
26
26
  getMavenCommand,
27
27
  } from "../ecosystems/gradleutils.js";
28
+ import {
29
+ analyzeKosiProject,
30
+ collectKosiApiEndpoints,
31
+ collectKosiEvidence,
32
+ collectKosiServices,
33
+ isKosiKotlinLanguage,
34
+ kosiWorkspaceComponent,
35
+ mergeKosiEvidence,
36
+ } from "../ecosystems/kosi.js";
28
37
  import {
29
38
  analyzeRusiProject,
30
39
  collectRusiEvidence,
@@ -42,6 +51,7 @@ import {
42
51
  import { collectMvnDependencies } from "../inventory/deps.js";
43
52
  import { mergeServices } from "../inventory/depsUtils.js";
44
53
  import {
54
+ applyDosaiReachabilityEvidence,
45
55
  collectDosaiAiComponents,
46
56
  collectDosaiDataFlowFrames,
47
57
  collectDosaiPurlEvidence,
@@ -616,8 +626,13 @@ export function initFromSbom(components, language) {
616
626
  });
617
627
  }
618
628
  if (comp.evidence.occurrences) {
629
+ // Keep the `location#line` shape so a re-run over a cdxgen BOM does not
630
+ // degrade seeded occurrences to file-only entries and duplicate the
631
+ // richer locations the fresh slice merge adds to the same set.
619
632
  purlLocationMap[comp.purl] = new Set(
620
- comp.evidence.occurrences.map((v) => v.location),
633
+ comp.evidence.occurrences.map((v) =>
634
+ v.line ? `${v.location}#${v.line}` : v.location,
635
+ ),
621
636
  );
622
637
  }
623
638
  }
@@ -724,6 +739,84 @@ export async function analyzeProject(dbObjMap, options) {
724
739
  };
725
740
  }
726
741
 
742
+ if (isKosiKotlinLanguage(language)) {
743
+ const kosiReports = analyzeKosiProject(dirPath, options);
744
+ if (kosiReports?.report && Object.keys(kosiReports.report).length) {
745
+ // The workspace component: cdxgen's Kotlin BOM carries no project
746
+ // component, so every workspace-anchored slice (reachability, the
747
+ // app's own flows) would have nothing to attach to. createEvinseFile
748
+ // re-reads the BOM from disk, so the candidate rides the artefacts
749
+ // instead of a mutation here — applied when the BOM lacks a project
750
+ // component OR carries one with no purl: every kosi evidence join is
751
+ // keyed by purl, so a purl-less component anchors nothing and is, for
752
+ // this purpose, no anchor at all (found on the sample app: Gradle
753
+ // identity with empty group and version "latest" emits no purl, and
754
+ // the reachableFromRoots/cryptoFlow evidence silently attached to
755
+ // nothing while the dependency-joined kinds landed).
756
+ const metadataComponentHasPurl = Boolean(
757
+ bomJson.metadata?.component?.purl,
758
+ );
759
+ let workspaceComponent;
760
+ if (!metadataComponentHasPurl) {
761
+ // kosi's modules[] holds the project's OWN Gradle/Maven modules
762
+ // (dependencies never appear there), so the project component is
763
+ // its root: the shortest modulePath, ties broken by name so the
764
+ // choice is deterministic. `workspaceMember` is not the selector —
765
+ // it is a String naming the owning workspace member in kosi's
766
+ // schema, empty for a single-module build and then omitted from the
767
+ // JSON entirely, so testing it against `undefined` picked nothing
768
+ // on exactly the single-module projects this fallback exists for.
769
+ const workspaceModule = (kosiReports.report.modules || [])
770
+ .filter((candidate) => candidate?.purl)
771
+ .sort(
772
+ (a, b) =>
773
+ (a.modulePath || "").length - (b.modulePath || "").length ||
774
+ (a.name || "").localeCompare(b.name || ""),
775
+ )[0];
776
+ if (workspaceModule?.purl) {
777
+ workspaceComponent = kosiWorkspaceComponent(workspaceModule);
778
+ }
779
+ }
780
+ const analysisComponents = [
781
+ bomJson.metadata?.component,
782
+ workspaceComponent,
783
+ ...components,
784
+ ].filter(Boolean);
785
+ const kosiEvidence = collectKosiEvidence(
786
+ kosiReports.report,
787
+ analysisComponents,
788
+ );
789
+ mergeKosiEvidence(
790
+ kosiEvidence,
791
+ collectKosiEvidence(
792
+ kosiReports.reachableReport || {},
793
+ analysisComponents,
794
+ ),
795
+ );
796
+ servicesMap = collectKosiServices(kosiReports.report, servicesMap);
797
+ // The INBOUND surface: the routes the app serves, with the
798
+ // authentication kosi found declared on them.
799
+ servicesMap = collectKosiApiEndpoints(kosiReports.report, servicesMap);
800
+ return {
801
+ purlLocationMap: kosiEvidence.purlLocationMap,
802
+ dataFlowFrames: kosiEvidence.dataFlowFrames,
803
+ componentPropertiesMap: kosiEvidence.componentPropertiesMap,
804
+ metadataProperties: kosiEvidence.metadataProperties,
805
+ cryptoComponents: kosiEvidence.cryptoComponents,
806
+ cryptoGeneratePurls: kosiEvidence.cryptoGeneratePurls,
807
+ servicesMap,
808
+ userDefinedTypesMap,
809
+ workspaceComponent,
810
+ };
811
+ }
812
+ return {
813
+ purlLocationMap,
814
+ servicesMap,
815
+ dataFlowFrames,
816
+ userDefinedTypesMap,
817
+ };
818
+ }
819
+
727
820
  if (isDosaiDotnetLanguage(language)) {
728
821
  if (options.profile === "research") {
729
822
  options.withDataFlow = true;
@@ -746,6 +839,10 @@ export async function analyzeProject(dbObjMap, options) {
746
839
  }
747
840
  if (usageSlice && Object.keys(usageSlice).length) {
748
841
  const dosaiEvidence = collectDosaiPurlEvidence(usageSlice, components);
842
+ // Confidence/evidence facts ride along as properties so an unbuilt tree (Low
843
+ // confidence, unresolved evidence) stays distinguishable from an only-imported
844
+ // package in the emitted BOM.
845
+ applyDosaiReachabilityEvidence(usageSlice, components);
749
846
  for (const [purl, locations] of Object.entries(
750
847
  dosaiEvidence.purlLocationMap,
751
848
  )) {
@@ -1833,6 +1930,7 @@ export async function createEvinseFile(sliceArtefacts, options) {
1833
1930
  cryptoComponents,
1834
1931
  aiComponents,
1835
1932
  cryptoGeneratePurls,
1933
+ workspaceComponent,
1836
1934
  } = sliceArtefacts;
1837
1935
  const bomFile = options.input;
1838
1936
  const evinseOutFile = options.output;
@@ -1840,6 +1938,18 @@ export async function createEvinseFile(sliceArtefacts, options) {
1840
1938
  const components = bomJson.components || [];
1841
1939
  // Clear existing annotations
1842
1940
  bomJson.annotations = [];
1941
+ if (workspaceComponent && !bomJson.metadata?.component?.purl) {
1942
+ if (bomJson.metadata?.component) {
1943
+ // A purl-less project component: keep cdxgen's own identity fields
1944
+ // and supply the purl kosi's module discovery published, so the
1945
+ // evidence loop below can anchor workspace evidence to it.
1946
+ bomJson.metadata.component.purl = workspaceComponent.purl;
1947
+ bomJson.metadata.component["bom-ref"] ??= workspaceComponent.purl;
1948
+ } else {
1949
+ bomJson.metadata ??= {};
1950
+ bomJson.metadata.component = workspaceComponent;
1951
+ }
1952
+ }
1843
1953
  appendComponentProperties(bomJson.metadata?.component, metadataProperties);
1844
1954
  let occEvidencePresent = false;
1845
1955
  let csEvidencePresent = false;