@cdxgen/cdxgen 13.1.0 → 13.3.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 (283) hide show
  1. package/README.md +32 -15
  2. package/bin/cdxgen.js +243 -122
  3. package/bin/evinse.js +7 -0
  4. package/bin/sign.js +88 -14
  5. package/bin/tracebom.js +64 -4
  6. package/bin/validate.js +29 -7
  7. package/bin/verify.js +79 -39
  8. package/data/crypto-oid.json +88 -12
  9. package/data/remediations.json +47 -0
  10. package/data/rules/ai-bom.yaml +78 -0
  11. package/data/rules/cargo-rust.yaml +132 -0
  12. package/data/rules/mcp-servers.yaml +122 -0
  13. package/lib/audit/targets.js +6 -1
  14. package/lib/cli/bomAssembly.js +117 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +235 -5
  17. package/lib/cli/jsBom.js +108 -26
  18. package/lib/cli/jvmBom.js +68 -12
  19. package/lib/cli/managedBom.js +264 -41
  20. package/lib/cli/nativeBom.js +923 -15
  21. package/lib/core/activity.js +120 -2
  22. package/lib/core/env.js +44 -7
  23. package/lib/core/fs.js +174 -6
  24. package/lib/core/metadataProperties.js +97 -0
  25. package/lib/core/projectConfig.js +31 -4
  26. package/lib/core/userAgent.js +151 -0
  27. package/lib/ecosystems/bunutils.js +269 -101
  28. package/lib/ecosystems/cargoCache.js +364 -0
  29. package/lib/ecosystems/caxa.js +447 -7
  30. package/lib/ecosystems/cmakeResolver.js +66 -0
  31. package/lib/ecosystems/cppEvidence.js +104 -33
  32. package/lib/ecosystems/ecosystems.js +260 -99
  33. package/lib/ecosystems/golem.js +11 -1
  34. package/lib/ecosystems/gradleutils.js +185 -9
  35. package/lib/ecosystems/jsEvidence.js +19 -20
  36. package/lib/ecosystems/kosi.js +1263 -0
  37. package/lib/ecosystems/npmutils.js +110 -9
  38. package/lib/ecosystems/parsers-crystal.js +179 -0
  39. package/lib/ecosystems/parsers-dotnet.js +120 -49
  40. package/lib/ecosystems/parsers-elm.js +816 -0
  41. package/lib/ecosystems/parsers-gleam.js +1 -1
  42. package/lib/ecosystems/parsers-go.js +47 -9
  43. package/lib/ecosystems/parsers-js.js +171 -50
  44. package/lib/ecosystems/parsers-julia.js +268 -0
  45. package/lib/ecosystems/parsers-lua.js +218 -0
  46. package/lib/ecosystems/parsers-misc.js +919 -161
  47. package/lib/ecosystems/parsers-nim.js +221 -0
  48. package/lib/ecosystems/parsers-opam.js +180 -0
  49. package/lib/ecosystems/parsers-perl.js +277 -0
  50. package/lib/ecosystems/parsers-python.js +27 -15
  51. package/lib/ecosystems/parsers-r.js +148 -0
  52. package/lib/ecosystems/parsers-rebar.js +411 -0
  53. package/lib/ecosystems/parsers-rust.js +954 -33
  54. package/lib/ecosystems/parsers-spack.js +204 -0
  55. package/lib/ecosystems/parsers-stack.js +264 -0
  56. package/lib/ecosystems/parsers-terraform.js +194 -0
  57. package/lib/ecosystems/rubyutils.js +1426 -166
  58. package/lib/ecosystems/rusi.js +158 -7
  59. package/lib/evinser/evinser.js +547 -129
  60. package/lib/evinser/swiftsem.js +1914 -302
  61. package/lib/helpers/agenticToolutils.js +925 -0
  62. package/lib/helpers/bomSigner.js +652 -221
  63. package/lib/inventory/agentFormulationParser.js +57 -4
  64. package/lib/inventory/aiCollector.js +504 -10
  65. package/lib/inventory/aiInventory.js +12 -0
  66. package/lib/inventory/analyzer.js +488 -30
  67. package/lib/inventory/astWorker.js +6 -0
  68. package/lib/inventory/atomUtils.js +568 -31
  69. package/lib/inventory/auditCategories.js +8 -0
  70. package/lib/inventory/cbomutils.js +30 -0
  71. package/lib/inventory/cdxrs.js +110 -14
  72. package/lib/inventory/communityAiConfigParser.js +215 -2
  73. package/lib/inventory/depsUtils.js +183 -11
  74. package/lib/inventory/display.js +4 -5
  75. package/lib/inventory/dosai.js +627 -13
  76. package/lib/inventory/dynamic.js +39 -1
  77. package/lib/inventory/envcontext.js +9 -6
  78. package/lib/inventory/evidenceUtils.js +26 -5
  79. package/lib/inventory/fetchBatch.js +190 -19
  80. package/lib/inventory/fetchRate.js +74 -11
  81. package/lib/inventory/forgeEnricher.js +3 -2
  82. package/lib/inventory/jvmToolEnv.js +6 -5
  83. package/lib/inventory/mcpConfigParser.js +166 -2
  84. package/lib/inventory/mcpDiscovery.js +11 -0
  85. package/lib/inventory/mcpServerManifest.js +389 -0
  86. package/lib/inventory/mcpbBundle.js +419 -0
  87. package/lib/inventory/plugins.js +3 -0
  88. package/lib/inventory/protobom.js +62 -89
  89. package/lib/inventory/protobomLoader.js +4 -0
  90. package/lib/inventory/purl.js +152 -3
  91. package/lib/inventory/rustFormulationParser.js +1 -1
  92. package/lib/inventory/sbomDocument.js +2 -70
  93. package/lib/inventory/source.js +47 -23
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/traceRunner.js +210 -21
  97. package/lib/managers/binary.js +115 -24
  98. package/lib/managers/docker.js +440 -20
  99. package/lib/managers/oci.js +39 -1
  100. package/lib/parsers/cmakeLists.js +452 -0
  101. package/lib/parsers/huggingfaceUtils.js +15 -7
  102. package/lib/parsers/largeJson.js +454 -0
  103. package/lib/parsers/npmrc.js +19 -1
  104. package/lib/parsers/onnx.js +294 -0
  105. package/lib/parsers/safetensors.js +114 -0
  106. package/lib/stages/postgen/introspection/markers.js +29 -6
  107. package/lib/stages/postgen/postgen.js +237 -16
  108. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  109. package/lib/validator/bomValidator.js +6 -16
  110. package/lib/validator/index.js +48 -10
  111. package/lib/validator/reporters/console.js +3 -0
  112. package/lib/validator/reporters/json.js +1 -0
  113. package/lib/validator/reporters/sarif.js +7 -0
  114. package/package.json +27 -27
  115. package/types/lib/audit/targets.d.ts.map +1 -1
  116. package/types/lib/cli/bomAssembly.d.ts +2 -1
  117. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  118. package/types/lib/cli/cliOptions.d.ts +33 -1
  119. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  120. package/types/lib/cli/index.d.ts +2 -2
  121. package/types/lib/cli/index.d.ts.map +1 -1
  122. package/types/lib/cli/jsBom.d.ts +23 -0
  123. package/types/lib/cli/jsBom.d.ts.map +1 -1
  124. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  125. package/types/lib/cli/managedBom.d.ts +33 -0
  126. package/types/lib/cli/managedBom.d.ts.map +1 -1
  127. package/types/lib/cli/nativeBom.d.ts +88 -8
  128. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  129. package/types/lib/core/activity.d.ts.map +1 -1
  130. package/types/lib/core/env.d.ts +21 -0
  131. package/types/lib/core/env.d.ts.map +1 -1
  132. package/types/lib/core/fs.d.ts +55 -0
  133. package/types/lib/core/fs.d.ts.map +1 -1
  134. package/types/lib/core/metadataProperties.d.ts +39 -0
  135. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  136. package/types/lib/core/projectConfig.d.ts +6 -2
  137. package/types/lib/core/projectConfig.d.ts.map +1 -1
  138. package/types/lib/core/userAgent.d.ts +56 -0
  139. package/types/lib/core/userAgent.d.ts.map +1 -0
  140. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  141. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  143. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  144. package/types/lib/ecosystems/caxa.d.ts +14 -0
  145. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  146. package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
  147. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  148. package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
  149. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  150. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  151. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  152. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  153. package/types/lib/ecosystems/gradleutils.d.ts +40 -4
  154. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  155. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  156. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  157. package/types/lib/ecosystems/kosi.d.ts +86 -0
  158. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  159. package/types/lib/ecosystems/npmutils.d.ts +42 -0
  160. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  161. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  162. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  163. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  164. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  165. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  166. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  167. package/types/lib/ecosystems/parsers-go.d.ts +10 -0
  168. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  169. package/types/lib/ecosystems/parsers-js.d.ts +10 -4
  170. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  171. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  172. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  173. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  174. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
  176. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  177. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  178. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  179. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  180. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  181. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  182. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  183. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  184. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  185. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  187. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  188. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  189. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  191. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  192. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  193. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  194. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  195. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  196. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  197. package/types/lib/ecosystems/rubyutils.d.ts +143 -2
  198. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  199. package/types/lib/ecosystems/rusi.d.ts +13 -0
  200. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  201. package/types/lib/evinser/evinser.d.ts +81 -2
  202. package/types/lib/evinser/evinser.d.ts.map +1 -1
  203. package/types/lib/evinser/swiftsem.d.ts +280 -15
  204. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  205. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  206. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  207. package/types/lib/helpers/bomSigner.d.ts +106 -13
  208. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  209. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  210. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  211. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  212. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  213. package/types/lib/inventory/atomUtils.d.ts +145 -3
  214. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +4 -0
  216. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  217. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  218. package/types/lib/inventory/cdxrs.d.ts +8 -3
  219. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  220. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  221. package/types/lib/inventory/depsUtils.d.ts +15 -0
  222. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/display.d.ts.map +1 -1
  224. package/types/lib/inventory/dosai.d.ts +87 -1
  225. package/types/lib/inventory/dosai.d.ts.map +1 -1
  226. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  227. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  228. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  229. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  230. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  231. package/types/lib/inventory/fetchRate.d.ts +9 -6
  232. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  233. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  234. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  235. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  236. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  237. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  238. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  239. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  240. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  241. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  242. package/types/lib/inventory/plugins.d.ts.map +1 -1
  243. package/types/lib/inventory/protobom.d.ts +18 -13
  244. package/types/lib/inventory/protobom.d.ts.map +1 -1
  245. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  246. package/types/lib/inventory/purl.d.ts +71 -2
  247. package/types/lib/inventory/purl.d.ts.map +1 -1
  248. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  249. package/types/lib/inventory/source.d.ts.map +1 -1
  250. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  251. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  252. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  253. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  254. package/types/lib/inventory/traceRunner.d.ts +47 -1
  255. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  256. package/types/lib/managers/binary.d.ts +30 -1
  257. package/types/lib/managers/binary.d.ts.map +1 -1
  258. package/types/lib/managers/docker.d.ts +29 -0
  259. package/types/lib/managers/docker.d.ts.map +1 -1
  260. package/types/lib/managers/oci.d.ts +14 -0
  261. package/types/lib/managers/oci.d.ts.map +1 -1
  262. package/types/lib/parsers/cmakeLists.d.ts +76 -0
  263. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  264. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  265. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  266. package/types/lib/parsers/largeJson.d.ts +52 -0
  267. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  268. package/types/lib/parsers/npmrc.d.ts +5 -0
  269. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  270. package/types/lib/parsers/onnx.d.ts +28 -0
  271. package/types/lib/parsers/onnx.d.ts.map +1 -0
  272. package/types/lib/parsers/safetensors.d.ts +22 -0
  273. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  275. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  276. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  277. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  278. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  279. package/types/lib/validator/index.d.ts +10 -1
  280. package/types/lib/validator/index.d.ts.map +1 -1
  281. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  282. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  283. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
@@ -1,6 +1,8 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
3
 
4
+ import * as toml from "smol-toml";
5
+
4
6
  import {
5
7
  sanitizeBomPropertyValue,
6
8
  sanitizeBomUrl,
@@ -30,6 +32,16 @@ const MCP_CONFIG_PATTERNS = [
30
32
  "**/opencode.json",
31
33
  "opencode.jsonc",
32
34
  "**/opencode.jsonc",
35
+ ".gemini/settings.json",
36
+ "**/.gemini/settings.json",
37
+ ".windsurf/mcp_config.json",
38
+ "**/.windsurf/mcp_config.json",
39
+ ".zed/settings.json",
40
+ "**/.zed/settings.json",
41
+ ".roo/mcp.json",
42
+ "**/.roo/mcp.json",
43
+ ".codex/config.toml",
44
+ "**/.codex/config.toml",
33
45
  ];
34
46
 
35
47
  const LOCAL_TUNNEL_HOST_PATTERNS = [
@@ -92,11 +104,32 @@ function configFormat(filePath) {
92
104
  ) {
93
105
  return "cursor";
94
106
  }
107
+ if (lowerPath.includes("/.gemini/")) {
108
+ return "gemini";
109
+ }
110
+ if (lowerPath.includes("/.windsurf/")) {
111
+ return "windsurf";
112
+ }
113
+ if (lowerPath.includes("/.zed/")) {
114
+ return "zed";
115
+ }
116
+ if (lowerPath.includes("/.roo/")) {
117
+ return "roo";
118
+ }
119
+ if (
120
+ lowerPath.includes("/.codex/") ||
121
+ lowerPath.endsWith(".codex/config.toml")
122
+ ) {
123
+ return "codex";
124
+ }
95
125
  return "generic-mcp-json";
96
126
  }
97
127
 
98
128
  function syntaxForFile(filePath) {
99
129
  const lowerPath = filePath.toLowerCase();
130
+ if (lowerPath.endsWith(".toml")) {
131
+ return "toml";
132
+ }
100
133
  return lowerPath.endsWith(".json") || lowerPath.endsWith(".jsonc")
101
134
  ? "json"
102
135
  : "text";
@@ -105,7 +138,15 @@ function syntaxForFile(filePath) {
105
138
  function extractServerMaps(config) {
106
139
  const serverMaps = [];
107
140
  for (const [key, value] of Object.entries(config || {})) {
108
- if (["mcpServers", "context_servers", "servers", "mcp"].includes(key)) {
141
+ if (
142
+ [
143
+ "mcpServers",
144
+ "context_servers",
145
+ "servers",
146
+ "mcp",
147
+ "mcp_servers",
148
+ ].includes(key)
149
+ ) {
109
150
  if (value && typeof value === "object" && !Array.isArray(value)) {
110
151
  serverMaps.push({ configKey: key, servers: Object.entries(value) });
111
152
  } else if (Array.isArray(value)) {
@@ -382,6 +423,106 @@ function trustProfile(officialSdk, exposureType, authPosture) {
382
423
  return officialSdk ? "official-sdk+unknown" : "review-needed";
383
424
  }
384
425
 
426
+ /**
427
+ * Extract an explicit MCP protocol version literal from a server config.
428
+ *
429
+ * @param {Object} serverConfig Parsed server configuration
430
+ * @returns {string|undefined} protocol version such as `2026-07-28`
431
+ */
432
+ function protocolVersionForConfig(serverConfig) {
433
+ const candidate =
434
+ serverConfig?.protocolVersion ||
435
+ serverConfig?.protocol_version ||
436
+ serverConfig?.["MCP-Protocol-Version"] ||
437
+ serverConfig?.mcpProtocolVersion;
438
+ if (typeof candidate === "string" && /^\d{4}-\d{2}-\d{2}$/u.test(candidate)) {
439
+ return candidate;
440
+ }
441
+ return undefined;
442
+ }
443
+
444
+ /**
445
+ * Detect MCP features deprecated by the 2026-07-28 specification that a config
446
+ * still references.
447
+ *
448
+ * Detection is structural rather than free-text: the transport type, an
449
+ * explicit dynamic client registration endpoint, and declared capability
450
+ * objects are reliable signals, whereas substring scans over a stringified
451
+ * config fire on server names and unrelated blocks. Wire-level method names
452
+ * (`roots/list`, `sampling/createMessage`) are exchanged at runtime and never
453
+ * appear in a client config stanza, so they are intentionally not matched
454
+ * here; source analysis is the right place for those.
455
+ *
456
+ * @param {Object} serverConfig Parsed server configuration
457
+ * @param {string} transport Resolved transport
458
+ * @returns {string[]} sorted list of deprecated feature labels
459
+ */
460
+ function deprecatedFeaturesForConfig(serverConfig, transport) {
461
+ const found = new Set();
462
+ // Legacy HTTP+SSE transport.
463
+ if (transport === "sse") {
464
+ found.add("sse-transport");
465
+ }
466
+ // Dynamic client registration, detected from an explicit endpoint or flag.
467
+ if (
468
+ serverConfig?.registration_endpoint ||
469
+ serverConfig?.auth?.registration_endpoint ||
470
+ serverConfig?.oauth?.registration_endpoint ||
471
+ serverConfig?.dynamicClientRegistration === true ||
472
+ serverConfig?.supportsDCR === true
473
+ ) {
474
+ found.add("dynamic-client-registration");
475
+ }
476
+ // Capability objects the client explicitly declares. A capabilities block is
477
+ // the structured place these appear; a bare declaration of them is the
478
+ // deprecated shape under 2026-07-28.
479
+ const capabilities = serverConfig?.capabilities;
480
+ if (capabilities && typeof capabilities === "object") {
481
+ for (const feature of ["sampling", "logging", "roots"]) {
482
+ if (capabilities[feature] && typeof capabilities[feature] === "object") {
483
+ found.add(feature);
484
+ }
485
+ }
486
+ }
487
+ return Array.from(found).sort();
488
+ }
489
+
490
+ /**
491
+ * Detect Client ID Metadata Document (CIMD) usage in a server config.
492
+ *
493
+ * @param {Object} serverConfig Parsed server configuration
494
+ * @returns {boolean} true when CIMD is configured
495
+ */
496
+ function usesClientIdMetadataDocument(serverConfig) {
497
+ return Boolean(
498
+ serverConfig?.clientIdMetadataDocument ||
499
+ serverConfig?.client_id_metadata_document ||
500
+ serverConfig?.clientIdMetadataDocumentEnabled ||
501
+ serverConfig?.client_id_metadata_document_enabled ||
502
+ serverConfig?.oauth?.clientIdMetadataDocument,
503
+ );
504
+ }
505
+
506
+ /**
507
+ * Detect whether a config binds tokens to a specific resource audience, per the
508
+ * hardened MCP 2026-07-28 authorization model.
509
+ *
510
+ * @param {Object} serverConfig Parsed server configuration
511
+ * @returns {boolean} true when audience-bound resource metadata is present
512
+ */
513
+ function usesAudienceBoundTokens(serverConfig) {
514
+ const resourceMetadata =
515
+ serverConfig?.resourceMetadata || serverConfig?.oauth?.resourceMetadata;
516
+ if (resourceMetadata && typeof resourceMetadata === "object") {
517
+ return Boolean(
518
+ resourceMetadata.resource &&
519
+ (Array.isArray(resourceMetadata.authorization_servers) ||
520
+ resourceMetadata.authorizationServers),
521
+ );
522
+ }
523
+ return false;
524
+ }
525
+
385
526
  function createServiceFromConfig(
386
527
  filePath,
387
528
  format,
@@ -511,6 +652,27 @@ function createServiceFromConfig(
511
652
  if (authHints.has("oauth")) {
512
653
  addUniqueProperty(properties, "cdx:mcp:auth:requiresOAuth", "true");
513
654
  }
655
+ if (usesClientIdMetadataDocument(serverConfig)) {
656
+ addUniqueProperty(properties, "cdx:mcp:auth:cimd", "true");
657
+ }
658
+ if (usesAudienceBoundTokens(serverConfig)) {
659
+ addUniqueProperty(properties, "cdx:mcp:auth:audienceBound", "true");
660
+ }
661
+ const protocolVersion = protocolVersionForConfig(serverConfig);
662
+ if (protocolVersion) {
663
+ addUniqueProperty(properties, "cdx:mcp:protocolVersion", protocolVersion);
664
+ }
665
+ const deprecatedFeatures = deprecatedFeaturesForConfig(
666
+ serverConfig,
667
+ transport,
668
+ );
669
+ if (deprecatedFeatures.length) {
670
+ addUniqueProperty(
671
+ properties,
672
+ "cdx:mcp:deprecatedFeatures",
673
+ deprecatedFeatures.join(","),
674
+ );
675
+ }
514
676
  if (
515
677
  serverConfig?.protectedResourceMetadata ||
516
678
  serverConfig?.resourceServerUrl ||
@@ -650,7 +812,9 @@ export const mcpConfigParser = {
650
812
  }
651
813
  let configJson;
652
814
  try {
653
- configJson = parseJsonLike(raw);
815
+ configJson = filePath.toLowerCase().endsWith(".toml")
816
+ ? toml.parse(raw)
817
+ : parseJsonLike(raw);
654
818
  } catch {
655
819
  continue;
656
820
  }
@@ -6,6 +6,17 @@ const PROVIDER_TEXT_PATTERNS = [
6
6
  ["deepseek", /\bdeepseek\b/i],
7
7
  ["ollama", /\bollama\b/i],
8
8
  ["groq", /\bgroq\b/i],
9
+ ["xai", /\bgrok\b|\bx\.ai\b/i],
10
+ ["openrouter", /\bopenrouter\b/i],
11
+ // "bedrock" alone is also a WordPress stack and a Minecraft edition, so
12
+ // require AWS context before attributing it to Amazon Bedrock.
13
+ ["aws-bedrock", /\b(?:aws|amazon)[\s-]?bedrock\b|\bbedrock[\s-]?runtime\b/i],
14
+ ["cerebras", /\bcerebras\b/i],
15
+ // Drop the bare "kimi" alias (a common given name); keep the distinctive
16
+ // "moonshot" vendor token.
17
+ ["moonshot", /\bmoonshot\b/i],
18
+ ["zhipu", /\bzhipu\b|\bglm-[0-9]/i],
19
+ ["qwen", /\bqwen\b|\bdashscope\b/i],
9
20
  ];
10
21
 
11
22
  const INLINE_CREDENTIAL_PATTERNS = [
@@ -0,0 +1,389 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { basename } from "node:path";
3
+
4
+ import { sanitizeBomPropertyValue } from "../core/propertySanitizer.js";
5
+ import { parseJsonLike } from "./jsonLike.js";
6
+ import { isLocalHost, sanitizeMcpRefToken } from "./mcpDiscovery.js";
7
+ import { tryBuildPurl } from "./purl.js";
8
+ import { scanTextForHiddenUnicode } from "./unicodeScan.js";
9
+
10
+ const SERVER_MANIFEST_PATTERNS = ["server.json", "**/server.json"];
11
+
12
+ // registryType values understood by the official MCP Registry, mapped to the
13
+ // package-url ecosystem cdxgen uses when a component purl can be built.
14
+ const REGISTRY_TYPE_PURL_TYPES = {
15
+ npm: "npm",
16
+ pypi: "pypi",
17
+ nuget: "nuget",
18
+ oci: "oci",
19
+ mcpb: undefined,
20
+ };
21
+
22
+ function addUniqueProperty(properties, name, value) {
23
+ const sanitizedValue = sanitizeBomPropertyValue(name, value);
24
+ if (
25
+ sanitizedValue === undefined ||
26
+ sanitizedValue === null ||
27
+ sanitizedValue === ""
28
+ ) {
29
+ return;
30
+ }
31
+ if (
32
+ properties.some(
33
+ (property) =>
34
+ property.name === name && property.value === String(sanitizedValue),
35
+ )
36
+ ) {
37
+ return;
38
+ }
39
+ properties.push({ name, value: String(sanitizedValue) });
40
+ }
41
+
42
+ /**
43
+ * Determine whether a parsed object looks like an MCP Registry server manifest.
44
+ *
45
+ * The heuristic keeps unrelated `server.json` files (for example dev-server
46
+ * configuration) out of the MCP inventory by requiring the manifest to carry a
47
+ * server name plus at least one `packages[]` or `remotes[]` entry, or an
48
+ * explicit MCP Registry `$schema` reference.
49
+ *
50
+ * @param {Object} manifest Parsed JSON object
51
+ * @returns {boolean} true when the object resembles a registry manifest
52
+ */
53
+ function looksLikeServerManifest(manifest) {
54
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
55
+ return false;
56
+ }
57
+ const schema = String(manifest.$schema || "").toLowerCase();
58
+ if (schema.includes("modelcontextprotocol")) {
59
+ return true;
60
+ }
61
+ const hasName = typeof manifest.name === "string" && manifest.name.length > 0;
62
+ const hasPackages =
63
+ Array.isArray(manifest.packages) && manifest.packages.length > 0;
64
+ const hasRemotes =
65
+ Array.isArray(manifest.remotes) && manifest.remotes.length > 0;
66
+ return hasName && (hasPackages || hasRemotes);
67
+ }
68
+
69
+ /**
70
+ * Build a package-url string for a registry package entry when the
71
+ * registryType maps to a supported ecosystem and enough coordinates exist.
72
+ *
73
+ * @param {Object} pkg Registry package entry
74
+ * @returns {string|undefined} purl string, or undefined when not derivable
75
+ */
76
+ function purlForPackage(pkg) {
77
+ const registryType = String(pkg?.registryType || pkg?.registry_type || "")
78
+ .trim()
79
+ .toLowerCase();
80
+ const purlType = REGISTRY_TYPE_PURL_TYPES[registryType];
81
+ if (!purlType) {
82
+ return undefined;
83
+ }
84
+ const identifier = String(pkg?.identifier || pkg?.name || "").trim();
85
+ if (!identifier) {
86
+ return undefined;
87
+ }
88
+ const version = pkg?.version ? String(pkg.version) : undefined;
89
+ let namespace;
90
+ let name = identifier;
91
+ const parts = { type: purlType, version };
92
+ if (purlType === "npm" && identifier.startsWith("@")) {
93
+ const [scope, unscoped] = identifier.split("/");
94
+ namespace = scope;
95
+ name = unscoped || identifier;
96
+ } else if (purlType === "pypi") {
97
+ // PyPI purls require the normalized project name: lowercase with runs of
98
+ // [-_.] collapsed to a single hyphen.
99
+ name = identifier.toLowerCase().replace(/[-_.]+/gu, "-");
100
+ } else if (purlType === "oci") {
101
+ // OCI purls are only meaningful with a lowercase name, a digest version,
102
+ // and a repository_url qualifier. A tag-only reference is not resolvable,
103
+ // so skip the purl rather than emit a misleading one.
104
+ const digest =
105
+ pkg?.digest || (version?.startsWith("sha256:") ? version : undefined);
106
+ const repositoryUrl = pkg?.registryBaseUrl || pkg?.repository_url;
107
+ if (!digest || !repositoryUrl) {
108
+ return undefined;
109
+ }
110
+ name = identifier.toLowerCase().split("/").pop();
111
+ parts.version = digest;
112
+ parts.qualifiers = { repository_url: String(repositoryUrl) };
113
+ }
114
+ parts.namespace = namespace;
115
+ parts.name = name;
116
+ return tryBuildPurl(parts)?.toString() || undefined;
117
+ }
118
+
119
+ /**
120
+ * Convert one registry package entry into a CycloneDX component describing the
121
+ * distributable that hosts the MCP server.
122
+ *
123
+ * @param {string} filePath Manifest path
124
+ * @param {string} serverName MCP server name
125
+ * @param {Object} pkg Registry package entry
126
+ * @param {number} index Package index for stable identity
127
+ * @returns {Object|undefined} component or undefined when not usable
128
+ */
129
+ function componentForPackage(filePath, serverName, pkg, index) {
130
+ const registryType = String(pkg?.registryType || pkg?.registry_type || "")
131
+ .trim()
132
+ .toLowerCase();
133
+ const identifier = String(pkg?.identifier || pkg?.name || "").trim();
134
+ if (!registryType && !identifier) {
135
+ return undefined;
136
+ }
137
+ const purl = purlForPackage(pkg);
138
+ const properties = [
139
+ { name: "internal:SrcFile", value: filePath },
140
+ { name: "cdx:mcp:inventorySource", value: "server-manifest" },
141
+ { name: "cdx:registry:server:packageRegistryType", value: registryType },
142
+ ];
143
+ if (pkg?.transport?.type || pkg?.transport) {
144
+ addUniqueProperty(
145
+ properties,
146
+ "cdx:registry:server:packageTransport",
147
+ typeof pkg.transport === "string" ? pkg.transport : pkg.transport?.type,
148
+ );
149
+ }
150
+ // Record only the counts and field names the server will be handed at launch,
151
+ // never their values. These describe the launch surface without leaking
152
+ // secrets that a registry manifest may reference through them.
153
+ const secretInputFields = new Set();
154
+ for (const [inputKey, property] of [
155
+ ["environmentVariables", "cdx:registry:server:environmentVariableCount"],
156
+ ["runtimeArguments", "cdx:registry:server:runtimeArgumentCount"],
157
+ ["packageArguments", "cdx:registry:server:packageArgumentCount"],
158
+ ]) {
159
+ const entries = pkg?.[inputKey];
160
+ if (Array.isArray(entries) && entries.length) {
161
+ addUniqueProperty(properties, property, String(entries.length));
162
+ for (const entry of entries) {
163
+ const fieldName = entry?.name || entry?.variable;
164
+ if (
165
+ entry?.isSecret === true ||
166
+ entry?.secret === true ||
167
+ (typeof fieldName === "string" &&
168
+ /(token|secret|password|api[_-]?key|credential)/iu.test(fieldName))
169
+ ) {
170
+ secretInputFields.add(String(fieldName || "secret"));
171
+ }
172
+ }
173
+ }
174
+ }
175
+ if (secretInputFields.size) {
176
+ addUniqueProperty(
177
+ properties,
178
+ "cdx:registry:server:secretInputCount",
179
+ String(secretInputFields.size),
180
+ );
181
+ }
182
+ const component = {
183
+ "bom-ref":
184
+ purl ||
185
+ `urn:mcp:server-package:${sanitizeMcpRefToken(serverName)}:${sanitizeMcpRefToken(identifier || registryType)}:${index}`,
186
+ type: "application",
187
+ name: identifier || `${serverName}-package-${index}`,
188
+ properties,
189
+ };
190
+ if (purl) {
191
+ component.purl = purl;
192
+ }
193
+ if (pkg?.version) {
194
+ component.version = String(pkg.version);
195
+ }
196
+ return component;
197
+ }
198
+
199
+ /**
200
+ * Convert one registry remote entry into a CycloneDX service describing the
201
+ * remotely hosted MCP endpoint.
202
+ *
203
+ * @param {string} filePath Manifest path
204
+ * @param {string} serverName MCP server name
205
+ * @param {Object} remote Registry remote entry
206
+ * @param {number} index Remote index for stable identity
207
+ * @returns {Object|undefined} service or undefined when no URL is present
208
+ */
209
+ function serviceForRemote(filePath, serverName, remote, index) {
210
+ const url = typeof remote?.url === "string" ? remote.url : undefined;
211
+ if (!url) {
212
+ return undefined;
213
+ }
214
+ let hostname;
215
+ try {
216
+ hostname = new URL(url).hostname;
217
+ } catch {
218
+ return undefined;
219
+ }
220
+ const transport = String(remote?.type || remote?.transport || "")
221
+ .trim()
222
+ .toLowerCase();
223
+ const exposureType = isLocalHost(hostname)
224
+ ? "local-only"
225
+ : "networked-public";
226
+ const properties = [
227
+ { name: "internal:SrcFile", value: filePath },
228
+ { name: "cdx:mcp:serviceType", value: "registry-remote" },
229
+ { name: "cdx:mcp:inventorySource", value: "server-manifest" },
230
+ { name: "cdx:mcp:exposureType", value: exposureType },
231
+ // A remote server has no resolvable local package, so its composition
232
+ // cannot be verified from the manifest alone.
233
+ { name: "cdx:mcp:composition", value: "unknown" },
234
+ { name: "cdx:mcp:reviewNeeded", value: "true" },
235
+ ];
236
+ if (transport) {
237
+ addUniqueProperty(
238
+ properties,
239
+ "cdx:mcp:transport",
240
+ transport.includes("sse")
241
+ ? "sse"
242
+ : transport.includes("stream") || transport.includes("http")
243
+ ? "streamable-http"
244
+ : transport,
245
+ );
246
+ }
247
+ return {
248
+ "bom-ref": `urn:service:mcp:${sanitizeMcpRefToken(serverName)}:remote:${index}`,
249
+ group: "mcp",
250
+ name: serverName,
251
+ endpoints: [url],
252
+ properties,
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Build the file component that represents the discovered manifest itself and
258
+ * summarizes the packages and remotes it declares.
259
+ *
260
+ * @param {string} filePath Manifest path
261
+ * @param {Object} manifest Parsed manifest
262
+ * @param {string} raw Raw file contents
263
+ * @param {number} packageCount Number of package components emitted
264
+ * @param {number} remoteCount Number of remote services emitted
265
+ * @returns {Object} file component
266
+ */
267
+ function manifestFileComponent(
268
+ filePath,
269
+ manifest,
270
+ raw,
271
+ packageCount,
272
+ remoteCount,
273
+ ) {
274
+ const properties = [
275
+ { name: "internal:SrcFile", value: filePath },
276
+ { name: "cdx:file:kind", value: "mcp-server-manifest" },
277
+ { name: "cdx:mcp:inventorySource", value: "server-manifest" },
278
+ {
279
+ name: "cdx:registry:server:packageCount",
280
+ value: String(packageCount),
281
+ },
282
+ { name: "cdx:registry:server:remoteCount", value: String(remoteCount) },
283
+ ];
284
+ addUniqueProperty(
285
+ properties,
286
+ "cdx:registry:server:name",
287
+ typeof manifest.name === "string" ? manifest.name : undefined,
288
+ );
289
+ addUniqueProperty(
290
+ properties,
291
+ "cdx:registry:server:version",
292
+ manifest.version ? String(manifest.version) : undefined,
293
+ );
294
+ const hiddenUnicodeScan = scanTextForHiddenUnicode(raw, { syntax: "json" });
295
+ if (hiddenUnicodeScan.hasHiddenUnicode) {
296
+ addUniqueProperty(properties, "cdx:file:hasHiddenUnicode", "true");
297
+ addUniqueProperty(
298
+ properties,
299
+ "cdx:file:hiddenUnicodeCodePoints",
300
+ hiddenUnicodeScan.codePoints.join(","),
301
+ );
302
+ }
303
+ return {
304
+ "bom-ref": `file:${filePath}`,
305
+ type: "file",
306
+ name: basename(filePath),
307
+ properties,
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Parser for MCP Registry server manifests (`server.json`).
313
+ *
314
+ * Converts each declared distributable package into a CycloneDX component
315
+ * (with a purl when the registry type maps to a supported ecosystem) and each
316
+ * remotely hosted endpoint into a service, alongside a file component that
317
+ * summarizes the manifest.
318
+ *
319
+ * @type {{id: string, patterns: string[], parse(files: string[], options?: Object): {components: Object[], services: Object[]}}}
320
+ */
321
+ export const mcpServerManifestParser = {
322
+ id: "mcp-server-manifest",
323
+ patterns: SERVER_MANIFEST_PATTERNS,
324
+ parse(files, _options = {}) {
325
+ const components = [];
326
+ const services = [];
327
+ for (const filePath of [...new Set(files || [])]) {
328
+ let raw;
329
+ try {
330
+ raw = readFileSync(filePath, "utf-8");
331
+ } catch {
332
+ continue;
333
+ }
334
+ let manifest;
335
+ try {
336
+ manifest = parseJsonLike(raw);
337
+ } catch {
338
+ continue;
339
+ }
340
+ if (!looksLikeServerManifest(manifest)) {
341
+ continue;
342
+ }
343
+ const serverName =
344
+ typeof manifest.name === "string" && manifest.name.length
345
+ ? manifest.name
346
+ : basename(filePath);
347
+ const packageComponents = [];
348
+ const packages = Array.isArray(manifest.packages)
349
+ ? manifest.packages
350
+ : [];
351
+ for (let index = 0; index < packages.length; index++) {
352
+ const component = componentForPackage(
353
+ filePath,
354
+ serverName,
355
+ packages[index],
356
+ index,
357
+ );
358
+ if (component) {
359
+ packageComponents.push(component);
360
+ }
361
+ }
362
+ const remoteServices = [];
363
+ const remotes = Array.isArray(manifest.remotes) ? manifest.remotes : [];
364
+ for (let index = 0; index < remotes.length; index++) {
365
+ const service = serviceForRemote(
366
+ filePath,
367
+ serverName,
368
+ remotes[index],
369
+ index,
370
+ );
371
+ if (service) {
372
+ remoteServices.push(service);
373
+ }
374
+ }
375
+ components.push(
376
+ manifestFileComponent(
377
+ filePath,
378
+ manifest,
379
+ raw,
380
+ packageComponents.length,
381
+ remoteServices.length,
382
+ ),
383
+ );
384
+ components.push(...packageComponents);
385
+ services.push(...remoteServices);
386
+ }
387
+ return { components, services };
388
+ },
389
+ };