@cyclonedx/cdxgen 12.3.0 → 12.3.2

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 (121) hide show
  1. package/README.md +15 -5
  2. package/bin/audit.js +7 -0
  3. package/bin/cdxgen.js +241 -81
  4. package/bin/repl.js +138 -0
  5. package/data/rules/ai-agent-governance.yaml +249 -0
  6. package/data/rules/dependency-sources.yaml +41 -0
  7. package/data/rules/mcp-servers.yaml +304 -0
  8. package/data/rules/package-integrity.yaml +123 -0
  9. package/lib/audit/index.js +353 -29
  10. package/lib/audit/index.poku.js +247 -7
  11. package/lib/audit/reporters.js +26 -0
  12. package/lib/audit/scoring.js +262 -13
  13. package/lib/audit/scoring.poku.js +179 -0
  14. package/lib/audit/targets.js +391 -2
  15. package/lib/audit/targets.poku.js +416 -3
  16. package/lib/cli/index.js +588 -45
  17. package/lib/cli/index.poku.js +735 -1
  18. package/lib/evinser/evinser.js +8 -5
  19. package/lib/helpers/agentFormulationParser.js +318 -0
  20. package/lib/helpers/aiInventory.js +262 -0
  21. package/lib/helpers/aiInventory.poku.js +111 -0
  22. package/lib/helpers/analyzer.js +1769 -0
  23. package/lib/helpers/analyzer.poku.js +284 -3
  24. package/lib/helpers/auditCategories.js +76 -0
  25. package/lib/helpers/ciParsers/githubActions.js +140 -16
  26. package/lib/helpers/ciParsers/githubActions.poku.js +110 -0
  27. package/lib/helpers/communityAiConfigParser.js +672 -0
  28. package/lib/helpers/communityAiConfigParser.poku.js +63 -0
  29. package/lib/helpers/depsUtils.js +108 -0
  30. package/lib/helpers/depsUtils.poku.js +72 -1
  31. package/lib/helpers/display.js +325 -3
  32. package/lib/helpers/display.poku.js +301 -0
  33. package/lib/helpers/formulationParsers.js +28 -0
  34. package/lib/helpers/formulationParsers.poku.js +504 -1
  35. package/lib/helpers/jsonLike.js +102 -0
  36. package/lib/helpers/jsonLike.poku.js +34 -0
  37. package/lib/helpers/mcp.js +248 -0
  38. package/lib/helpers/mcp.poku.js +101 -0
  39. package/lib/helpers/mcpConfigParser.js +656 -0
  40. package/lib/helpers/mcpConfigParser.poku.js +126 -0
  41. package/lib/helpers/mcpDiscovery.js +84 -0
  42. package/lib/helpers/mcpDiscovery.poku.js +21 -0
  43. package/lib/helpers/protobom.js +3 -3
  44. package/lib/helpers/provenanceUtils.js +29 -4
  45. package/lib/helpers/provenanceUtils.poku.js +29 -3
  46. package/lib/helpers/registryProvenance.js +210 -0
  47. package/lib/helpers/registryProvenance.poku.js +144 -0
  48. package/lib/helpers/rustFormulationParser.js +330 -0
  49. package/lib/helpers/source.js +21 -2
  50. package/lib/helpers/source.poku.js +38 -0
  51. package/lib/helpers/utils.js +1331 -83
  52. package/lib/helpers/utils.poku.js +599 -188
  53. package/lib/helpers/vsixutils.js +12 -4
  54. package/lib/helpers/vsixutils.poku.js +34 -0
  55. package/lib/managers/binary.js +36 -12
  56. package/lib/managers/binary.poku.js +68 -0
  57. package/lib/managers/docker.js +59 -9
  58. package/lib/managers/docker.poku.js +61 -0
  59. package/lib/managers/piptree.js +12 -7
  60. package/lib/managers/piptree.poku.js +44 -0
  61. package/lib/stages/postgen/annotator.js +2 -1
  62. package/lib/stages/postgen/annotator.poku.js +15 -0
  63. package/lib/stages/postgen/auditBom.js +20 -6
  64. package/lib/stages/postgen/auditBom.poku.js +694 -1
  65. package/lib/stages/postgen/postgen.js +262 -11
  66. package/lib/stages/postgen/postgen.poku.js +306 -2
  67. package/lib/stages/postgen/ruleEngine.js +49 -1
  68. package/lib/stages/postgen/spdxConverter.poku.js +70 -0
  69. package/lib/stages/pregen/pregen.js +6 -4
  70. package/package.json +1 -1
  71. package/types/bin/repl.d.ts.map +1 -1
  72. package/types/lib/audit/index.d.ts.map +1 -1
  73. package/types/lib/audit/reporters.d.ts.map +1 -1
  74. package/types/lib/audit/scoring.d.ts.map +1 -1
  75. package/types/lib/audit/targets.d.ts +12 -0
  76. package/types/lib/audit/targets.d.ts.map +1 -1
  77. package/types/lib/cli/index.d.ts +2 -8
  78. package/types/lib/cli/index.d.ts.map +1 -1
  79. package/types/lib/evinser/evinser.d.ts.map +1 -1
  80. package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
  81. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
  82. package/types/lib/helpers/aiInventory.d.ts +23 -0
  83. package/types/lib/helpers/aiInventory.d.ts.map +1 -0
  84. package/types/lib/helpers/analyzer.d.ts +10 -0
  85. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  86. package/types/lib/helpers/auditCategories.d.ts +12 -0
  87. package/types/lib/helpers/auditCategories.d.ts.map +1 -0
  88. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  89. package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
  90. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
  91. package/types/lib/helpers/depsUtils.d.ts +8 -0
  92. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  93. package/types/lib/helpers/display.d.ts +17 -1
  94. package/types/lib/helpers/display.d.ts.map +1 -1
  95. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  96. package/types/lib/helpers/jsonLike.d.ts +4 -0
  97. package/types/lib/helpers/jsonLike.d.ts.map +1 -0
  98. package/types/lib/helpers/mcp.d.ts +29 -0
  99. package/types/lib/helpers/mcp.d.ts.map +1 -0
  100. package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
  101. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
  102. package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
  103. package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
  104. package/types/lib/helpers/provenanceUtils.d.ts +5 -3
  105. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -1
  106. package/types/lib/helpers/registryProvenance.d.ts +9 -0
  107. package/types/lib/helpers/registryProvenance.d.ts.map +1 -1
  108. package/types/lib/helpers/rustFormulationParser.d.ts +17 -0
  109. package/types/lib/helpers/rustFormulationParser.d.ts.map +1 -0
  110. package/types/lib/helpers/source.d.ts.map +1 -1
  111. package/types/lib/helpers/utils.d.ts +31 -1
  112. package/types/lib/helpers/utils.d.ts.map +1 -1
  113. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  114. package/types/lib/managers/binary.d.ts.map +1 -1
  115. package/types/lib/managers/docker.d.ts.map +1 -1
  116. package/types/lib/managers/piptree.d.ts.map +1 -1
  117. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  118. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  119. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  120. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  121. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
@@ -0,0 +1,656 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { basename } from "node:path";
3
+
4
+ import { parseJsonLike } from "./jsonLike.js";
5
+ import { classifyMcpReference } from "./mcp.js";
6
+ import {
7
+ credentialIndicatorsForText,
8
+ isLocalHost,
9
+ providerNamesForText,
10
+ sanitizeMcpRefToken,
11
+ } from "./mcpDiscovery.js";
12
+ import { scanTextForHiddenUnicode } from "./unicodeScan.js";
13
+
14
+ const MCP_CONFIG_PATTERNS = [
15
+ ".mcp.json",
16
+ "**/.mcp.json",
17
+ "mcp.json",
18
+ "**/mcp.json",
19
+ ".vscode/mcp.json",
20
+ "**/.vscode/mcp.json",
21
+ ".cursor/mcp.json",
22
+ "**/.cursor/mcp.json",
23
+ "claude_desktop_config.json",
24
+ "**/claude_desktop_config.json",
25
+ "opencode.json",
26
+ "**/opencode.json",
27
+ "opencode.jsonc",
28
+ "**/opencode.jsonc",
29
+ ];
30
+
31
+ const LOCAL_TUNNEL_HOST_PATTERNS = [
32
+ /\.ngrok(?:-free)?\.app$/iu,
33
+ /\.ngrok\.io$/iu,
34
+ /\.trycloudflare\.com$/iu,
35
+ /\.localhost\.run$/iu,
36
+ /\.serveo\.net$/iu,
37
+ ];
38
+ const SECRET_FIELD_NAME_PATTERN =
39
+ /(token|secret|password|api[_-]?key|client[_-]?secret|authorization)/iu;
40
+ const ENV_REFERENCE_PATTERN = /(?:\$\{?[A-Z0-9_]+\}?|%[A-Z0-9_]+%)/u;
41
+
42
+ function addUniqueProperty(properties, name, value) {
43
+ if (value === undefined || value === null || value === "") {
44
+ return;
45
+ }
46
+ if (properties.some((prop) => prop.name === name && prop.value === value)) {
47
+ return;
48
+ }
49
+ properties.push({ name, value: String(value) });
50
+ }
51
+
52
+ function normalizeFilePath(filePath) {
53
+ return filePath.replaceAll("\\", "/");
54
+ }
55
+
56
+ function configFormat(filePath) {
57
+ const lowerPath = normalizeFilePath(filePath).toLowerCase();
58
+ if (lowerPath.endsWith("claude_desktop_config.json")) {
59
+ return "claude-desktop";
60
+ }
61
+ if (lowerPath.endsWith(".mcp.json")) {
62
+ return "dot-mcp-json";
63
+ }
64
+ if (
65
+ lowerPath.endsWith("opencode.json") ||
66
+ lowerPath.endsWith("opencode.jsonc")
67
+ ) {
68
+ return "opencode";
69
+ }
70
+ if (
71
+ lowerPath.includes("/.vscode/") ||
72
+ lowerPath.endsWith(".vscode/mcp.json")
73
+ ) {
74
+ return "vscode";
75
+ }
76
+ if (
77
+ lowerPath.includes("/.cursor/") ||
78
+ lowerPath.endsWith(".cursor/mcp.json")
79
+ ) {
80
+ return "cursor";
81
+ }
82
+ return "generic-mcp-json";
83
+ }
84
+
85
+ function syntaxForFile(filePath) {
86
+ const lowerPath = filePath.toLowerCase();
87
+ return lowerPath.endsWith(".json") || lowerPath.endsWith(".jsonc")
88
+ ? "json"
89
+ : "text";
90
+ }
91
+
92
+ function extractServerMaps(config) {
93
+ const serverMaps = [];
94
+ for (const [key, value] of Object.entries(config || {})) {
95
+ if (["mcpServers", "context_servers", "servers", "mcp"].includes(key)) {
96
+ if (value && typeof value === "object" && !Array.isArray(value)) {
97
+ serverMaps.push({ configKey: key, servers: Object.entries(value) });
98
+ } else if (Array.isArray(value)) {
99
+ serverMaps.push({
100
+ configKey: key,
101
+ servers: value.map((entry, index) => [
102
+ entry?.name || `server-${index + 1}`,
103
+ entry,
104
+ ]),
105
+ });
106
+ }
107
+ }
108
+ }
109
+ return serverMaps;
110
+ }
111
+
112
+ function authHintsFromValue(value, hints = new Set()) {
113
+ const text = JSON.stringify(value || {});
114
+ if (!text || text === "{}") {
115
+ return hints;
116
+ }
117
+ if (/\bbearer\b|authorization/iu.test(text)) {
118
+ hints.add("bearer");
119
+ }
120
+ if (
121
+ /\boauth\b|authorization_endpoint|token_endpoint|issuer|registration_endpoint/iu.test(
122
+ text,
123
+ )
124
+ ) {
125
+ hints.add("oauth");
126
+ }
127
+ if (/\bapi[_ -]?key\b/iu.test(text)) {
128
+ hints.add("api-key");
129
+ }
130
+ if (/\btoken\b/iu.test(text)) {
131
+ hints.add("token");
132
+ }
133
+ return hints;
134
+ }
135
+
136
+ function isEnvReference(value) {
137
+ return typeof value === "string" && ENV_REFERENCE_PATTERN.test(value);
138
+ }
139
+
140
+ function detectInlineCredentialIndicators(value) {
141
+ if (typeof value !== "string" || isEnvReference(value)) {
142
+ return new Set();
143
+ }
144
+ return new Set(credentialIndicatorsForText(value));
145
+ }
146
+
147
+ function detectConfigCredentialSignals(serverConfig) {
148
+ const inlineIndicators = new Set();
149
+ const exposureFields = new Set();
150
+ const credentialRefs = new Set();
151
+ const envConfig = serverConfig?.env || serverConfig?.environment || {};
152
+ for (const [envKey, envValue] of Object.entries(envConfig)) {
153
+ if (typeof envValue === "string" && isEnvReference(envValue)) {
154
+ credentialRefs.add(envKey);
155
+ continue;
156
+ }
157
+ if (
158
+ SECRET_FIELD_NAME_PATTERN.test(envKey) ||
159
+ detectInlineCredentialIndicators(envValue).size
160
+ ) {
161
+ exposureFields.add(`env:${envKey}`);
162
+ detectInlineCredentialIndicators(envValue).forEach((item) => {
163
+ inlineIndicators.add(item);
164
+ });
165
+ if (
166
+ typeof envValue === "string" &&
167
+ !detectInlineCredentialIndicators(envValue).size
168
+ ) {
169
+ inlineIndicators.add("secret-env-value");
170
+ }
171
+ }
172
+ }
173
+ const args = Array.isArray(serverConfig?.args) ? serverConfig.args : [];
174
+ for (let index = 0; index < args.length; index++) {
175
+ const argValue = String(args[index] || "");
176
+ const priorArg = index > 0 ? String(args[index - 1] || "") : "";
177
+ const indicators = detectInlineCredentialIndicators(argValue);
178
+ const secretFlag =
179
+ SECRET_FIELD_NAME_PATTERN.test(argValue) ||
180
+ (priorArg.startsWith("--") && SECRET_FIELD_NAME_PATTERN.test(priorArg));
181
+ if (indicators.size || (secretFlag && !isEnvReference(argValue))) {
182
+ exposureFields.add(
183
+ priorArg.startsWith("--") ? `arg:${priorArg}` : `arg:${index}`,
184
+ );
185
+ indicators.forEach((item) => {
186
+ inlineIndicators.add(item);
187
+ });
188
+ if (secretFlag && !indicators.size) {
189
+ inlineIndicators.add("secret-arg-value");
190
+ }
191
+ }
192
+ if (isEnvReference(argValue)) {
193
+ credentialRefs.add(argValue);
194
+ }
195
+ }
196
+ for (const [headerName, headerValue] of Object.entries(
197
+ serverConfig?.headers || {},
198
+ )) {
199
+ if (
200
+ SECRET_FIELD_NAME_PATTERN.test(headerName) ||
201
+ detectInlineCredentialIndicators(headerValue).size
202
+ ) {
203
+ exposureFields.add(`header:${headerName}`);
204
+ detectInlineCredentialIndicators(headerValue).forEach((item) => {
205
+ inlineIndicators.add(item);
206
+ });
207
+ if (
208
+ typeof headerValue === "string" &&
209
+ SECRET_FIELD_NAME_PATTERN.test(headerName) &&
210
+ !detectInlineCredentialIndicators(headerValue).size
211
+ ) {
212
+ inlineIndicators.add("secret-header-value");
213
+ }
214
+ }
215
+ }
216
+ return {
217
+ credentialIndicatorCount: inlineIndicators.size,
218
+ credentialReferenceCount: credentialRefs.size,
219
+ exposureFieldCount: exposureFields.size,
220
+ credentialRefs: Array.from(credentialRefs).sort(),
221
+ exposureFields: Array.from(exposureFields).sort(),
222
+ inlineIndicators: Array.from(inlineIndicators).sort(),
223
+ };
224
+ }
225
+
226
+ function inferTransport(serverConfig, endpoints) {
227
+ const declaredTransport = String(
228
+ serverConfig?.transport || serverConfig?.type || "",
229
+ ).toLowerCase();
230
+ if (declaredTransport === "local") {
231
+ return "stdio";
232
+ }
233
+ if (declaredTransport.includes("sse")) {
234
+ return "sse";
235
+ }
236
+ if (declaredTransport.includes("websocket") || declaredTransport === "ws") {
237
+ return "websocket";
238
+ }
239
+ if (
240
+ declaredTransport.includes("http") ||
241
+ declaredTransport === "remote" ||
242
+ endpoints.some((endpoint) => endpoint.startsWith("http"))
243
+ ) {
244
+ return "streamable-http";
245
+ }
246
+ return "stdio";
247
+ }
248
+
249
+ function extractEndpoints(serverConfig) {
250
+ const endpoints = new Set();
251
+ for (const candidateKey of ["endpoint", "url", "uri"]) {
252
+ const value = serverConfig?.[candidateKey];
253
+ if (typeof value === "string" && /^https?:\/\//iu.test(value)) {
254
+ endpoints.add(value);
255
+ }
256
+ }
257
+ for (const arg of Array.isArray(serverConfig?.args)
258
+ ? serverConfig.args
259
+ : []) {
260
+ if (typeof arg === "string" && /^https?:\/\//iu.test(arg)) {
261
+ endpoints.add(arg);
262
+ }
263
+ }
264
+ return Array.from(endpoints).sort();
265
+ }
266
+
267
+ function extractPackageRefs(command, args) {
268
+ const packageRefs = new Set();
269
+ const candidates = [command]
270
+ .concat(Array.isArray(args) ? args : [])
271
+ .filter((value) => typeof value === "string");
272
+ for (const candidate of candidates) {
273
+ const normalized = candidate.replace(/^[./]+/u, "");
274
+ if (!normalized || normalized.startsWith("-")) {
275
+ continue;
276
+ }
277
+ const classification = classifyMcpReference(normalized);
278
+ if (classification.isMcp) {
279
+ packageRefs.add(classification.packageName || normalized);
280
+ }
281
+ }
282
+ return Array.from(packageRefs).sort();
283
+ }
284
+
285
+ function normalizeCommandAndArgs(serverConfig) {
286
+ if (Array.isArray(serverConfig?.command)) {
287
+ const [command, ...args] = serverConfig.command;
288
+ return {
289
+ args,
290
+ command: String(command || ""),
291
+ };
292
+ }
293
+ return {
294
+ args: Array.isArray(serverConfig?.args) ? serverConfig.args : [],
295
+ command: String(
296
+ serverConfig?.command ||
297
+ serverConfig?.cmd ||
298
+ serverConfig?.executable ||
299
+ "",
300
+ ),
301
+ };
302
+ }
303
+
304
+ function authPostureForConfig(serverConfig, endpoints, authHints) {
305
+ const posture = new Set();
306
+ if (authHints.has("oauth")) {
307
+ posture.add("oauth");
308
+ }
309
+ if (authHints.has("bearer")) {
310
+ posture.add("bearer");
311
+ }
312
+ if (
313
+ serverConfig?.resourceServerUrl ||
314
+ serverConfig?.protectedResourceMetadata
315
+ ) {
316
+ posture.add("protected-resource-metadata");
317
+ }
318
+ if (!posture.size && endpoints.length) {
319
+ posture.add("none");
320
+ }
321
+ return Array.from(posture).sort();
322
+ }
323
+
324
+ function dynamicClientRegistrationConfig(serverConfig) {
325
+ return Boolean(
326
+ serverConfig?.dynamicClientRegistration ||
327
+ serverConfig?.supportsDCR ||
328
+ serverConfig?.registration_endpoint ||
329
+ serverConfig?.auth?.registration_endpoint ||
330
+ serverConfig?.oauth?.registration_endpoint,
331
+ );
332
+ }
333
+
334
+ function staticClientIdPresent(serverConfig) {
335
+ const clientId =
336
+ serverConfig?.client_id ||
337
+ serverConfig?.clientId ||
338
+ serverConfig?.oauth?.client_id ||
339
+ serverConfig?.oauth?.clientId;
340
+ return (
341
+ typeof clientId === "string" && clientId.length && !isEnvReference(clientId)
342
+ );
343
+ }
344
+
345
+ function tokenPassthroughRisk(serverConfig) {
346
+ const serialized = JSON.stringify(serverConfig || {});
347
+ if (
348
+ /forward(?:ing)?(?:authorization|auth|access)?token/iu.test(serialized) ||
349
+ /tokenPassthrough|passthroughToken/iu.test(serialized)
350
+ ) {
351
+ return "high";
352
+ }
353
+ return "none";
354
+ }
355
+
356
+ function trustProfile(officialSdk, exposureType, authPosture) {
357
+ const hasAuth = authPosture.some((value) => value !== "none");
358
+ if (officialSdk && exposureType === "local-only" && hasAuth) {
359
+ return "official-sdk+auth+localhost-only";
360
+ }
361
+ if (officialSdk && exposureType !== "local-only" && hasAuth) {
362
+ return "official-sdk+networked+auth";
363
+ }
364
+ if (!officialSdk && exposureType !== "local-only") {
365
+ return hasAuth
366
+ ? "non-official-sdk+networked"
367
+ : "non-official-sdk+unauthenticated-networked";
368
+ }
369
+ return officialSdk ? "official-sdk+unknown" : "review-needed";
370
+ }
371
+
372
+ function createServiceFromConfig(
373
+ filePath,
374
+ format,
375
+ configKey,
376
+ serverName,
377
+ serverConfig,
378
+ ) {
379
+ const normalized = normalizeCommandAndArgs(serverConfig);
380
+ const command = normalized.command;
381
+ const args = normalized.args;
382
+ const endpoints = extractEndpoints(serverConfig);
383
+ const transport = inferTransport(serverConfig, endpoints);
384
+ const authHints = authHintsFromValue(serverConfig);
385
+ const authPosture = authPostureForConfig(serverConfig, endpoints, authHints);
386
+ const packageRefs = extractPackageRefs(command, args);
387
+ const classifications = packageRefs.map((ref) => classifyMcpReference(ref));
388
+ const explicitMcpConfig =
389
+ packageRefs.length > 0 ||
390
+ transport !== "stdio" ||
391
+ Boolean(serverConfig?.mcp || serverConfig?.mcpServer);
392
+ if (!explicitMcpConfig) {
393
+ return undefined;
394
+ }
395
+ const providerNames = providerNamesForText(
396
+ JSON.stringify({
397
+ args,
398
+ command,
399
+ endpoints,
400
+ env: serverConfig?.env || serverConfig?.environment || {},
401
+ }),
402
+ );
403
+ const credentialSignals = detectConfigCredentialSignals(serverConfig);
404
+ const publicNetwork = endpoints.some((endpoint) => {
405
+ try {
406
+ return !isLocalHost(new URL(endpoint).hostname);
407
+ } catch {
408
+ return false;
409
+ }
410
+ });
411
+ const hasTunnelReference = endpoints.some((endpoint) => {
412
+ try {
413
+ return LOCAL_TUNNEL_HOST_PATTERNS.some((pattern) =>
414
+ pattern.test(new URL(endpoint).hostname),
415
+ );
416
+ } catch {
417
+ return false;
418
+ }
419
+ });
420
+ const officialSdk = classifications.some((item) => item.isOfficial)
421
+ ? true
422
+ : classifications.length
423
+ ? false
424
+ : undefined;
425
+ const exposureType = publicNetwork
426
+ ? "networked-public"
427
+ : transport === "stdio"
428
+ ? "stdio-configured"
429
+ : "local-only";
430
+ const supportsDcr = dynamicClientRegistrationConfig(serverConfig);
431
+ const confusedDeputyRisk =
432
+ supportsDcr && staticClientIdPresent(serverConfig) ? "high" : "none";
433
+ const passthroughRisk = tokenPassthroughRisk(serverConfig);
434
+ const version = String(serverConfig?.version || "latest");
435
+ const properties = [{ name: "SrcFile", value: filePath }];
436
+ addUniqueProperty(properties, "cdx:mcp:serviceType", "configured-server");
437
+ addUniqueProperty(properties, "cdx:mcp:inventorySource", "config-file");
438
+ addUniqueProperty(properties, "cdx:mcp:configFormat", format);
439
+ addUniqueProperty(
440
+ properties,
441
+ "cdx:mcp:configKey",
442
+ `${configKey}.${serverName}`,
443
+ );
444
+ addUniqueProperty(properties, "cdx:mcp:transport", transport);
445
+ addUniqueProperty(properties, "cdx:mcp:exposureType", exposureType);
446
+ addUniqueProperty(properties, "cdx:mcp:usageConfidence", "high");
447
+ addUniqueProperty(properties, "cdx:mcp:command", command || "configured");
448
+ addUniqueProperty(properties, "cdx:mcp:reviewNeeded", "true");
449
+ if (typeof officialSdk === "boolean") {
450
+ addUniqueProperty(
451
+ properties,
452
+ "cdx:mcp:officialSdk",
453
+ officialSdk ? "true" : "false",
454
+ );
455
+ }
456
+ if (packageRefs.length) {
457
+ addUniqueProperty(properties, "cdx:mcp:packageRefs", packageRefs.join(","));
458
+ }
459
+ if (providerNames.length) {
460
+ addUniqueProperty(
461
+ properties,
462
+ "cdx:mcp:providerNames",
463
+ providerNames.join(","),
464
+ );
465
+ }
466
+ if (authPosture.length) {
467
+ addUniqueProperty(properties, "cdx:mcp:authPosture", authPosture.join(","));
468
+ addUniqueProperty(properties, "cdx:mcp:authMode", authPosture.join(","));
469
+ }
470
+ if (credentialSignals.inlineIndicators.length) {
471
+ addUniqueProperty(properties, "cdx:mcp:credentialExposure", "true");
472
+ addUniqueProperty(
473
+ properties,
474
+ "cdx:mcp:credentialIndicatorCount",
475
+ String(credentialSignals.credentialIndicatorCount),
476
+ );
477
+ }
478
+ if (credentialSignals.exposureFields.length) {
479
+ addUniqueProperty(
480
+ properties,
481
+ "cdx:mcp:credentialExposureFieldCount",
482
+ String(credentialSignals.exposureFieldCount),
483
+ );
484
+ }
485
+ if (credentialSignals.credentialRefs.length) {
486
+ addUniqueProperty(
487
+ properties,
488
+ "cdx:mcp:credentialReferenceCount",
489
+ String(credentialSignals.credentialReferenceCount),
490
+ );
491
+ }
492
+ if (supportsDcr) {
493
+ addUniqueProperty(properties, "cdx:mcp:auth:supportsDCR", "true");
494
+ }
495
+ if (authHints.has("oauth")) {
496
+ addUniqueProperty(properties, "cdx:mcp:auth:requiresOAuth", "true");
497
+ }
498
+ if (
499
+ serverConfig?.protectedResourceMetadata ||
500
+ serverConfig?.resourceServerUrl ||
501
+ serverConfig?.oauth?.resourceServerUrl
502
+ ) {
503
+ addUniqueProperty(
504
+ properties,
505
+ "cdx:mcp:auth:protectedResourceMetadata",
506
+ "true",
507
+ );
508
+ }
509
+ addUniqueProperty(
510
+ properties,
511
+ "cdx:mcp:security:confusedDeputyRisk",
512
+ confusedDeputyRisk,
513
+ );
514
+ addUniqueProperty(
515
+ properties,
516
+ "cdx:mcp:security:tokenPassthroughRisk",
517
+ passthroughRisk,
518
+ );
519
+ if (hasTunnelReference) {
520
+ addUniqueProperty(properties, "cdx:mcp:hasTunnelReference", "true");
521
+ }
522
+ addUniqueProperty(
523
+ properties,
524
+ "cdx:mcp:trustProfile",
525
+ trustProfile(officialSdk, exposureType, authPosture),
526
+ );
527
+ const serviceName = serverConfig?.name || serverName || basename(filePath);
528
+ const authenticated =
529
+ transport === "stdio"
530
+ ? authPosture.some((value) => value !== "none")
531
+ ? true
532
+ : undefined
533
+ : authPosture.some((value) => value !== "none");
534
+ return {
535
+ "bom-ref": `urn:service:mcp:${sanitizeMcpRefToken(serviceName)}:${sanitizeMcpRefToken(version)}`,
536
+ authenticated,
537
+ endpoints,
538
+ group: "mcp",
539
+ name: serviceName,
540
+ properties,
541
+ version,
542
+ };
543
+ }
544
+
545
+ function createConfigComponent(filePath, format, raw, services) {
546
+ const hiddenUnicodeScan = scanTextForHiddenUnicode(raw, {
547
+ syntax: syntaxForFile(filePath),
548
+ });
549
+ const properties = [
550
+ { name: "SrcFile", value: filePath },
551
+ { name: "cdx:file:kind", value: "mcp-config" },
552
+ { name: "cdx:mcp:inventorySource", value: "config-file" },
553
+ { name: "cdx:mcp:configFormat", value: format },
554
+ { name: "cdx:mcp:configuredServiceCount", value: String(services.length) },
555
+ ];
556
+ if (services.length) {
557
+ addUniqueProperty(
558
+ properties,
559
+ "cdx:mcp:configuredServiceNames",
560
+ services
561
+ .map((service) => service.name)
562
+ .sort()
563
+ .join(","),
564
+ );
565
+ addUniqueProperty(
566
+ properties,
567
+ "cdx:mcp:configuredEndpoints",
568
+ services
569
+ .flatMap((service) => service.endpoints || [])
570
+ .filter(Boolean)
571
+ .sort()
572
+ .join(","),
573
+ );
574
+ }
575
+ if (hiddenUnicodeScan.hasHiddenUnicode) {
576
+ addUniqueProperty(properties, "cdx:file:hasHiddenUnicode", "true");
577
+ addUniqueProperty(
578
+ properties,
579
+ "cdx:file:hiddenUnicodeCodePoints",
580
+ hiddenUnicodeScan.codePoints.join(","),
581
+ );
582
+ addUniqueProperty(
583
+ properties,
584
+ "cdx:file:hiddenUnicodeLineNumbers",
585
+ hiddenUnicodeScan.lineNumbers.join(","),
586
+ );
587
+ }
588
+ const credentialServices = services.filter((service) =>
589
+ service.properties?.some(
590
+ (property) =>
591
+ property.name === "cdx:mcp:credentialExposure" &&
592
+ property.value === "true",
593
+ ),
594
+ );
595
+ if (credentialServices.length) {
596
+ addUniqueProperty(properties, "cdx:mcp:credentialExposure", "true");
597
+ addUniqueProperty(
598
+ properties,
599
+ "cdx:mcp:credentialExposedServiceCount",
600
+ String(credentialServices.length),
601
+ );
602
+ }
603
+ return {
604
+ "bom-ref": `file:${filePath}`,
605
+ name: basename(filePath),
606
+ properties,
607
+ type: "file",
608
+ };
609
+ }
610
+
611
+ export const mcpConfigParser = {
612
+ id: "mcp-config",
613
+ patterns: MCP_CONFIG_PATTERNS,
614
+ parse(files, _options = {}) {
615
+ const components = [];
616
+ const services = [];
617
+ for (const filePath of [...new Set(files || [])]) {
618
+ let raw;
619
+ try {
620
+ raw = readFileSync(filePath, "utf-8");
621
+ } catch {
622
+ continue;
623
+ }
624
+ let configJson;
625
+ try {
626
+ configJson = parseJsonLike(raw);
627
+ } catch {
628
+ continue;
629
+ }
630
+ const format = configFormat(filePath);
631
+ const fileServices = [];
632
+ for (const { configKey, servers } of extractServerMaps(configJson)) {
633
+ for (const [serverName, serverConfig] of servers) {
634
+ const service = createServiceFromConfig(
635
+ filePath,
636
+ format,
637
+ configKey,
638
+ serverName,
639
+ serverConfig,
640
+ );
641
+ if (service) {
642
+ fileServices.push(service);
643
+ }
644
+ }
645
+ }
646
+ if (!fileServices.length) {
647
+ continue;
648
+ }
649
+ services.push(...fileServices);
650
+ components.push(
651
+ createConfigComponent(filePath, format, raw, fileServices),
652
+ );
653
+ }
654
+ return { components, services };
655
+ },
656
+ };