@cdxgen/cdxgen 13.1.0 → 13.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +8 -7
  2. package/bin/cdxgen.js +16 -3
  3. package/bin/evinse.js +2 -0
  4. package/data/crypto-oid.json +88 -12
  5. package/data/remediations.json +23 -0
  6. package/data/rules/ai-bom.yaml +78 -0
  7. package/data/rules/cargo-rust.yaml +132 -0
  8. package/data/rules/mcp-servers.yaml +122 -0
  9. package/lib/audit/targets.js +6 -1
  10. package/lib/cli/bomAssembly.js +12 -0
  11. package/lib/cli/index.js +192 -1
  12. package/lib/cli/jsBom.js +59 -2
  13. package/lib/cli/jvmBom.js +22 -0
  14. package/lib/cli/managedBom.js +152 -0
  15. package/lib/cli/nativeBom.js +798 -1
  16. package/lib/core/activity.js +120 -2
  17. package/lib/core/env.js +39 -1
  18. package/lib/core/userAgent.js +151 -0
  19. package/lib/ecosystems/cargoCache.js +364 -0
  20. package/lib/ecosystems/caxa.js +13 -5
  21. package/lib/ecosystems/cppEvidence.js +40 -2
  22. package/lib/ecosystems/ecosystems.js +241 -91
  23. package/lib/ecosystems/golem.js +11 -1
  24. package/lib/ecosystems/gradleutils.js +143 -2
  25. package/lib/ecosystems/kosi.js +1256 -0
  26. package/lib/ecosystems/parsers-crystal.js +179 -0
  27. package/lib/ecosystems/parsers-dotnet.js +69 -39
  28. package/lib/ecosystems/parsers-elm.js +816 -0
  29. package/lib/ecosystems/parsers-gleam.js +1 -1
  30. package/lib/ecosystems/parsers-julia.js +268 -0
  31. package/lib/ecosystems/parsers-lua.js +218 -0
  32. package/lib/ecosystems/parsers-misc.js +172 -0
  33. package/lib/ecosystems/parsers-nim.js +221 -0
  34. package/lib/ecosystems/parsers-opam.js +180 -0
  35. package/lib/ecosystems/parsers-perl.js +277 -0
  36. package/lib/ecosystems/parsers-python.js +1 -1
  37. package/lib/ecosystems/parsers-r.js +148 -0
  38. package/lib/ecosystems/parsers-rebar.js +411 -0
  39. package/lib/ecosystems/parsers-rust.js +932 -17
  40. package/lib/ecosystems/parsers-spack.js +204 -0
  41. package/lib/ecosystems/parsers-stack.js +264 -0
  42. package/lib/ecosystems/parsers-terraform.js +194 -0
  43. package/lib/ecosystems/rusi.js +126 -6
  44. package/lib/evinser/evinser.js +111 -1
  45. package/lib/helpers/agenticToolutils.js +925 -0
  46. package/lib/inventory/agentFormulationParser.js +57 -4
  47. package/lib/inventory/aiCollector.js +504 -10
  48. package/lib/inventory/aiInventory.js +12 -0
  49. package/lib/inventory/analyzer.js +488 -30
  50. package/lib/inventory/astWorker.js +6 -0
  51. package/lib/inventory/auditCategories.js +8 -0
  52. package/lib/inventory/cbomutils.js +30 -0
  53. package/lib/inventory/cdxrs.js +7 -2
  54. package/lib/inventory/communityAiConfigParser.js +215 -2
  55. package/lib/inventory/dosai.js +77 -0
  56. package/lib/inventory/envcontext.js +9 -6
  57. package/lib/inventory/evidenceUtils.js +13 -0
  58. package/lib/inventory/fetchBatch.js +74 -2
  59. package/lib/inventory/fetchRate.js +74 -11
  60. package/lib/inventory/forgeEnricher.js +3 -2
  61. package/lib/inventory/jvmToolEnv.js +6 -5
  62. package/lib/inventory/mcpConfigParser.js +166 -2
  63. package/lib/inventory/mcpDiscovery.js +11 -0
  64. package/lib/inventory/mcpServerManifest.js +389 -0
  65. package/lib/inventory/mcpbBundle.js +419 -0
  66. package/lib/inventory/plugins.js +3 -0
  67. package/lib/inventory/protobom.js +54 -88
  68. package/lib/inventory/protobomLoader.js +4 -0
  69. package/lib/inventory/rustFormulationParser.js +1 -1
  70. package/lib/inventory/source.js +10 -4
  71. package/lib/inventory/stylePackageRefs.js +41 -0
  72. package/lib/inventory/svelteUtils.js +524 -0
  73. package/lib/parsers/huggingfaceUtils.js +15 -7
  74. package/lib/parsers/onnx.js +294 -0
  75. package/lib/parsers/safetensors.js +114 -0
  76. package/lib/stages/postgen/introspection/markers.js +29 -6
  77. package/lib/stages/postgen/postgen.js +169 -1
  78. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  79. package/package.json +25 -25
  80. package/types/lib/audit/targets.d.ts.map +1 -1
  81. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  82. package/types/lib/cli/index.d.ts +2 -2
  83. package/types/lib/cli/index.d.ts.map +1 -1
  84. package/types/lib/cli/jsBom.d.ts +23 -0
  85. package/types/lib/cli/jsBom.d.ts.map +1 -1
  86. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  87. package/types/lib/cli/managedBom.d.ts +33 -0
  88. package/types/lib/cli/managedBom.d.ts.map +1 -1
  89. package/types/lib/cli/nativeBom.d.ts +88 -8
  90. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  91. package/types/lib/core/activity.d.ts.map +1 -1
  92. package/types/lib/core/env.d.ts +21 -0
  93. package/types/lib/core/env.d.ts.map +1 -1
  94. package/types/lib/core/userAgent.d.ts +56 -0
  95. package/types/lib/core/userAgent.d.ts.map +1 -0
  96. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  97. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  98. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  99. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  100. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  101. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  102. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  103. package/types/lib/ecosystems/gradleutils.d.ts +20 -0
  104. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  105. package/types/lib/ecosystems/kosi.d.ts +73 -0
  106. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  107. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  108. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  109. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  110. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  111. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  112. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  113. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  114. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  115. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  116. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  117. package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
  118. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  119. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  120. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  121. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  122. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  123. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  124. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  125. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  126. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  127. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  128. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  129. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  130. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  131. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  132. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  133. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  134. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  135. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  136. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  137. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  138. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  139. package/types/lib/evinser/evinser.d.ts +29 -0
  140. package/types/lib/evinser/evinser.d.ts.map +1 -1
  141. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  142. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  143. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  144. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  145. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  146. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  147. package/types/lib/inventory/auditCategories.d.ts +4 -0
  148. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  149. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  150. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  151. package/types/lib/inventory/dosai.d.ts +23 -0
  152. package/types/lib/inventory/dosai.d.ts.map +1 -1
  153. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  154. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  155. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  156. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  157. package/types/lib/inventory/fetchRate.d.ts +9 -6
  158. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  159. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  160. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  161. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  162. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  163. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  164. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  165. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  166. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  167. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  168. package/types/lib/inventory/plugins.d.ts.map +1 -1
  169. package/types/lib/inventory/protobom.d.ts +18 -13
  170. package/types/lib/inventory/protobom.d.ts.map +1 -1
  171. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  172. package/types/lib/inventory/source.d.ts.map +1 -1
  173. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  174. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  175. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  176. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  177. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  178. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  179. package/types/lib/parsers/onnx.d.ts +28 -0
  180. package/types/lib/parsers/onnx.d.ts.map +1 -0
  181. package/types/lib/parsers/safetensors.d.ts +22 -0
  182. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  183. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  184. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  185. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  186. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
@@ -2,18 +2,21 @@
2
2
  * Per-host rate policy for batched registry fetches.
3
3
  *
4
4
  * This is the single source of truth for the JS transport's concurrency and
5
- * minimum-interval decisions. The numbers are shared with the Rust transport
5
+ * minimum-interval decisions. Every number is shared with the Rust transport
6
6
  * (`cdxgen-plugins-bin/thirdparty/cdxrs/src/fetch/rate.rs`); a change to one
7
7
  * side must be reflected in the other, and `fetchRate.poku.js` pins them so a
8
- * drift is visible.
8
+ * drift is visible. crates.io and package.elm-lang.org are routed so that their
9
+ * requests never reach the Rust transport, which makes this table the one that
10
+ * governs them in practice — their rows are mirrored there regardless, because
11
+ * two tables that disagree are a bug waiting for the routing to change.
9
12
  *
10
13
  * Why a per-host table rather than a single global interval: a single-registry
11
14
  * project — which is to say, almost every npm project — has exactly one host,
12
15
  * so a conservative per-host cap *is* the global cap. Applying crates.io's
13
- * 250 ms to every host caps an npm run at 4 requests per second, which is
14
- * slower than the serial JS path once connection reuse is taken into account.
15
- * Hosts that publish a limit get an interval; hosts that do not are bounded by
16
- * the per-host concurrency cap alone, and by whatever `Retry-After` they send.
16
+ * one-per-second policy to every host would cap an npm run at one request a
17
+ * second, orders of magnitude slower than that registry asks for. Hosts that
18
+ * publish a limit get an interval; hosts that do not are bounded by the
19
+ * per-host concurrency cap alone, and by whatever `Retry-After` they send.
17
20
  */
18
21
 
19
22
  import { readEnvironmentVariable } from "../core/activity.js";
@@ -48,13 +51,18 @@ import { readEnvironmentVariable } from "../core/activity.js";
48
51
  * Suffix match is used so `index.crates.io` inherits `crates.io`.
49
52
  */
50
53
  const HOST_POLICIES = [
51
- // crates.io's published budget is per IP and does not improve with a token.
54
+ // crates.io's crawler policy asks for at most one request per second, made
55
+ // serially. It is a condition of use rather than a throughput hint, so the
56
+ // interval is a full second and the concurrency is 1: with more than one
57
+ // request in flight the gate would still average a second apiece but would
58
+ // deliver them in bursts, which is the thing the policy rules out. The
59
+ // budget is per IP and does not improve with a token.
52
60
  {
53
61
  host: "crates.io",
54
- anonInterval: 250,
55
- anonConcurrency: 4,
56
- authInterval: 250,
57
- authConcurrency: 4,
62
+ anonInterval: 1000,
63
+ anonConcurrency: 1,
64
+ authInterval: 1000,
65
+ authConcurrency: 1,
58
66
  },
59
67
  // GitHub: 60 req/h anonymous, 5000 req/h authenticated. The limit is an
60
68
  // hourly budget rather than a rate, so a batch of a few hundred lookups is
@@ -88,6 +96,61 @@ const HOST_POLICIES = [
88
96
  authInterval: 250,
89
97
  authConcurrency: 4,
90
98
  },
99
+ // package.elm-lang.org publishes no limit: its robots.txt carries no
100
+ // `Crawl-delay` and the registry has no terms of use or documented API. What
101
+ // it does have is a single community-run origin rather than a CDN, so it is
102
+ // held to the same gentle allowance as the other unpublished host in this
103
+ // table instead of the CDN default. Like crates.io, its requests are kept off
104
+ // the Rust transport to leave one gate in charge; rate.rs still carries the
105
+ // same row so the two tables state one policy.
106
+ // Maven Central publishes no numeric limit. What it publishes instead is a
107
+ // policy: consumption limits are enforced against aggregate traffic, the
108
+ // thresholds are unpublished and moving downward, and the named offenders are
109
+ // "CI/CD platforms, security scanners, and other services that generate
110
+ // outsized traffic" — which is cdxgen's exact job description. A block is an
111
+ // escalating ban of up to 30 days, not a per-request 429, so the cost of
112
+ // being slightly too fast is losing Maven resolution for a month rather than
113
+ // retrying a second later. That asymmetry, not a published number, is what
114
+ // sets these rows.
115
+ //
116
+ // repo1 is the artifact store: Cloudflare-fronted (`cf-cache-status` on every
117
+ // response), and a POM fetch that hits the edge cache never reaches Sonatype.
118
+ // It gets a real allowance — 100 ms and 8 in flight, so ~10 req/s sustained —
119
+ // which is half the CDN default and orders of magnitude below anything that
120
+ // would show up in the top few percent of Central's traffic.
121
+ {
122
+ host: "repo1.maven.org",
123
+ anonInterval: 100,
124
+ anonConcurrency: 8,
125
+ authInterval: 100,
126
+ authConcurrency: 8,
127
+ },
128
+ // The search API is the opposite: a Solr query against the origin, one
129
+ // request per unknown jar hash, cacheable by nobody. cdxgen already treats it
130
+ // as fragile — a single timeout or 429 disables jar search for the rest of
131
+ // the run — so the gate is a serial 500 ms (~2 req/s). A jar sweep stays
132
+ // usable while staying far below anything worth throttling.
133
+ {
134
+ host: "search.maven.org",
135
+ anonInterval: 500,
136
+ anonConcurrency: 1,
137
+ authInterval: 500,
138
+ authConcurrency: 1,
139
+ },
140
+ {
141
+ host: "central.sonatype.com",
142
+ anonInterval: 500,
143
+ anonConcurrency: 1,
144
+ authInterval: 500,
145
+ authConcurrency: 1,
146
+ },
147
+ {
148
+ host: "package.elm-lang.org",
149
+ anonInterval: 250,
150
+ anonConcurrency: 4,
151
+ authInterval: 250,
152
+ authConcurrency: 4,
153
+ },
91
154
  ];
92
155
 
93
156
  /**
@@ -1,4 +1,5 @@
1
1
  import { cdxgenAgent, readEnvironmentVariable } from "../core/activity.js";
2
+ import { getUserAgent } from "../core/userAgent.js";
2
3
  import { getOriginUrl } from "./envcontext.js";
3
4
  import { prefetchJson } from "./fetchBatch.js";
4
5
 
@@ -123,7 +124,7 @@ export async function enrichFromForge(dir, aiCommits, options = {}) {
123
124
  const githubHeaders = {
124
125
  Accept: "application/vnd.github+json",
125
126
  Authorization: `Bearer ${githubToken}`,
126
- "User-Agent": "cdxgen-oversight",
127
+ "User-Agent": getUserAgent("api.github.com"),
127
128
  };
128
129
  const pullsUrlFor = (commit) =>
129
130
  `https://api.github.com/repos/${owner}/${repo}/commits/${commit.hash}/pulls`;
@@ -214,7 +215,7 @@ export async function enrichFromForge(dir, aiCommits, options = {}) {
214
215
  const commitsToQuery = aiCommits.slice(0, 5);
215
216
  const gitlabHeaders = {
216
217
  "PRIVATE-TOKEN": gitlabToken,
217
- "User-Agent": "cdxgen-oversight",
218
+ "User-Agent": getUserAgent("gitlab.com"),
218
219
  };
219
220
  const mrUrlFor = (commit) =>
220
221
  `https://gitlab.com/api/v4/projects/${projectEncoded}/repository/commits/${commit.hash}/merge_requests`;
@@ -45,10 +45,11 @@ const JVM_BUILD_TOOL_CANDIDATES = ["maven", "gradle", "sbt", "scala"];
45
45
 
46
46
  /**
47
47
  * Anchored validator for sdkman version identifiers such as `3.9.9`,
48
- * `4.0.0-rc-5`, `8.14`, or `21.0.7-tem`. Versions are interpolated into a
49
- * shell command by the sdkman installer, so every identifier must be a single
50
- * safe token: it must start with a digit and may only contain digits, ascii
51
- * letters, dots, and hyphens.
48
+ * `4.0.0-rc-5`, `8.14`, `21.0.7-tem`, or `26.0.2+1.1-tem`. Versions are
49
+ * interpolated into a shell command by the sdkman installer, so every
50
+ * identifier must be a single safe token: it must start with a digit and may
51
+ * only contain digits, ascii letters, dots, hyphens, and plus signs (`+` has
52
+ * no shell meaning and sdkman uses it in Temurin respin identifiers).
52
53
  *
53
54
  * Every separator must be followed by at least one alphanumeric character, and
54
55
  * a single repeated group covers both the dotted numeric part and the vendor
@@ -63,7 +64,7 @@ export function isValidSdkmanVersion(version) {
63
64
  typeof version === "string" &&
64
65
  version.length > 0 &&
65
66
  version.length <= MAX_VERSION_TOKEN_LENGTH &&
66
- /^\d+(?:[-.][0-9A-Za-z]+)*$/.test(version)
67
+ /^\d+(?:[-.+][0-9A-Za-z]+)*$/.test(version)
67
68
  );
68
69
  }
69
70
 
@@ -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 = [