@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
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Declared coding-agent behaviour at CycloneDX 2.0 (cdxgen/cdxgen#4593).
3
+ *
4
+ * The source is the AI inventory the 1.x parsers already produce
5
+ * (communityAiConfigParser: Kiro agents, Claude subagents and agent
6
+ * definitions). Only real agent definitions count: a component with a
7
+ * `cdx:agent:role`, or whose `cdx:file:kind` is `agent-definition` or
8
+ * `claude-subagent`. Instruction files, specs, steering documents, agent
9
+ * configs and plugins describe or configure agents but are not agents.
10
+ * At 2.0 this module derives:
11
+ *
12
+ * - one party per agent component with a bom-ref
13
+ * (#/$defs/cyclonedx-party-2.0/$defs/party, role `agent`) whose `system`
14
+ * (#/$defs/cyclonedx-party-2.0/$defs/system) has kind `agent`, `ref` the
15
+ * agent component's refId, `label` its name and `permissions` the tool
16
+ * names its config allows. The allow list is split on the commas outside
17
+ * parentheses, and each entry is reduced to the tool name before any "("
18
+ * (`Bash(git status:*)` is `Bash`), so arguments, paths and commands
19
+ * never reach the BOM. Names that are not safe tool names are dropped
20
+ * (see lib/inventory/agentToolNames.js). The list is unique and sorted.
21
+ *
22
+ * - one blueprint (#/$defs/cyclonedx-blueprint-2.0/$defs/blueprint,
23
+ * modelTypes `behavioral`) whose actors reference the agent parties and
24
+ * whose behaviors.instances carry `acknowledgment: ["declared"]` and a
25
+ * `behavior` from data/behavior-taxonomy.schema.json:
26
+ * `ai:agent:invokesTool` when the agent declares tools or MCP servers, and
27
+ * `ai:agent:requestsUserApproval` when its permission policy has ask rules.
28
+ * The inventory records only how many MCP servers an agent's own config
29
+ * names, not which, so instances carry no `targets`: naming the project's
30
+ * MCP services would claim a binding no config makes.
31
+ *
32
+ * No free text from instruction, skill or MCP files is emitted. Business
33
+ * objectives and use cases are free text, so they are out of scope, and so
34
+ * is observed behaviour.
35
+ */
36
+
37
+ import { readFileSync } from "node:fs";
38
+ import { join } from "node:path";
39
+
40
+ import { dirNameStr } from "../core/paths.js";
41
+ import { agentToolName, splitAllowList } from "./agentToolNames.js";
42
+
43
+ /** refId of the derived blueprint. */
44
+ export const AGENT_BLUEPRINT_REF_ID = "blueprint-declared-agents";
45
+ /** Prefix of every derived agent party refId. */
46
+ export const AGENT_PARTY_REF_ID_PREFIX = "party-agent-";
47
+
48
+ const AGENT_ROLE_PROPERTY = "cdx:agent:role";
49
+ const AGENT_DEFINITION_FILE_KINDS = new Set([
50
+ "agent-definition",
51
+ "claude-subagent",
52
+ ]);
53
+ const BEHAVIOR_TAXONOMY = new Set(
54
+ JSON.parse(
55
+ readFileSync(
56
+ join(dirNameStr, "data", "behavior-taxonomy.schema.json"),
57
+ "utf-8",
58
+ ),
59
+ ).enum,
60
+ );
61
+ const propertyValue = (subject, name) =>
62
+ (subject?.properties || []).find((entry) => entry?.name === name)?.value;
63
+
64
+ const propertyCount = (subject, name) => {
65
+ const parsed = Number(propertyValue(subject, name));
66
+ return Number.isFinite(parsed) ? parsed : 0;
67
+ };
68
+
69
+ /**
70
+ * Is this internal-model component a real coding-agent definition?
71
+ *
72
+ * @param {object} component Internal-model component
73
+ * @returns {boolean} True for a `cdx:agent:role` component or an
74
+ * agent-definition / claude-subagent file
75
+ */
76
+ export function isDeclaredAgentComponent(component) {
77
+ if (
78
+ (component?.properties || []).some(
79
+ (entry) => entry?.name === AGENT_ROLE_PROPERTY,
80
+ )
81
+ ) {
82
+ return true;
83
+ }
84
+ return AGENT_DEFINITION_FILE_KINDS.has(
85
+ propertyValue(component, "cdx:file:kind"),
86
+ );
87
+ }
88
+
89
+ /**
90
+ * The permissions of an agent: the tool names of `cdx:agent:tools` and the
91
+ * scopes of `cdx:agent:permissionCapabilities`, each reduced to the name
92
+ * before any "(", unique and sorted.
93
+ *
94
+ * @param {object} component Internal-model agent component
95
+ * @returns {string[]} Permission names
96
+ */
97
+ export function declaredAgentPermissions(component) {
98
+ const names = [
99
+ ...splitAllowList(propertyValue(component, "cdx:agent:tools")),
100
+ ...splitAllowList(
101
+ propertyValue(component, "cdx:agent:permissionCapabilities"),
102
+ ),
103
+ ]
104
+ .map(agentToolName)
105
+ .filter(Boolean);
106
+ return [...new Set(names)].sort();
107
+ }
108
+
109
+ function agentInstances(agent, partyRefId) {
110
+ const instances = [];
111
+ const declaresTools =
112
+ propertyCount(agent, "cdx:agent:toolCount") > 0 ||
113
+ splitAllowList(propertyValue(agent, "cdx:agent:tools")).length > 0 ||
114
+ propertyCount(agent, "cdx:agent:mcpServerCount") > 0;
115
+ if (declaresTools) {
116
+ instances.push({
117
+ refId: `behavior-${partyRefId}-invokes-tool`,
118
+ acknowledgment: ["declared"],
119
+ behavior: "ai:agent:invokesTool",
120
+ actors: [partyRefId],
121
+ });
122
+ }
123
+ if (propertyCount(agent, "cdx:agent:permissionAskRuleCount") > 0) {
124
+ instances.push({
125
+ refId: `behavior-${partyRefId}-requests-approval`,
126
+ acknowledgment: ["declared"],
127
+ behavior: "ai:agent:requestsUserApproval",
128
+ actors: [partyRefId],
129
+ });
130
+ }
131
+ return instances.filter((instance) =>
132
+ BEHAVIOR_TAXONOMY.has(instance.behavior),
133
+ );
134
+ }
135
+
136
+ function agentParty(agent, partyRefId) {
137
+ const system = { kind: "agent", ref: agent["bom-ref"] };
138
+ if (typeof agent.name === "string" && agent.name.length) {
139
+ system.label = agent.name;
140
+ }
141
+ const permissions = declaredAgentPermissions(agent);
142
+ if (permissions.length) {
143
+ system.permissions = permissions;
144
+ }
145
+ return { refId: partyRefId, roles: [{ role: "agent" }], system };
146
+ }
147
+
148
+ /**
149
+ * Collect the 2.0 agent parties and the behavioral blueprint for the
150
+ * declared agents of an internal-model BOM. Agents without a bom-ref are
151
+ * skipped (their party could not point at them); parties are unique by
152
+ * refId.
153
+ *
154
+ * @param {object} internalBom The 1.x-shaped internal model
155
+ * @returns {{parties: object[], blueprint: object|undefined}}
156
+ */
157
+ export function collectAgentBehaviour(internalBom) {
158
+ const parties = [];
159
+ const actors = [];
160
+ const instances = [];
161
+ const seen = new Set();
162
+ for (const agent of internalBom?.components || []) {
163
+ const bomRef = agent?.["bom-ref"];
164
+ if (
165
+ typeof bomRef !== "string" ||
166
+ !bomRef.length ||
167
+ !isDeclaredAgentComponent(agent)
168
+ ) {
169
+ continue;
170
+ }
171
+ const partyRefId = `${AGENT_PARTY_REF_ID_PREFIX}${bomRef}`;
172
+ if (seen.has(partyRefId)) {
173
+ continue;
174
+ }
175
+ seen.add(partyRefId);
176
+ parties.push(agentParty(agent, partyRefId));
177
+ actors.push({ refId: `actor-${partyRefId}`, party: { ref: partyRefId } });
178
+ instances.push(...agentInstances(agent, partyRefId));
179
+ }
180
+ if (!instances.length) {
181
+ return { parties, blueprint: undefined };
182
+ }
183
+ return {
184
+ parties,
185
+ blueprint: {
186
+ refId: AGENT_BLUEPRINT_REF_ID,
187
+ name: "Declared coding-agent behaviour",
188
+ modelTypes: ["behavioral"],
189
+ actors,
190
+ behaviors: { instances },
191
+ },
192
+ };
193
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Tool allow lists of coding-agent configurations, reduced to tool names.
3
+ *
4
+ * An allow list such as `Read, Bash(git status:*), Bash(curl -u user,secret)`
5
+ * carries commands, paths and sometimes credentials inside its parentheses.
6
+ * Only the tool name before any "(" is kept, so nothing an entry scopes its
7
+ * tool to reaches a BOM. The list is split on the commas outside parentheses,
8
+ * so the arguments of one entry never become entries of their own.
9
+ *
10
+ * A tool name is at most 64 characters of letters, digits and `_ - . : *`
11
+ * (`Read`, `mcp__github__*`, `fs_write`), optionally in the `@server/tool`
12
+ * form Kiro uses. A name that looks like a credential (a known token prefix
13
+ * such as `ghp_` or `AKIA`, or a long run of letters and digits with no
14
+ * separator) is dropped.
15
+ */
16
+
17
+ const NAME_CHARACTERS = new Set(
18
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._:*-",
19
+ );
20
+ const NAME_SEPARATORS = new Set("._:*-/@");
21
+ const MAX_NAME_LENGTH = 64;
22
+ // Prefixes of GitHub, OpenAI and Anthropic, Slack and AWS access key ids.
23
+ const CREDENTIAL_PREFIXES = Object.freeze([
24
+ "ghp_",
25
+ "gho_",
26
+ "ghu_",
27
+ "ghs_",
28
+ "ghr_",
29
+ "github_pat_",
30
+ "sk-",
31
+ "xoxa-",
32
+ "xoxb-",
33
+ "xoxp-",
34
+ "AKIA",
35
+ ]);
36
+ // A tool name is a word or words joined by separators; a longer unbroken run
37
+ // of letters and digits that mixes both is treated as a token.
38
+ const MAX_UNSEPARATED_TOKEN_LENGTH = 24;
39
+
40
+ /**
41
+ * Split an allow list on the commas outside parentheses. An array is taken
42
+ * as already split.
43
+ *
44
+ * @param {string|string[]|undefined} value Allow list
45
+ * @returns {string[]} Trimmed, non-empty entries
46
+ */
47
+ export function splitAllowList(value) {
48
+ if (Array.isArray(value)) {
49
+ return value
50
+ .filter((entry) => typeof entry === "string")
51
+ .flatMap((entry) => splitAllowList(entry));
52
+ }
53
+ const entries = [];
54
+ let depth = 0;
55
+ let current = "";
56
+ for (const ch of `${value ?? ""}`) {
57
+ if (ch === "(") {
58
+ depth++;
59
+ } else if (ch === ")" && depth > 0) {
60
+ depth--;
61
+ } else if (ch === "," && depth === 0) {
62
+ entries.push(current);
63
+ current = "";
64
+ continue;
65
+ }
66
+ current += ch;
67
+ }
68
+ entries.push(current);
69
+ return entries.map((entry) => entry.trim()).filter(Boolean);
70
+ }
71
+
72
+ function looksLikeCredential(name) {
73
+ if (CREDENTIAL_PREFIXES.some((prefix) => name.startsWith(prefix))) {
74
+ return true;
75
+ }
76
+ const chars = [...name];
77
+ return (
78
+ name.length >= MAX_UNSEPARATED_TOKEN_LENGTH &&
79
+ !chars.some((ch) => NAME_SEPARATORS.has(ch)) &&
80
+ chars.some((ch) => ch >= "0" && ch <= "9") &&
81
+ chars.some((ch) => ch.toLowerCase() !== ch.toUpperCase())
82
+ );
83
+ }
84
+
85
+ function isNamePart(part) {
86
+ return part.length > 0 && [...part].every((ch) => NAME_CHARACTERS.has(ch));
87
+ }
88
+
89
+ /**
90
+ * The tool name of one allow-list entry, or undefined when it is not a safe
91
+ * tool name.
92
+ *
93
+ * @param {string} entry Allow-list entry
94
+ * @returns {string|undefined} Tool name
95
+ */
96
+ export function agentToolName(entry) {
97
+ const text = `${entry ?? ""}`;
98
+ const parenthesis = text.indexOf("(");
99
+ const name = (parenthesis >= 0 ? text.slice(0, parenthesis) : text).trim();
100
+ if (!name.length || name.length > MAX_NAME_LENGTH) {
101
+ return undefined;
102
+ }
103
+ const scoped = name.startsWith("@") ? name.slice(1).split("/") : [name];
104
+ const shapeOk = name.startsWith("@")
105
+ ? scoped.length === 2 && scoped.every(isNamePart)
106
+ : isNamePart(name);
107
+ if (!shapeOk || looksLikeCredential(name)) {
108
+ return undefined;
109
+ }
110
+ return name;
111
+ }
112
+
113
+ /**
114
+ * The unique, sorted tool names of an allow list.
115
+ *
116
+ * @param {string|string[]|undefined} value Allow list
117
+ * @returns {string[]} Tool names
118
+ */
119
+ export function agentToolNames(value) {
120
+ return [
121
+ ...new Set(splitAllowList(value).map(agentToolName).filter(Boolean)),
122
+ ].sort();
123
+ }
124
+
125
+ /**
126
+ * An allow list as cdxgen records it in `cdx:agent:tools` and
127
+ * `cdx:agent:disallowedTools`: each entry reduced to its tool name, written
128
+ * `Name(...)` when the entry scoped the tool with arguments, so a scoped
129
+ * `Bash(git status:*)` stays distinguishable from an unrestricted `Bash`
130
+ * without copying the scope. Unique, in the order the config lists them,
131
+ * comma separated.
132
+ *
133
+ * @param {string|string[]|undefined} value Allow list
134
+ * @returns {string|undefined} Summary, or undefined when nothing is left
135
+ */
136
+ export function summarizeAgentToolList(value) {
137
+ const summary = new Set();
138
+ for (const entry of splitAllowList(value)) {
139
+ const name = agentToolName(entry);
140
+ if (name) {
141
+ summary.add(entry.includes("(") ? `${name}(...)` : name);
142
+ }
143
+ }
144
+ return summary.size ? [...summary].join(",") : undefined;
145
+ }
@@ -29,6 +29,7 @@ import {
29
29
  repositoryUrlForHuggingFaceAssetType,
30
30
  toHuggingFaceAssetUrl,
31
31
  toHuggingFacePurl,
32
+ toHuggingFaceSupplier,
32
33
  } from "../parsers/huggingfaceUtils.js";
33
34
  import { parseOllamaModelfile } from "../parsers/ollama.js";
34
35
  import { readOnnxHeader } from "../parsers/onnx.js";
@@ -37,6 +38,12 @@ import {
37
38
  detectAiModelVariants,
38
39
  normalizeDetectedVariants,
39
40
  } from "./aiModelVariants.js";
41
+ import {
42
+ modelCreatorEntity,
43
+ modelCreatorFromOrganization,
44
+ resolveModelCreator,
45
+ splitModelReference,
46
+ } from "./aiProviders.js";
40
47
  import { getLicenses } from "./spdx.js";
41
48
 
42
49
  // -----------------------------------------------------------------------------
@@ -98,7 +105,8 @@ const IGNORE_SOURCE_FILE_PATTERN =
98
105
  const HOST_PROVIDER_PATTERNS = [
99
106
  ["openai", /(?:^|\.)openai\.com$/iu],
100
107
  ["anthropic", /(?:^|\.)anthropic\.com$/iu],
101
- ["google", /(?:^|\.)googleapis\.com$/iu],
108
+ // Only the Gemini API host. Other googleapis.com hosts serve Cloud Storage,
109
+ // Vertex AI (matched below) and unrelated Google APIs.
102
110
  ["google", /(?:^|\.)generativelanguage\.googleapis\.com$/iu],
103
111
  ["azure-openai", /(?:^|\.)openai\.azure\.com$/iu],
104
112
  ["huggingface", /(?:^|\.)huggingface\.co$/iu],
@@ -110,7 +118,9 @@ const HOST_PROVIDER_PATTERNS = [
110
118
  ["together", /(?:^|\.)together\.xyz$/iu],
111
119
  ["replicate", /(?:^|\.)replicate\.com$/iu],
112
120
  ["perplexity", /(?:^|\.)perplexity\.ai$/iu],
113
- ["vertex-ai", /(?:^|\.)aiplatform\.googleapis\.com$/iu],
121
+ // Regional Vertex AI endpoints join the region with a hyphen, as in
122
+ // us-central1-aiplatform.googleapis.com.
123
+ ["vertex-ai", /(?:^|[.-])aiplatform\.googleapis\.com$/iu],
114
124
  ["xai", /(?:^|\.)x\.ai$/iu],
115
125
  [
116
126
  "aws-bedrock",
@@ -1326,9 +1336,62 @@ const createAiFileComponent = (
1326
1336
  return componentsByKey.get(key);
1327
1337
  };
1328
1338
 
1339
+ // Longest producer name a model file may declare about itself.
1340
+ const MAX_DECLARED_PRODUCER_LENGTH = 128;
1341
+
1342
+ /**
1343
+ * Trim and bound a producer name a model file declares about itself, such as
1344
+ * the GGUF general.organization field. The value is file-controlled free text.
1345
+ *
1346
+ * @param {unknown} value Declared producer
1347
+ * @returns {string|undefined} Usable producer name
1348
+ */
1349
+ const declaredProducerName = (value) => {
1350
+ if (typeof value !== "string") {
1351
+ return undefined;
1352
+ }
1353
+ const name = value.trim();
1354
+ if (!name || name.length > MAX_DECLARED_PRODUCER_LENGTH) {
1355
+ return undefined;
1356
+ }
1357
+ return name;
1358
+ };
1359
+
1360
+ /**
1361
+ * Build the manufacturer of a model, the organisation that created it.
1362
+ *
1363
+ * A producer the model file declares about itself wins, written with the
1364
+ * creator's full name when it is a known creator and as declared otherwise.
1365
+ * A Hub repository credits its namespace only when the namespace is the
1366
+ * creator's own organisation (meta-llama, mistralai, Qwen), because
1367
+ * re-packagers such as TheBloke, unsloth or mlx-community publish models
1368
+ * other organisations made. A model that source code calls by name uses the
1369
+ * creator id the caller resolved.
1370
+ *
1371
+ * @param {{ hubNamespace?: string, modelCreator?: string, producer?: unknown }} sources
1372
+ * Creator evidence for the model
1373
+ * @returns {{ name: string }|undefined} Manufacturer entity
1374
+ */
1375
+ const modelManufacturer = ({ hubNamespace, modelCreator, producer }) => {
1376
+ const declared = declaredProducerName(producer);
1377
+ if (declared) {
1378
+ return (
1379
+ modelCreatorEntity(modelCreatorFromOrganization(declared)) || {
1380
+ name: declared,
1381
+ }
1382
+ );
1383
+ }
1384
+ if (hubNamespace) {
1385
+ return modelCreatorEntity(modelCreatorFromOrganization(hubNamespace));
1386
+ }
1387
+ return modelCreatorEntity(modelCreator);
1388
+ };
1389
+
1329
1390
  const normalizeComponentIdentity = ({
1330
1391
  assetType,
1392
+ modelCreator,
1331
1393
  modelId,
1394
+ producer,
1332
1395
  provider,
1333
1396
  version,
1334
1397
  }) => {
@@ -1343,7 +1406,14 @@ const normalizeComponentIdentity = ({
1343
1406
  : normalizedAssetType === "space"
1344
1407
  ? `spaces/${normalizedModelId.replace(/^spaces\//u, "")}`
1345
1408
  : normalizedModelId;
1346
- const repoId = normalizeHuggingFaceReference(typedModelId);
1409
+ // A litellm route such as groq/llama3-8b or a Google resource name such as
1410
+ // models/gemini-1.5-flash has the shape of a Hub repository id but is not
1411
+ // one, so its first segment is never taken as a Hub namespace.
1412
+ const repoId =
1413
+ normalizedAssetType === "model" &&
1414
+ splitModelReference(normalizedModelId).nonHub
1415
+ ? undefined
1416
+ : normalizeHuggingFaceReference(typedModelId);
1347
1417
  if (
1348
1418
  repoId?.assetType === normalizedAssetType &&
1349
1419
  repoId?.repoId?.includes("/")
@@ -1354,11 +1424,24 @@ const normalizeComponentIdentity = ({
1354
1424
  repoId.version || version,
1355
1425
  repositoryUrlForHuggingFaceAssetType(normalizedAssetType),
1356
1426
  );
1427
+ // The Hub namespace is the account that published the repository, so it
1428
+ // is the supplier. Its Hub page is the supplier url only when the model
1429
+ // came from the Hub; a routed id such as meta-llama/Llama-3-70b on
1430
+ // together keeps the name.
1431
+ const hubSupplier = toHuggingFaceSupplier(group);
1357
1432
  return {
1358
1433
  bomRef: purl,
1359
1434
  group,
1435
+ manufacturer:
1436
+ normalizedAssetType === "model"
1437
+ ? modelManufacturer({ hubNamespace: group, producer })
1438
+ : undefined,
1360
1439
  name,
1361
1440
  purl,
1441
+ supplier:
1442
+ hubSupplier && normalizedProvider.toLowerCase() !== "huggingface"
1443
+ ? { name: hubSupplier.name }
1444
+ : hubSupplier,
1362
1445
  };
1363
1446
  }
1364
1447
  return {
@@ -1368,6 +1451,12 @@ const normalizeComponentIdentity = ({
1368
1451
  normalizedModelId,
1369
1452
  ),
1370
1453
  group: normalizedProvider,
1454
+ // Hosting, routing and local providers stay in group and
1455
+ // cdx:ai:provider; only the creator is the manufacturer.
1456
+ manufacturer:
1457
+ normalizedAssetType === "model"
1458
+ ? modelManufacturer({ modelCreator, producer })
1459
+ : undefined,
1371
1460
  name: normalizedModelId,
1372
1461
  };
1373
1462
  };
@@ -1390,6 +1479,11 @@ const ensureModelComponent = (componentsByKey, key, seed) => {
1390
1479
  description: seed.description,
1391
1480
  version: seed.version,
1392
1481
  licenses: seed.licenses,
1482
+ // The organisation that created the model is the manufacturer. The
1483
+ // Hugging Face namespace that published the repository is the
1484
+ // supplier.
1485
+ manufacturer: identity.manufacturer,
1486
+ supplier: identity.supplier,
1393
1487
  externalReferences: seed.externalReferences || [],
1394
1488
  evidence: { occurrences: [] },
1395
1489
  modelCard: seed.modelCard
@@ -1691,6 +1785,25 @@ const scanUrlMatches = (
1691
1785
  }
1692
1786
  };
1693
1787
 
1788
+ // A plausible model id: 2-160 characters with no whitespace, starting with an
1789
+ // alphanumeric (or the `@` of a scoped id such as Cloudflare's `@cf/...`) and
1790
+ // otherwise limited to name, scope, version, and registry punctuation.
1791
+ // Markdown prose around an inline `` `model:` `` label fails these checks, so
1792
+ // documentation such as "editing the `model:` field. Run `kiro-cli chat
1793
+ // --list-models`" does not become a model.
1794
+ const MODEL_ID_CHARS_PATTERN = /^@?[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/u;
1795
+ const MODEL_ID_MIN_LENGTH = 2;
1796
+ const MODEL_ID_MAX_LENGTH = 160;
1797
+
1798
+ const isPlausibleModelId = (value) => {
1799
+ const normalized = String(value || "").trim();
1800
+ return (
1801
+ normalized.length >= MODEL_ID_MIN_LENGTH &&
1802
+ normalized.length <= MODEL_ID_MAX_LENGTH &&
1803
+ MODEL_ID_CHARS_PATTERN.test(normalized)
1804
+ );
1805
+ };
1806
+
1694
1807
  const extractModelAssignments = (raw) => {
1695
1808
  const values = [];
1696
1809
  const patterns = [
@@ -1708,9 +1821,10 @@ const extractModelAssignments = (raw) => {
1708
1821
  }
1709
1822
  return values.filter((entry) => {
1710
1823
  const normalized = String(entry.value || "").trim();
1711
- return (
1712
- normalized && !normalized.startsWith("http") && normalized !== "auto"
1713
- );
1824
+ if (!normalized || normalized.startsWith("http") || normalized === "auto") {
1825
+ return false;
1826
+ }
1827
+ return isPlausibleModelId(normalized);
1714
1828
  });
1715
1829
  };
1716
1830
 
@@ -1732,6 +1846,15 @@ const extractHuggingFaceReferences = (raw) => {
1732
1846
  }
1733
1847
  }
1734
1848
  return refs
1849
+ .filter(
1850
+ (entry) =>
1851
+ // URL-derived references are already anchored to a real host, while
1852
+ // quoted `repo_id`/`model` captures suffer the same backtick-as-quote
1853
+ // ambiguity as model assignments, so they must look like an id too.
1854
+ entry.sourceUrl ||
1855
+ (isPlausibleModelId(entry.value) &&
1856
+ !splitModelReference(entry.value).nonHub),
1857
+ )
1735
1858
  .map((entry) => {
1736
1859
  const reference = normalizeHuggingFaceReference(entry.value);
1737
1860
  return {
@@ -1770,7 +1893,16 @@ const createModelComponent = (componentsByKey, seed, occurrenceLocation) => {
1770
1893
  ? seed.assetType
1771
1894
  : "model";
1772
1895
  const key = `${normalizedAssetType}:${seed.provider || "ai"}:${seed.modelId}`;
1896
+ const isKnownComponent = componentsByKey.has(key);
1773
1897
  const component = ensureModelComponent(componentsByKey, key, seed);
1898
+ // The same model can be seen first in a file that names no creator and
1899
+ // later in one that does, so a later seed fills a missing manufacturer.
1900
+ if (isKnownComponent && !component.manufacturer) {
1901
+ const { manufacturer } = normalizeComponentIdentity(seed);
1902
+ if (manufacturer) {
1903
+ component.manufacturer = manufacturer;
1904
+ }
1905
+ }
1774
1906
  syncComponentProperties(component, {
1775
1907
  artifactFormat: seed.artifactFormat,
1776
1908
  confidence: seed.confidence,
@@ -1900,14 +2032,25 @@ const collectTextAiInventory = (discoveryPath, options = {}, config = {}) => {
1900
2032
  }
1901
2033
  for (const match of extractModelAssignments(raw)) {
1902
2034
  const modelId = String(match.value || "").trim();
2035
+ // Only a provider the file declares through an SDK import or AI host
2036
+ // may agree with the creator. The fallback guessed from the model name
2037
+ // stays in group and cdx:ai:provider.
2038
+ const declaredProvider = Array.from(fileSignals.providers)[0];
1903
2039
  const provider =
1904
- Array.from(fileSignals.providers)[0] || providerFromModelName(modelId);
2040
+ declaredProvider ||
2041
+ splitModelReference(modelId).routeProvider ||
2042
+ providerFromModelName(modelId);
1905
2043
  const component = createModelComponent(
1906
2044
  componentsByKey,
1907
2045
  {
1908
2046
  assetType: "model",
1909
2047
  confidence: "medium",
1910
2048
  kind: "model",
2049
+ modelCreator: resolveModelCreator(modelId, {
2050
+ fileProviders: fileSignals.providers,
2051
+ provider: declaredProvider,
2052
+ runtimes: fileSignals.runtimes,
2053
+ }),
1911
2054
  modelFamily: modelFamilyFromName(modelId),
1912
2055
  modelId,
1913
2056
  provider,
@@ -2102,6 +2245,9 @@ export function collectJsAiInventory(discoveryPath, options = {}) {
2102
2245
  ggufModelIdFromFilename(parsedFileName, filePath),
2103
2246
  parameterCount: metadata["general.parameter_count"],
2104
2247
  pedigree: createGgufPedigree(metadata),
2248
+ // general.organization is the creator the file declares, so it is
2249
+ // the manufacturer even when it is not a known creator.
2250
+ producer: metadata["general.organization"],
2105
2251
  provider: metadata["general.organization"] || "local",
2106
2252
  quantization,
2107
2253
  reviewNeeded: true,