@cdxgen/cdxgen 13.6.0 → 13.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/README.md +15 -8
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +256 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +25 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +18 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/pqc-migration.json +239 -0
  15. package/data/remediations.json +52 -0
  16. package/data/rules/cbom-security.yaml +4 -3
  17. package/data/rules/package-integrity.yaml +28 -0
  18. package/lib/audit/index.js +25 -3
  19. package/lib/cli/bomAssembly.js +80 -1
  20. package/lib/cli/cliOptions.js +121 -17
  21. package/lib/cli/index.js +609 -898
  22. package/lib/cli/jsBom.js +3 -0
  23. package/lib/cli/jvmBom.js +365 -40
  24. package/lib/cli/managedBom.js +19 -3
  25. package/lib/cli/nativeBom.js +10 -0
  26. package/lib/core/env.js +26 -0
  27. package/lib/core/fs.js +126 -3
  28. package/lib/core/globPatterns.js +218 -0
  29. package/lib/core/processTree.js +99 -12
  30. package/lib/core/projectConfig.js +332 -25
  31. package/lib/core/toolRegistry.js +15 -1
  32. package/lib/ecosystems/denoutils.js +2 -2
  33. package/lib/ecosystems/golem.js +3 -8
  34. package/lib/ecosystems/gradleutils.js +1082 -6
  35. package/lib/ecosystems/jsEvidence.js +90 -38
  36. package/lib/ecosystems/kosi.js +3 -5
  37. package/lib/ecosystems/npmVersionResolution.js +376 -0
  38. package/lib/ecosystems/parsers-python.js +89 -28
  39. package/lib/ecosystems/parsers-rust.js +117 -15
  40. package/lib/ecosystems/remote/huggingface.js +22 -0
  41. package/lib/ecosystems/rusi.js +2 -4
  42. package/lib/ecosystems/scalasem.js +2 -1
  43. package/lib/evinser/evinser.js +196 -34
  44. package/lib/evinser/scalasem.js +5 -1
  45. package/lib/helpers/agenticToolutils.js +112 -1
  46. package/lib/helpers/bomSigner.js +43 -9
  47. package/lib/helpers/jssSigner.js +1276 -0
  48. package/lib/inventory/agentBlueprint.js +193 -0
  49. package/lib/inventory/agentToolNames.js +145 -0
  50. package/lib/inventory/aiCollector.js +153 -7
  51. package/lib/inventory/aiInventory.js +44 -8
  52. package/lib/inventory/aiProviders.js +324 -0
  53. package/lib/inventory/bomUtils.js +8 -1
  54. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  55. package/lib/inventory/citations.js +31 -2
  56. package/lib/inventory/communityAiConfigParser.js +978 -80
  57. package/lib/inventory/cyclonedx20.js +152 -0
  58. package/lib/inventory/cyclonedx20Common.js +286 -0
  59. package/lib/inventory/cyclonedx20Derived.js +159 -0
  60. package/lib/inventory/cyclonedx20Parties.js +552 -0
  61. package/lib/inventory/cyclonedx20Read.js +768 -0
  62. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  63. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  64. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  65. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  66. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  67. package/lib/inventory/cyclonedx20Render.js +766 -0
  68. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  69. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  70. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  71. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  72. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  73. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  74. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  75. package/lib/inventory/cyclonedx20Tables.js +594 -0
  76. package/lib/inventory/deps.js +106 -13
  77. package/lib/inventory/dosai.js +236 -1
  78. package/lib/inventory/dosaiParsers.js +3 -6
  79. package/lib/inventory/dotnetRuntime.js +164 -14
  80. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  81. package/lib/inventory/envcontext.js +132 -38
  82. package/lib/inventory/evidenceUtils.js +2 -1
  83. package/lib/inventory/formulationParsers.js +95 -13
  84. package/lib/inventory/hbom.js +45 -6
  85. package/lib/inventory/hbomLoader.js +166 -0
  86. package/lib/inventory/mcpConfigParser.js +5 -0
  87. package/lib/inventory/occurrenceKey.js +90 -0
  88. package/lib/inventory/pqcMigration.js +426 -0
  89. package/lib/inventory/rustFormulationParser.js +21 -10
  90. package/lib/inventory/source.js +65 -6
  91. package/lib/parsers/huggingfaceManifest.js +9 -1
  92. package/lib/parsers/huggingfaceUtils.js +18 -0
  93. package/lib/parsers/kiroPermissions.js +166 -0
  94. package/lib/server/server.js +36 -34
  95. package/lib/stages/postgen/postgen.js +41 -1
  96. package/lib/stages/postgen/specVersionCompat.js +76 -0
  97. package/lib/validator/bomValidator.js +8 -1
  98. package/lib/validator/complianceEngine.js +30 -3
  99. package/lib/validator/complianceRules.js +1416 -4
  100. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  101. package/lib/validator/index.js +48 -7
  102. package/lib/validator/reporters/json.js +6 -0
  103. package/package.json +9 -9
  104. package/types/bin/repl.d.ts.map +1 -1
  105. package/types/lib/audit/index.d.ts +6 -2
  106. package/types/lib/audit/index.d.ts.map +1 -1
  107. package/types/lib/cli/bomAssembly.d.ts +16 -0
  108. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  109. package/types/lib/cli/cliOptions.d.ts +6 -0
  110. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  111. package/types/lib/cli/index.d.ts.map +1 -1
  112. package/types/lib/cli/jsBom.d.ts +2 -1
  113. package/types/lib/cli/jsBom.d.ts.map +1 -1
  114. package/types/lib/cli/jvmBom.d.ts +15 -0
  115. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  116. package/types/lib/cli/managedBom.d.ts.map +1 -1
  117. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  118. package/types/lib/core/boundedFile.d.ts +30 -0
  119. package/types/lib/core/boundedFile.d.ts.map +1 -1
  120. package/types/lib/core/env.d.ts +10 -0
  121. package/types/lib/core/env.d.ts.map +1 -1
  122. package/types/lib/core/fs.d.ts +4 -0
  123. package/types/lib/core/fs.d.ts.map +1 -1
  124. package/types/lib/core/globPatterns.d.ts +70 -0
  125. package/types/lib/core/globPatterns.d.ts.map +1 -0
  126. package/types/lib/core/paths.d.ts +9 -0
  127. package/types/lib/core/paths.d.ts.map +1 -1
  128. package/types/lib/core/processTree.d.ts +16 -5
  129. package/types/lib/core/processTree.d.ts.map +1 -1
  130. package/types/lib/core/projectConfig.d.ts +47 -8
  131. package/types/lib/core/projectConfig.d.ts.map +1 -1
  132. package/types/lib/core/toolRegistry.d.ts.map +1 -1
  133. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  134. package/types/lib/ecosystems/gradleutils.d.ts +248 -8
  135. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  136. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  137. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  139. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  140. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  141. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  142. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  143. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  144. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  145. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  146. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  147. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  148. package/types/lib/evinser/evinser.d.ts +61 -9
  149. package/types/lib/evinser/evinser.d.ts.map +1 -1
  150. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  151. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  152. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  153. package/types/lib/helpers/bomSigner.d.ts +20 -3
  154. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  155. package/types/lib/helpers/jssSigner.d.ts +288 -0
  156. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  157. package/types/lib/helpers/vsixutils.d.ts +303 -19
  158. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  159. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  160. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  161. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  162. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  163. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  164. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  165. package/types/lib/inventory/aiProviders.d.ts +85 -0
  166. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  167. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  168. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  169. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  170. package/types/lib/inventory/citations.d.ts +5 -0
  171. package/types/lib/inventory/citations.d.ts.map +1 -1
  172. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  173. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  174. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  175. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  176. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  177. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  178. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  179. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  180. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  181. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  182. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  183. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  184. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  185. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  186. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  187. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  188. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  189. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  190. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  191. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  192. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  193. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  194. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  195. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  196. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  197. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  198. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  199. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  200. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  201. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  202. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  203. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  204. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  205. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  206. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  207. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  208. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  209. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  210. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  211. package/types/lib/inventory/deps.d.ts +35 -2
  212. package/types/lib/inventory/deps.d.ts.map +1 -1
  213. package/types/lib/inventory/dosai.d.ts +19 -0
  214. package/types/lib/inventory/dosai.d.ts.map +1 -1
  215. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  216. package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
  217. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
  218. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  219. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  220. package/types/lib/inventory/envcontext.d.ts +22 -6
  221. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  222. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  224. package/types/lib/inventory/hbom.d.ts +11 -0
  225. package/types/lib/inventory/hbom.d.ts.map +1 -1
  226. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  227. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  228. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  229. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  230. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  231. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  232. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  233. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  234. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  235. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  236. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  237. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  238. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  239. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  240. package/types/lib/inventory/source.d.ts +15 -0
  241. package/types/lib/inventory/source.d.ts.map +1 -1
  242. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  243. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  244. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  245. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  246. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  247. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  248. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  249. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  250. package/types/lib/server/server.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  252. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  253. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  254. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  255. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  256. package/types/lib/validator/bomValidator.d.ts +5 -1
  257. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  258. package/types/lib/validator/complianceEngine.d.ts +4 -4
  259. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  260. package/types/lib/validator/complianceRules.d.ts +14 -2
  261. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  262. package/types/lib/validator/index.d.ts +10 -0
  263. package/types/lib/validator/index.d.ts.map +1 -1
  264. package/types/lib/validator/reporters/json.d.ts.map +1 -1
@@ -17,6 +17,7 @@ import {
17
17
  safeRmSync,
18
18
  safeWriteSync,
19
19
  } from "../core/fs.js";
20
+ import { thoughtLog } from "../core/logger.js";
20
21
  import { isProjectToolingAllowed } from "../core/projectTooling.js";
21
22
  import { hasCppSources } from "../ecosystems/cppEvidence.js";
22
23
  import {
@@ -42,6 +43,7 @@ import {
42
43
  kosiWorkspaceComponent,
43
44
  mergeKosiEvidence,
44
45
  } from "../ecosystems/kosi.js";
46
+ import { keepResolvedNpmVersions } from "../ecosystems/npmVersionResolution.js";
45
47
  import {
46
48
  analyzeRusiProject,
47
49
  collectRusiEvidence,
@@ -71,6 +73,10 @@ import {
71
73
  findCryptoAlgos,
72
74
  } from "../inventory/cbomutils.js";
73
75
  import { cmakeBuildDirCandidates } from "../inventory/cmakeBuildDirs.js";
76
+ import {
77
+ formatReadLossNotice,
78
+ loadBomForAnalysis,
79
+ } from "../inventory/cyclonedx20.js";
74
80
  import { collectMvnDependencies } from "../inventory/deps.js";
75
81
  import { mergeServices } from "../inventory/depsUtils.js";
76
82
  import {
@@ -86,6 +92,7 @@ import {
86
92
  dosaiOmittedSections,
87
93
  dosaiReportFileOmittedSections,
88
94
  dosaiSectionsCdxgenReads,
95
+ dosaiSlicesFileIsFresh,
89
96
  isDosaiDotnetLanguage,
90
97
  normalizeDosaiServiceMap,
91
98
  persistDosaiSemanticsReport,
@@ -93,6 +100,10 @@ import {
93
100
  readDosaiMethodsReport,
94
101
  } from "../inventory/dosai.js";
95
102
  import { parseOccurrenceEvidenceLocation } from "../inventory/evidenceUtils.js";
103
+ import {
104
+ dropSubsumedOccurrenceKeys,
105
+ occurrenceLocationKey,
106
+ } from "../inventory/occurrenceKey.js";
96
107
  import { splitPurlLoose } from "../inventory/purl.js";
97
108
  import { executeOsQuery } from "../managers/binary.js";
98
109
  import { MAX_JSON_TEXT_BYTES, readJsonFile } from "../parsers/largeJson.js";
@@ -298,6 +309,45 @@ export function buildEvinseOptions(options, args, input) {
298
309
  };
299
310
  }
300
311
 
312
+ /**
313
+ * Read the evinse input BOM as cdxgen's 1.x-shaped internal model. A
314
+ * CycloneDX 2.0 document is converted with loadBomForAnalysis; a 1.x document
315
+ * (or an internal model) is returned as is. When `options.inputBomJson` holds
316
+ * a model converted earlier, a copy of it is returned instead of reading
317
+ * `options.input` again, so prepareDB, analyzeProject and createEvinseFile
318
+ * each get their own object, as they did when each parsed the file.
319
+ *
320
+ * @param {Object} options Evinse options with `input` and optional
321
+ * `inputBomJson`
322
+ * @returns {{bomJson: Object, losses: Array<{pointer: string, reason: string}>}}
323
+ * The internal model and the losses of the 2.0 read (none when
324
+ * `inputBomJson` was given)
325
+ */
326
+ export function loadEvinseInputBom(options) {
327
+ if (options?.inputBomJson && typeof options.inputBomJson === "object") {
328
+ return { bomJson: structuredClone(options.inputBomJson), losses: [] };
329
+ }
330
+ const parsedBom = JSON.parse(fs.readFileSync(options.input, "utf8"));
331
+ const { internalBom, losses } = loadBomForAnalysis(parsedBom);
332
+ return { bomJson: internalBom, losses };
333
+ }
334
+
335
+ /**
336
+ * Print the one-line notice for the losses of reading a 2.0 evinse input,
337
+ * with the details in thoughtLog.
338
+ *
339
+ * @param {Array<{pointer: string, reason: string}>} losses Reader losses
340
+ */
341
+ export function reportEvinseInputLosses(losses) {
342
+ if (!losses?.length) {
343
+ return;
344
+ }
345
+ console.log(formatReadLossNotice(losses));
346
+ for (const loss of losses) {
347
+ thoughtLog(`evinse 2.0 loss at ${loss.pointer}: ${loss.reason}`);
348
+ }
349
+ }
350
+
301
351
  /**
302
352
  * Function to create the db for the libraries referred in the sbom.
303
353
  *
@@ -317,7 +367,7 @@ export async function prepareDB(options) {
317
367
  }
318
368
  return;
319
369
  }
320
- const bomJson = JSON.parse(fs.readFileSync(bomJsonFile, "utf8"));
370
+ const { bomJson } = loadEvinseInputBom(options);
321
371
  if (bomJson.specVersion < 1.5) {
322
372
  console.log(
323
373
  "Evinse requires the input SBOM in CycloneDX 1.5 format or above. You can generate one by invoking cdxgen without any --spec-version argument.",
@@ -845,15 +895,18 @@ export function purlToLanguage(purl, filePath) {
845
895
  * Walks the supplied components reading `internal:ImportedModules` (or
846
896
  * `internal:Namespaces` for php/ruby, plus the `internal:ImportedSymbols` the
847
897
  * C/C++ collector records) and `evidence.occurrences` properties to construct
848
- * lookup maps used during slice analysis.
898
+ * lookup maps used during slice analysis. An occurrence that carries `offset`
899
+ * or `symbol` seeds those too, so a re-run over a previous evinse output keys
900
+ * and annotates the same use sites the fresh slice adds.
849
901
  *
850
902
  * @param {Object[]} components CycloneDX components from the input SBOM
851
903
  * @param {string} language Application language used to select the import property name
852
- * @returns {{ purlLocationMap: Object<string, Set>, purlImportsMap: Object<string, string[]> }} Seeded purl location and import maps
904
+ * @returns {{ purlLocationMap: Object<string, Set>, purlImportsMap: Object<string, string[]>, purlSymbolMap: Object<string, Object<string, string>> }} Seeded purl location, import and symbol maps
853
905
  */
854
906
  export function initFromSbom(components, language) {
855
907
  const purlLocationMap = {};
856
908
  const purlImportsMap = {};
909
+ const purlSymbolMap = {};
857
910
  for (const comp of components) {
858
911
  if (!comp?.evidence) {
859
912
  continue;
@@ -886,17 +939,30 @@ export function initFromSbom(components, language) {
886
939
  if (comp.evidence.occurrences) {
887
940
  // Keep the `location#line` shape so a re-run over a cdxgen BOM does not
888
941
  // degrade seeded occurrences to file-only entries and duplicate the
889
- // richer locations the fresh slice merge adds to the same set.
942
+ // richer locations the fresh slice merge adds to the same set. The key
943
+ // function also writes the location with `/`, so a Windows BOM's
944
+ // backslash spelling and a slice's forward-slash spelling of one place
945
+ // are one occurrence.
890
946
  purlLocationMap[comp.purl] = new Set(
891
- comp.evidence.occurrences.map((v) =>
892
- v.line ? `${v.location}#${v.line}` : v.location,
893
- ),
947
+ comp.evidence.occurrences
948
+ .map((v) => occurrenceLocationKey(v.location, v.line, v.offset))
949
+ .filter(Boolean),
894
950
  );
951
+ // The first symbol a key carries wins, so a line two records without a
952
+ // column share keeps one symbol across a re-run over this output.
953
+ const symbols = (purlSymbolMap[comp.purl] ??= {});
954
+ for (const v of comp.evidence.occurrences) {
955
+ const key = occurrenceLocationKey(v.location, v.line, v.offset);
956
+ if (v.symbol && key && symbols[key] === undefined) {
957
+ symbols[key] = v.symbol;
958
+ }
959
+ }
895
960
  }
896
961
  }
897
962
  return {
898
963
  purlLocationMap,
899
964
  purlImportsMap,
965
+ purlSymbolMap,
900
966
  };
901
967
  }
902
968
 
@@ -1250,7 +1316,7 @@ export async function analyzeProject(dbObjMap, options) {
1250
1316
  let retMap = {};
1251
1317
  let userDefinedTypesMap = {};
1252
1318
  const bomFile = options.input;
1253
- const bomJson = JSON.parse(fs.readFileSync(bomFile, "utf8"));
1319
+ const { bomJson } = loadEvinseInputBom(options);
1254
1320
  const components = bomJson.components || [];
1255
1321
  let cryptoComponents = [];
1256
1322
  let aiComponents = [];
@@ -1258,7 +1324,22 @@ export async function analyzeProject(dbObjMap, options) {
1258
1324
  let openapiSpecFile;
1259
1325
  // Load any existing purl-location information from the sbom.
1260
1326
  // For eg: cdxgen populates this information for javascript projects
1261
- let { purlLocationMap, purlImportsMap } = initFromSbom(components, language);
1327
+ let { purlLocationMap, purlImportsMap, purlSymbolMap } = initFromSbom(
1328
+ components,
1329
+ language,
1330
+ );
1331
+ // purls whose input occurrences all belong to another installed version
1332
+ let clearedOccurrencePurls = new Set();
1333
+ // The places the input BOM's own import evidence gives each npm version,
1334
+ // before any slice adds to them: they say which version a file uses.
1335
+ const seededJsLocationMap = JS_LANGUAGES.includes(language)
1336
+ ? Object.fromEntries(
1337
+ Object.entries(purlLocationMap).map(([purl, keys]) => [
1338
+ purl,
1339
+ new Set(keys),
1340
+ ]),
1341
+ )
1342
+ : {};
1262
1343
 
1263
1344
  if (isGolemGoLanguage(language)) {
1264
1345
  const golemReport = analyzeGolemProject(dirPath, options);
@@ -1277,6 +1358,7 @@ export async function analyzeProject(dbObjMap, options) {
1277
1358
  cryptoGeneratePurls: golemEvidence.cryptoGeneratePurls,
1278
1359
  servicesMap,
1279
1360
  userDefinedTypesMap,
1361
+ purlSymbolMap,
1280
1362
  };
1281
1363
  }
1282
1364
  return {
@@ -1284,6 +1366,7 @@ export async function analyzeProject(dbObjMap, options) {
1284
1366
  servicesMap,
1285
1367
  dataFlowFrames,
1286
1368
  userDefinedTypesMap,
1369
+ purlSymbolMap,
1287
1370
  };
1288
1371
  }
1289
1372
 
@@ -1303,6 +1386,7 @@ export async function analyzeProject(dbObjMap, options) {
1303
1386
  cryptoGeneratePurls: rusiEvidence.cryptoGeneratePurls,
1304
1387
  servicesMap,
1305
1388
  userDefinedTypesMap,
1389
+ purlSymbolMap,
1306
1390
  };
1307
1391
  }
1308
1392
  return {
@@ -1310,6 +1394,7 @@ export async function analyzeProject(dbObjMap, options) {
1310
1394
  servicesMap,
1311
1395
  dataFlowFrames,
1312
1396
  userDefinedTypesMap,
1397
+ purlSymbolMap,
1313
1398
  };
1314
1399
  }
1315
1400
 
@@ -1381,6 +1466,7 @@ export async function analyzeProject(dbObjMap, options) {
1381
1466
  servicesMap,
1382
1467
  userDefinedTypesMap,
1383
1468
  workspaceComponent,
1469
+ purlSymbolMap,
1384
1470
  };
1385
1471
  }
1386
1472
  return {
@@ -1388,6 +1474,7 @@ export async function analyzeProject(dbObjMap, options) {
1388
1474
  servicesMap,
1389
1475
  dataFlowFrames,
1390
1476
  userDefinedTypesMap,
1477
+ purlSymbolMap,
1391
1478
  };
1392
1479
  }
1393
1480
 
@@ -1449,6 +1536,17 @@ export async function analyzeProject(dbObjMap, options) {
1449
1536
  `The dosai deps slices file "${options.depsSlicesFile}" omits sections (${omittedSections.join(", ")}); running dosai for the full usages report.`,
1450
1537
  );
1451
1538
  }
1539
+ } else if (
1540
+ !dosaiSlicesFileIsFresh(options.depsSlicesFile, dirPath, options)
1541
+ ) {
1542
+ // A slice older than the sources it describes holds the occurrences
1543
+ // of the sources as they were when it was written, so reusing it
1544
+ // keeps occurrences of call sites that no longer exist (issue 4675).
1545
+ if (DEBUG_MODE) {
1546
+ console.log(
1547
+ `The dosai deps slices file "${options.depsSlicesFile}" is older than the sources it describes; running dosai for a fresh usages report.`,
1548
+ );
1549
+ }
1452
1550
  } else {
1453
1551
  if (DEBUG_MODE) {
1454
1552
  console.log(
@@ -1546,6 +1644,7 @@ export async function analyzeProject(dbObjMap, options) {
1546
1644
  cryptoComponents,
1547
1645
  aiComponents,
1548
1646
  cryptoGeneratePurls,
1647
+ purlSymbolMap,
1549
1648
  };
1550
1649
  }
1551
1650
  // Do reachables first so that usages slicing can reuse the atom file
@@ -1695,8 +1794,21 @@ export async function analyzeProject(dbObjMap, options) {
1695
1794
  }
1696
1795
  }
1697
1796
  if (callsitesSlice?.callsites?.length) {
1698
- attributeJsCallsites(callsitesSlice, purlImportsMap, purlLocationMap);
1797
+ attributeJsCallsites(
1798
+ callsitesSlice,
1799
+ purlImportsMap,
1800
+ purlLocationMap,
1801
+ purlSymbolMap,
1802
+ );
1699
1803
  }
1804
+ // A slice record names a package, not a version: keep each occurrence on
1805
+ // the installed version its file uses.
1806
+ clearedOccurrencePurls = keepResolvedNpmVersions(
1807
+ purlLocationMap,
1808
+ purlSymbolMap,
1809
+ seededJsLocationMap,
1810
+ bomJson,
1811
+ );
1700
1812
  }
1701
1813
  // A component whose calls the slices do not show (code inside the project,
1702
1814
  // such as a vendored library) is still used where its header is included
@@ -1771,6 +1883,8 @@ export async function analyzeProject(dbObjMap, options) {
1771
1883
  reachablesSlicesFile,
1772
1884
  semanticsSlicesFile,
1773
1885
  purlLocationMap,
1886
+ purlSymbolMap,
1887
+ clearedOccurrencePurls,
1774
1888
  servicesMap,
1775
1889
  dataFlowFrames,
1776
1890
  tempDir: retMap?.tempDir,
@@ -1877,11 +1991,7 @@ export function attributeCIncludes(
1877
1991
  continue;
1878
1992
  }
1879
1993
  const known = locations.get(purl) || new Set();
1880
- known.add(
1881
- slice.lineNumber
1882
- ? `${slice.fileName}#${slice.lineNumber}`
1883
- : slice.fileName,
1884
- );
1994
+ known.add(occurrenceLocationKey(slice.fileName, slice.lineNumber));
1885
1995
  locations.set(purl, known);
1886
1996
  }
1887
1997
  return { symbols, locations };
@@ -2284,7 +2394,7 @@ export function parseSemanticSlices(_language, components, semanticsSlice) {
2284
2394
  continue;
2285
2395
  }
2286
2396
  for (const aline of lines) {
2287
- locations.add(`${aswiftFile}#${aline}`);
2397
+ locations.add(occurrenceLocationKey(aswiftFile, aline));
2288
2398
  }
2289
2399
  }
2290
2400
  }
@@ -2294,7 +2404,7 @@ export function parseSemanticSlices(_language, components, semanticsSlice) {
2294
2404
  fileIndexes,
2295
2405
  )) {
2296
2406
  for (const aline of ahit.lineNumbers) {
2297
- locations.add(`${ahit.file}#${aline}`);
2407
+ locations.add(occurrenceLocationKey(ahit.file, aline));
2298
2408
  }
2299
2409
  }
2300
2410
  }
@@ -2958,8 +3068,11 @@ export function indexVersionlessCrypto(cryptoGeneratePurls) {
2958
3068
  * Method to create the SBOM with evidence file called evinse file.
2959
3069
  *
2960
3070
  * @param {Object} sliceArtefacts Various artefacts from the slice operation
2961
- * @param {Object} options Command line options
2962
- * @returns
3071
+ * @param {Object} options Command line options. With `returnInternalModel`,
3072
+ * a CycloneDX 2.0 run returns the 1.x-shaped internal model instead of the
3073
+ * rendered document; the file written is the rendered document either way.
3074
+ * @returns {Promise<Object>} The BOM written to the output file, or its
3075
+ * internal model when `returnInternalModel` is set
2963
3076
  */
2964
3077
  export async function createEvinseFile(sliceArtefacts, options) {
2965
3078
  const {
@@ -2969,6 +3082,8 @@ export async function createEvinseFile(sliceArtefacts, options) {
2969
3082
  dataFlowSlicesFile,
2970
3083
  reachablesSlicesFile,
2971
3084
  purlLocationMap,
3085
+ purlSymbolMap = {},
3086
+ clearedOccurrencePurls,
2972
3087
  servicesMap,
2973
3088
  dataFlowFrames,
2974
3089
  componentPropertiesMap = {},
@@ -2978,9 +3093,13 @@ export async function createEvinseFile(sliceArtefacts, options) {
2978
3093
  cryptoGeneratePurls,
2979
3094
  workspaceComponent,
2980
3095
  } = sliceArtefacts;
2981
- const bomFile = options.input;
2982
3096
  const evinseOutFile = options.output;
2983
- const bomJson = JSON.parse(fs.readFileSync(bomFile, "utf8"));
3097
+ // A 2.0 input is read into the 1.x internal model; everything below works
3098
+ // on the 1.x shape and the final postProcess renders 2.0 again. A caller
3099
+ // that converted the input already (bin/evinse.js) passes the model as
3100
+ // `inputBomJson` and has printed the losses.
3101
+ const { bomJson, losses: bomLosses } = loadEvinseInputBom(options);
3102
+ reportEvinseInputLosses(bomLosses);
2984
3103
  const components = bomJson.components || [];
2985
3104
  // Clear existing annotations
2986
3105
  bomJson.annotations = [];
@@ -3020,19 +3139,40 @@ export async function createEvinseFile(sliceArtefacts, options) {
3020
3139
  }
3021
3140
  delete comp.signature;
3022
3141
  appendComponentProperties(comp, componentPropertiesMap[comp.purl]);
3023
- const locationOccurrences = Array.from(
3024
- purlLocationMap[comp.purl] || [],
3025
- ).sort();
3142
+ // A key with an offset names a use site; the line-only key of the same
3143
+ // line names no place of its own then, so it is dropped rather than
3144
+ // counted beside the use sites (see dropSubsumedOccurrenceKeys).
3145
+ const locationOccurrences = dropSubsumedOccurrenceKeys(
3146
+ Array.from(purlLocationMap[comp.purl] || [])
3147
+ .filter(Boolean)
3148
+ .sort(),
3149
+ );
3026
3150
  if (locationOccurrences.length) {
3027
3151
  if (!comp.evidence) {
3028
3152
  comp.evidence = {};
3029
3153
  }
3030
3154
  // This step would replace any existing occurrences
3031
3155
  // This is fine as long as the input sbom was also generated by cdxgen
3032
- comp.evidence.occurrences = locationOccurrences
3033
- .filter((l) => !!l)
3034
- .map((l) => parseOccurrenceEvidenceLocation(l));
3156
+ // A callsites occurrence carries its column as `offset` and its member
3157
+ // as `symbol`; an occurrence from any other source has neither.
3158
+ const symbols = purlSymbolMap[comp.purl] || {};
3159
+ comp.evidence.occurrences = locationOccurrences.map((l) =>
3160
+ parseOccurrenceEvidenceLocation(
3161
+ l,
3162
+ symbols[l] ? { symbol: symbols[l] } : {},
3163
+ ),
3164
+ );
3035
3165
  occEvidencePresent = true;
3166
+ } else if (
3167
+ clearedOccurrencePurls?.has(comp.purl) &&
3168
+ comp.evidence?.occurrences
3169
+ ) {
3170
+ // The input BOM credited this version with places another installed
3171
+ // version of the package takes.
3172
+ delete comp.evidence.occurrences;
3173
+ if (!Object.keys(comp.evidence).length) {
3174
+ delete comp.evidence;
3175
+ }
3036
3176
  }
3037
3177
  const compKey = versionlessPurlKey(comp.purl);
3038
3178
  let dfFrames = dataFlowFrames[comp.purl];
@@ -3147,9 +3287,10 @@ export async function createEvinseFile(sliceArtefacts, options) {
3147
3287
  // Redo post-processing with evinse data. `executeOsQuery` has to be injected
3148
3288
  // because postgen no longer imports it directly (stages is below managers);
3149
3289
  // without it the crypto formulation path receives `undefined` and throws.
3290
+ const { inputBomJson: _inputBomJson, ...postProcessOptions } = options;
3150
3291
  const bomNSData = await postProcess(
3151
3292
  { bomJson },
3152
- { ...options, executeOsQuery },
3293
+ { ...postProcessOptions, executeOsQuery },
3153
3294
  );
3154
3295
  safeWriteSync(
3155
3296
  evinseOutFile,
@@ -3190,6 +3331,9 @@ export async function createEvinseFile(sliceArtefacts, options) {
3190
3331
  if (tempDirOwned && tempDir?.startsWith(getTmpDir())) {
3191
3332
  safeRmSync(tempDir, { recursive: true, force: true });
3192
3333
  }
3334
+ if (options.returnInternalModel && bomNSData?.internalBomJson) {
3335
+ return bomNSData.internalBomJson;
3336
+ }
3193
3337
  return bomNSData?.bomJson;
3194
3338
  }
3195
3339
 
@@ -3653,7 +3797,7 @@ export function attributeJsModules(
3653
3797
  if (!purlLocationMap[apurl]) {
3654
3798
  purlLocationMap[apurl] = new Set();
3655
3799
  }
3656
- purlLocationMap[apurl].add(`${fileName}#${line}`);
3800
+ purlLocationMap[apurl].add(occurrenceLocationKey(fileName, line));
3657
3801
  }
3658
3802
  };
3659
3803
  const calls = []
@@ -3683,17 +3827,23 @@ export function attributeJsModules(
3683
3827
  * Attribute a callsites slice's records to the packages providing their modules.
3684
3828
  *
3685
3829
  * Each record of `{"callsites": [...]}` (atom `callsites` command) is one external use site
3686
- * carrying its own file and line; the record is an occurrence of every purl whose package
3687
- * matches the record's module.
3830
+ * carrying its own file, line and column; the record is an occurrence of every purl whose
3831
+ * package matches the record's module. A record with a column (0 or more; atom writes -1 for
3832
+ * none) is keyed `file:line:col`, so two use sites on one line are two occurrences, and its
3833
+ * occurrence carries the column as `offset` and `module` joined with `path` by `.` as
3834
+ * `symbol`. A record without a column keys the line (`file#line`) and its occurrence carries
3835
+ * the `symbol` alone; a record without a `path` leaves the symbol at the bare `module`.
3688
3836
  *
3689
3837
  * @param {Object} callsitesSlice callsites slice with a `callsites` array
3690
3838
  * @param {Object} purlImportsMap purl -> imported modules and symbols
3691
- * @param {Object} purlLocationMap purl -> Set of `file#line`
3839
+ * @param {Object} purlLocationMap purl -> Set of occurrence keys
3840
+ * @param {Object} [purlSymbolMap] purl -> occurrence key -> symbol, filled in place
3692
3841
  */
3693
3842
  export function attributeJsCallsites(
3694
3843
  callsitesSlice,
3695
3844
  purlImportsMap,
3696
3845
  purlLocationMap,
3846
+ purlSymbolMap = {},
3697
3847
  ) {
3698
3848
  const byPackage = jsPurlsByPackage(purlImportsMap);
3699
3849
  for (const record of callsitesSlice?.callsites || []) {
@@ -3701,11 +3851,23 @@ export function attributeJsCallsites(
3701
3851
  if (!pkg || !(record.line > 0)) {
3702
3852
  continue;
3703
3853
  }
3854
+ const key = occurrenceLocationKey(record.file, record.line, record.col);
3855
+ const symbol = record.path
3856
+ ? `${record.module}.${record.path}`
3857
+ : record.module;
3704
3858
  for (const apurl of byPackage[pkg] || []) {
3705
3859
  if (!purlLocationMap[apurl]) {
3706
3860
  purlLocationMap[apurl] = new Set();
3707
3861
  }
3708
- purlLocationMap[apurl].add(`${record.file}#${record.line}`);
3862
+ purlLocationMap[apurl].add(key);
3863
+ // The first symbol a key carries wins, so a re-run over a previous
3864
+ // output, which seeds the symbol the first run wrote, stays stable.
3865
+ if (symbol) {
3866
+ const symbols = (purlSymbolMap[apurl] ??= {});
3867
+ if (symbols[key] === undefined) {
3868
+ symbols[key] = symbol;
3869
+ }
3870
+ }
3709
3871
  }
3710
3872
  }
3711
3873
  }
@@ -3714,5 +3876,5 @@ function addToOverrides(lKeyOverrides, atype, fileName, ausageLineNumber) {
3714
3876
  if (!lKeyOverrides[atype]) {
3715
3877
  lKeyOverrides[atype] = new Set();
3716
3878
  }
3717
- lKeyOverrides[atype].add(`${fileName}#${ausageLineNumber}`);
3879
+ lKeyOverrides[atype].add(occurrenceLocationKey(fileName, ausageLineNumber));
3718
3880
  }
@@ -1,3 +1,5 @@
1
+ import { occurrenceLocationKey } from "../inventory/occurrenceKey.js";
2
+
1
3
  function buildNSCache(components) {
2
4
  const typePurlsCache = {};
3
5
  for (const comp of components) {
@@ -52,7 +54,9 @@ export function findPurlLocations(components, semanticsSlice) {
52
54
  if (!purlLocationsSet[apurl]) {
53
55
  purlLocationsSet[apurl] = new Set();
54
56
  }
55
- purlLocationsSet[apurl].add(values.sourceFile || key);
57
+ purlLocationsSet[apurl].add(
58
+ occurrenceLocationKey(values.sourceFile || key),
59
+ );
56
60
  }
57
61
  }
58
62
  }
@@ -10,6 +10,10 @@ import { readUserFileText } from "../core/boundedFile.js";
10
10
  import { safeExistsSync } from "../core/fs.js";
11
11
  import { isMac, isWin, redactHomePath } from "../core/paths.js";
12
12
  import { parseToml } from "../core/toml.js";
13
+ import {
14
+ kiroPermissionSummaryEntries,
15
+ summarizeKiroPermissions,
16
+ } from "../parsers/kiroPermissions.js";
13
17
  import { findIdeProductInfo, IDE_PRODUCTS } from "./vsixutils.js";
14
18
 
15
19
  export { redactHomePath };
@@ -109,6 +113,7 @@ export function getAgenticToolDirs(home = homedir()) {
109
113
  join(home, ".kiro", "sessions", "cli"),
110
114
  ],
111
115
  homeDir: join(home, ".kiro"),
116
+ mcpConfigs: [{ file: join("settings", "mcp.json"), key: "mcpServers" }],
112
117
  supportDirs: isWin
113
118
  ? [join(appData, "kiro-cli")]
114
119
  : isMac
@@ -361,6 +366,28 @@ function parseJsonFile(filePath) {
361
366
  }
362
367
  }
363
368
 
369
+ /**
370
+ * Read a small YAML settings file, such as a Kiro permissions document,
371
+ * through the bounded user-file reader. A file that exists but holds no
372
+ * document, such as an empty or comment-only permissions file, reads as an
373
+ * empty object so its presence still counts.
374
+ *
375
+ * @param {string} filePath Absolute file path
376
+ * @returns {Object|undefined} Parsed document, or undefined when the file is
377
+ * missing, unreadable, or malformed
378
+ */
379
+ function parseYamlFile(filePath) {
380
+ try {
381
+ const raw = readConfigText(filePath);
382
+ if (typeof raw !== "string") {
383
+ return undefined;
384
+ }
385
+ return loadYaml(raw) || {};
386
+ } catch {
387
+ return undefined;
388
+ }
389
+ }
390
+
364
391
  /**
365
392
  * Strip `//` line comments, block comments, and trailing commas from JSONC
366
393
  * text.
@@ -556,6 +583,58 @@ function readToolVersion(tool, dir, supportDir) {
556
583
  return undefined;
557
584
  }
558
585
 
586
+ /**
587
+ * Collect the workspace-scope permission counts under
588
+ * `~/.kiro/workspace-roots/<hash>/permissions.yaml`. Only counts and booleans
589
+ * are returned; the hash-named directories, the file paths, and the rule
590
+ * match patterns stay out of the result.
591
+ *
592
+ * @param {string} dir Kiro home directory
593
+ * @returns {{workspacePermissionFileCount?: number, trustedWorkspaceCount?: number, workspaceWithAllowRuleCount?: number}}
594
+ * Aggregate workspace posture counts
595
+ */
596
+ function countKiroWorkspacePermissions(dir) {
597
+ const rootsDir = join(dir, "workspace-roots");
598
+ if (!safeExistsSync(rootsDir)) {
599
+ return {};
600
+ }
601
+ let entries = [];
602
+ try {
603
+ entries = readdirSync(rootsDir, { withFileTypes: true });
604
+ } catch {
605
+ return {};
606
+ }
607
+ let fileCount = 0;
608
+ let trustedCount = 0;
609
+ let withAllowRuleCount = 0;
610
+ for (const entry of entries) {
611
+ if (!entry.isDirectory()) {
612
+ continue;
613
+ }
614
+ const permissionsPath = join(rootsDir, entry.name, "permissions.yaml");
615
+ const doc = parseYamlFile(permissionsPath);
616
+ if (doc === undefined) {
617
+ continue;
618
+ }
619
+ fileCount += 1;
620
+ const summary = summarizeKiroPermissions(doc);
621
+ if (summary.workspaceTrusted === true) {
622
+ trustedCount += 1;
623
+ }
624
+ if (summary.allowRuleCount > 0) {
625
+ withAllowRuleCount += 1;
626
+ }
627
+ }
628
+ if (!fileCount) {
629
+ return {};
630
+ }
631
+ return {
632
+ trustedWorkspaceCount: trustedCount,
633
+ workspacePermissionFileCount: fileCount,
634
+ workspaceWithAllowRuleCount: withAllowRuleCount,
635
+ };
636
+ }
637
+
559
638
  function countKiroSignals(dir, supportDir) {
560
639
  const signals = {};
561
640
  const agentsDir = join(dir, "agents");
@@ -578,6 +657,17 @@ function countKiroSignals(dir, supportDir) {
578
657
  if (settings && typeof settings["chat.defaultAgent"] === "string") {
579
658
  signals.defaultAgent = settings["chat.defaultAgent"];
580
659
  }
660
+ // The V3 user-scope permission posture from ~/.kiro/settings/permissions.yaml.
661
+ const userPermissions = parseYamlFile(
662
+ join(dir, "settings", "permissions.yaml"),
663
+ );
664
+ if (userPermissions !== undefined) {
665
+ Object.assign(
666
+ signals,
667
+ kiroPermissionSummaryEntries(summarizeKiroPermissions(userPermissions)),
668
+ );
669
+ }
670
+ Object.assign(signals, countKiroWorkspacePermissions(dir));
581
671
  return signals;
582
672
  }
583
673
 
@@ -954,7 +1044,10 @@ function childBomRef(parentRef, kind, id) {
954
1044
  * Parse Kiro agent definitions (`~/.kiro/agents/*.json`) into components.
955
1045
  *
956
1046
  * Each agent contributes its name, model, and redacted counts of MCP servers,
957
- * tools, and allowed tools. Prompts and resource values are never copied.
1047
+ * tools, and allowed tools, plus the permission posture of its V3
1048
+ * `permissions` object and the integer `kiroUpgrade` revision. Prompts,
1049
+ * resource values, match patterns, and the upgrade fingerprint or source are
1050
+ * never copied.
958
1051
  *
959
1052
  * @param {string} dir Kiro home directory
960
1053
  * @param {string} parentRef Parent tool bom-ref
@@ -1010,6 +1103,24 @@ function parseKiroAgents(dir, parentRef, home = homedir()) {
1010
1103
  value: String(agent.allowedTools.length),
1011
1104
  });
1012
1105
  }
1106
+ if (agent.permissions && typeof agent.permissions === "object") {
1107
+ for (const [suffix, value] of Object.entries(
1108
+ kiroPermissionSummaryEntries(
1109
+ summarizeKiroPermissions(agent.permissions),
1110
+ ),
1111
+ )) {
1112
+ properties.push({
1113
+ name: `cdx:agentic:${suffix}`,
1114
+ value: String(value),
1115
+ });
1116
+ }
1117
+ }
1118
+ if (Number.isInteger(agent.kiroUpgrade?.revision)) {
1119
+ properties.push({
1120
+ name: "cdx:agentic:upgradeRevision",
1121
+ value: String(agent.kiroUpgrade.revision),
1122
+ });
1123
+ }
1013
1124
  components.push({
1014
1125
  "bom-ref": childBomRef(parentRef, "agent", agent.name),
1015
1126
  type: "application",