@bryan-thompson/inspector-assessment-client 1.15.0 → 1.16.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 (57) hide show
  1. package/dist/assets/{OAuthCallback-BleN4Jjs.js → OAuthCallback-KwMiy-L3.js} +1 -1
  2. package/dist/assets/{OAuthDebugCallback-C__lzEyx.js → OAuthDebugCallback-hckdJlo3.js} +1 -1
  3. package/dist/assets/{index-CPXmfP9b.js → index-C89umkGV.js} +745 -4350
  4. package/dist/index.html +1 -1
  5. package/lib/lib/assessmentTypes.d.ts +123 -0
  6. package/lib/lib/assessmentTypes.d.ts.map +1 -1
  7. package/lib/lib/assessmentTypes.js +20 -0
  8. package/lib/lib/securityPatterns.d.ts +2 -2
  9. package/lib/lib/securityPatterns.d.ts.map +1 -1
  10. package/lib/lib/securityPatterns.js +290 -15
  11. package/lib/services/assessment/AssessmentOrchestrator.d.ts +67 -0
  12. package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
  13. package/lib/services/assessment/AssessmentOrchestrator.js +91 -1
  14. package/lib/services/assessment/ResponseValidator.d.ts +7 -34
  15. package/lib/services/assessment/ResponseValidator.d.ts.map +1 -1
  16. package/lib/services/assessment/ResponseValidator.js +100 -704
  17. package/lib/services/assessment/config/annotationPatterns.js +1 -1
  18. package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts +67 -0
  19. package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts.map +1 -0
  20. package/lib/services/assessment/lib/RequestHistoryAnalyzer.js +191 -0
  21. package/lib/services/assessment/lib/claudeCodeBridge.d.ts +1 -0
  22. package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -1
  23. package/lib/services/assessment/lib/claudeCodeBridge.js +5 -4
  24. package/lib/services/assessment/modules/AuthenticationAssessor.d.ts +4 -0
  25. package/lib/services/assessment/modules/AuthenticationAssessor.d.ts.map +1 -1
  26. package/lib/services/assessment/modules/AuthenticationAssessor.js +97 -1
  27. package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts +39 -0
  28. package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts.map +1 -0
  29. package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.js +330 -0
  30. package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -1
  31. package/lib/services/assessment/modules/FunctionalityAssessor.js +46 -13
  32. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +5 -0
  33. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
  34. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +81 -0
  35. package/lib/services/assessment/modules/ManifestValidationAssessor.js +1 -1
  36. package/lib/services/assessment/modules/PromptAssessor.d.ts +30 -0
  37. package/lib/services/assessment/modules/PromptAssessor.d.ts.map +1 -0
  38. package/lib/services/assessment/modules/PromptAssessor.js +367 -0
  39. package/lib/services/assessment/modules/ResourceAssessor.d.ts +28 -0
  40. package/lib/services/assessment/modules/ResourceAssessor.d.ts.map +1 -0
  41. package/lib/services/assessment/modules/ResourceAssessor.js +296 -0
  42. package/lib/services/assessment/modules/SecurityAssessor.d.ts +4 -2
  43. package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
  44. package/lib/services/assessment/modules/SecurityAssessor.js +10 -41
  45. package/lib/services/assessment/modules/TemporalAssessor.d.ts +1 -0
  46. package/lib/services/assessment/modules/TemporalAssessor.d.ts.map +1 -1
  47. package/lib/services/assessment/modules/TemporalAssessor.js +35 -4
  48. package/lib/utils/jsonUtils.d.ts +68 -0
  49. package/lib/utils/jsonUtils.d.ts.map +1 -0
  50. package/lib/utils/jsonUtils.js +141 -0
  51. package/lib/utils/paramUtils.d.ts +11 -0
  52. package/lib/utils/paramUtils.d.ts.map +1 -0
  53. package/lib/utils/paramUtils.js +37 -0
  54. package/lib/utils/schemaUtils.d.ts +74 -0
  55. package/lib/utils/schemaUtils.d.ts.map +1 -0
  56. package/lib/utils/schemaUtils.js +268 -0
  57. package/package.json +1 -1
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Cross-Capability Security Assessor Module
3
+ * Tests interactions between tools, resources, and prompts for security vulnerabilities
4
+ *
5
+ * Tests include:
6
+ * - Tool->Resource access patterns (can a tool expose unauthorized resources?)
7
+ * - Prompt->Tool interaction (can a prompt trigger dangerous tool calls?)
8
+ * - Resource->Tool data flow (is sensitive resource data passed to tools?)
9
+ * - Privilege escalation across capabilities
10
+ */
11
+ import { BaseAssessor } from "./BaseAssessor.js";
12
+ // Tool patterns that indicate resource access capability
13
+ const RESOURCE_ACCESS_TOOL_PATTERNS = [
14
+ /read[_-]?file/i,
15
+ /get[_-]?file/i,
16
+ /fetch[_-]?resource/i,
17
+ /load[_-]?data/i,
18
+ /access[_-]?resource/i,
19
+ /retrieve/i,
20
+ /download/i,
21
+ ];
22
+ // Tool patterns that indicate dangerous operations
23
+ const DANGEROUS_TOOL_PATTERNS = [
24
+ /delete/i,
25
+ /remove/i,
26
+ /drop/i,
27
+ /exec(ute)?/i,
28
+ /run[_-]?command/i,
29
+ /shell/i,
30
+ /system/i,
31
+ /eval/i,
32
+ /write/i,
33
+ /modify/i,
34
+ /update/i,
35
+ /create/i,
36
+ /admin/i,
37
+ /config/i,
38
+ ];
39
+ // Sensitive resource patterns
40
+ const SENSITIVE_RESOURCE_PATTERNS = [
41
+ /credential/i,
42
+ /secret/i,
43
+ /password/i,
44
+ /token/i,
45
+ /key/i,
46
+ /config/i,
47
+ /\.env/i,
48
+ /auth/i,
49
+ ];
50
+ // Prompt patterns that could trigger tool execution
51
+ const TOOL_TRIGGER_PATTERNS = [
52
+ /execute/i,
53
+ /run/i,
54
+ /call/i,
55
+ /invoke/i,
56
+ /trigger/i,
57
+ /perform/i,
58
+ /do the following/i,
59
+ /carry out/i,
60
+ ];
61
+ export class CrossCapabilitySecurityAssessor extends BaseAssessor {
62
+ async assess(context) {
63
+ const results = [];
64
+ // Get all capabilities
65
+ const tools = context.tools || [];
66
+ const resources = context.resources || [];
67
+ const prompts = context.prompts || [];
68
+ this.log(`Testing cross-capability security: ${tools.length} tools, ${resources.length} resources, ${prompts.length} prompts`);
69
+ // Test 1: Tool->Resource access patterns
70
+ const toolResourceResults = this.testToolResourceAccess(tools, resources);
71
+ results.push(...toolResourceResults);
72
+ // Test 2: Prompt->Tool interaction security
73
+ const promptToolResults = this.testPromptToolInteraction(prompts, tools);
74
+ results.push(...promptToolResults);
75
+ // Test 3: Resource->Tool data flow
76
+ const dataFlowResults = this.testResourceToolDataFlow(resources, tools, context);
77
+ results.push(...dataFlowResults);
78
+ // Test 4: Privilege escalation paths
79
+ const escalationResults = this.testPrivilegeEscalation(tools, resources, prompts);
80
+ results.push(...escalationResults);
81
+ // Calculate metrics
82
+ const vulnerabilitiesFound = results.filter((r) => r.vulnerable).length;
83
+ const privilegeEscalationRisks = results.filter((r) => r.testType === "privilege_escalation" && r.vulnerable).length;
84
+ const dataFlowViolations = results.filter((r) => (r.testType === "resource_to_tool" ||
85
+ r.testType === "tool_to_resource") &&
86
+ r.vulnerable).length;
87
+ // Determine status
88
+ const status = this.determineCrossCapabilityStatus(vulnerabilitiesFound, privilegeEscalationRisks);
89
+ // Generate explanation and recommendations
90
+ const explanation = this.generateExplanation(results, vulnerabilitiesFound);
91
+ const recommendations = this.generateRecommendations(results);
92
+ return {
93
+ testsRun: results.length,
94
+ vulnerabilitiesFound,
95
+ privilegeEscalationRisks,
96
+ dataFlowViolations,
97
+ results,
98
+ status,
99
+ explanation,
100
+ recommendations,
101
+ };
102
+ }
103
+ /**
104
+ * Test if tools can access resources in unauthorized ways
105
+ */
106
+ testToolResourceAccess(tools, resources) {
107
+ const results = [];
108
+ // Find tools that can access resources
109
+ const resourceAccessTools = tools.filter((tool) => RESOURCE_ACCESS_TOOL_PATTERNS.some((pattern) => pattern.test(tool.name) || pattern.test(tool.description || "")));
110
+ // Find sensitive resources
111
+ const sensitiveResources = resources.filter((resource) => SENSITIVE_RESOURCE_PATTERNS.some((pattern) => pattern.test(resource.uri) ||
112
+ pattern.test(resource.name || "") ||
113
+ pattern.test(resource.description || "")));
114
+ this.testCount += resourceAccessTools.length * sensitiveResources.length;
115
+ // Test each combination
116
+ for (const tool of resourceAccessTools) {
117
+ for (const resource of sensitiveResources) {
118
+ const hasPathParameter = this.toolHasPathParameter(tool);
119
+ results.push({
120
+ testType: "tool_to_resource",
121
+ sourceCapability: `tool:${tool.name}`,
122
+ targetCapability: `resource:${resource.uri}`,
123
+ vulnerable: hasPathParameter, // If tool has path param, it could access sensitive resources
124
+ evidence: hasPathParameter
125
+ ? `Tool ${tool.name} has path/file parameter that could access sensitive resource ${resource.uri}`
126
+ : undefined,
127
+ riskLevel: hasPathParameter ? "HIGH" : "LOW",
128
+ description: `Tool ${tool.name} access to resource ${resource.uri}`,
129
+ });
130
+ }
131
+ }
132
+ return results;
133
+ }
134
+ /**
135
+ * Test if prompts could trigger dangerous tool calls
136
+ */
137
+ testPromptToolInteraction(prompts, tools) {
138
+ const results = [];
139
+ // Find dangerous tools
140
+ const dangerousTools = tools.filter((tool) => DANGEROUS_TOOL_PATTERNS.some((pattern) => pattern.test(tool.name) || pattern.test(tool.description || "")));
141
+ // Find prompts that mention tool execution
142
+ const toolTriggerPrompts = prompts.filter((prompt) => TOOL_TRIGGER_PATTERNS.some((pattern) => pattern.test(prompt.name) || pattern.test(prompt.description || "")) ||
143
+ prompt.arguments?.some((arg) => TOOL_TRIGGER_PATTERNS.some((pattern) => pattern.test(arg.name) || pattern.test(arg.description || ""))));
144
+ this.testCount += toolTriggerPrompts.length * dangerousTools.length;
145
+ for (const prompt of toolTriggerPrompts) {
146
+ for (const tool of dangerousTools) {
147
+ // Check if prompt could potentially reference this tool
148
+ const promptText = `${prompt.name} ${prompt.description || ""} ${prompt.arguments?.map((a) => a.name).join(" ") || ""}`.toLowerCase();
149
+ const toolName = tool.name.toLowerCase();
150
+ const couldTrigger = promptText.includes(toolName) ||
151
+ this.promptCouldTriggerTool(prompt, tool);
152
+ results.push({
153
+ testType: "prompt_to_tool",
154
+ sourceCapability: `prompt:${prompt.name}`,
155
+ targetCapability: `tool:${tool.name}`,
156
+ vulnerable: couldTrigger,
157
+ evidence: couldTrigger
158
+ ? `Prompt ${prompt.name} could trigger dangerous tool ${tool.name}`
159
+ : undefined,
160
+ riskLevel: couldTrigger ? "HIGH" : "LOW",
161
+ description: `Prompt ${prompt.name} interaction with tool ${tool.name}`,
162
+ });
163
+ }
164
+ }
165
+ return results;
166
+ }
167
+ /**
168
+ * Test if resource data could be passed to tools in unsafe ways
169
+ */
170
+ testResourceToolDataFlow(resources, tools, _context) {
171
+ const results = [];
172
+ // Find sensitive resources
173
+ const sensitiveResources = resources.filter((resource) => SENSITIVE_RESOURCE_PATTERNS.some((pattern) => pattern.test(resource.uri) ||
174
+ pattern.test(resource.name || "") ||
175
+ pattern.test(resource.description || "")));
176
+ // Find tools that could exfiltrate data
177
+ const exfiltrationTools = tools.filter((tool) => /send|post|upload|email|notify|webhook|http|request|api/i.test(tool.name) ||
178
+ /send|post|upload|email|notify|webhook|http|request|api/i.test(tool.description || ""));
179
+ this.testCount += sensitiveResources.length * exfiltrationTools.length;
180
+ for (const resource of sensitiveResources) {
181
+ for (const tool of exfiltrationTools) {
182
+ // Check if tool has parameters that could accept resource content
183
+ const hasContentParam = this.toolHasContentParameter(tool);
184
+ results.push({
185
+ testType: "resource_to_tool",
186
+ sourceCapability: `resource:${resource.uri}`,
187
+ targetCapability: `tool:${tool.name}`,
188
+ vulnerable: hasContentParam,
189
+ evidence: hasContentParam
190
+ ? `Sensitive resource ${resource.uri} content could be exfiltrated via tool ${tool.name}`
191
+ : undefined,
192
+ riskLevel: hasContentParam ? "HIGH" : "MEDIUM",
193
+ description: `Resource ${resource.uri} data flow to tool ${tool.name}`,
194
+ });
195
+ }
196
+ }
197
+ return results;
198
+ }
199
+ /**
200
+ * Test for privilege escalation paths
201
+ */
202
+ testPrivilegeEscalation(tools, resources, prompts) {
203
+ const results = [];
204
+ // Pattern: Low-privilege prompt -> High-privilege tool
205
+ const readOnlyPrompts = prompts.filter((p) => /read|view|list|get|show|display/i.test(p.name) ||
206
+ /read|view|list|get|show|display/i.test(p.description || ""));
207
+ const writeTools = tools.filter((t) => /write|delete|modify|update|create|drop|exec/i.test(t.name) ||
208
+ /write|delete|modify|update|create|drop|exec/i.test(t.description || ""));
209
+ this.testCount += readOnlyPrompts.length;
210
+ for (const prompt of readOnlyPrompts) {
211
+ // Check if prompt arguments could be used to call write tools
212
+ const hasOpenArg = prompt.arguments?.some((arg) => /action|command|operation|tool|function/i.test(arg.name) ||
213
+ /action|command|operation|tool|function/i.test(arg.description || ""));
214
+ if (hasOpenArg && writeTools.length > 0) {
215
+ results.push({
216
+ testType: "privilege_escalation",
217
+ sourceCapability: `prompt:${prompt.name}`,
218
+ targetCapability: `tools:write_operations`,
219
+ vulnerable: true,
220
+ evidence: `Read-only prompt ${prompt.name} has arguments that could specify write operations`,
221
+ riskLevel: "HIGH",
222
+ description: `Privilege escalation path from ${prompt.name} to write tools`,
223
+ });
224
+ }
225
+ }
226
+ // Pattern: Public resource -> Admin tool
227
+ const publicResources = resources.filter((r) => /public|shared|common/i.test(r.uri) ||
228
+ /public|shared|common/i.test(r.name || ""));
229
+ const adminTools = tools.filter((t) => /admin|config|system|manage|control/i.test(t.name) ||
230
+ /admin|config|system|manage|control/i.test(t.description || ""));
231
+ this.testCount += publicResources.length;
232
+ for (const resource of publicResources) {
233
+ for (const tool of adminTools) {
234
+ // Check if resource content could be used as tool input
235
+ const toolAcceptsData = this.toolHasContentParameter(tool);
236
+ if (toolAcceptsData) {
237
+ results.push({
238
+ testType: "privilege_escalation",
239
+ sourceCapability: `resource:${resource.uri}`,
240
+ targetCapability: `tool:${tool.name}`,
241
+ vulnerable: true,
242
+ evidence: `Public resource ${resource.uri} content could influence admin tool ${tool.name}`,
243
+ riskLevel: "HIGH",
244
+ description: `Privilege escalation path from ${resource.uri} to ${tool.name}`,
245
+ });
246
+ }
247
+ }
248
+ }
249
+ return results;
250
+ }
251
+ toolHasPathParameter(tool) {
252
+ const schema = tool.inputSchema;
253
+ if (!schema?.properties)
254
+ return false;
255
+ return Object.entries(schema.properties).some(([name, prop]) => /path|file|uri|url|location|directory|folder/i.test(name) ||
256
+ /path|file|uri|url|location|directory|folder/i.test(prop.description || ""));
257
+ }
258
+ toolHasContentParameter(tool) {
259
+ const schema = tool.inputSchema;
260
+ if (!schema?.properties)
261
+ return false;
262
+ return Object.entries(schema.properties).some(([name, prop]) => /content|data|body|text|message|payload/i.test(name) ||
263
+ /content|data|body|text|message|payload/i.test(prop.description || ""));
264
+ }
265
+ promptCouldTriggerTool(prompt, tool) {
266
+ // Check if prompt has action/tool arguments
267
+ const hasActionArg = prompt.arguments?.some((arg) => /action|tool|function|command|operation/i.test(arg.name) ||
268
+ /action|tool|function|command|operation/i.test(arg.description || ""));
269
+ // Check if prompt description mentions tool-like operations
270
+ const descMentionsTool = tool.name
271
+ .toLowerCase()
272
+ .split(/[_-]/)
273
+ .some((word) => word.length > 2 &&
274
+ (prompt.description || "").toLowerCase().includes(word));
275
+ return hasActionArg || descMentionsTool;
276
+ }
277
+ determineCrossCapabilityStatus(vulnerabilitiesFound, privilegeEscalationRisks) {
278
+ if (privilegeEscalationRisks > 0)
279
+ return "FAIL";
280
+ if (vulnerabilitiesFound > 2)
281
+ return "FAIL";
282
+ if (vulnerabilitiesFound > 0)
283
+ return "NEED_MORE_INFO";
284
+ return "PASS";
285
+ }
286
+ generateExplanation(results, vulnerabilitiesFound) {
287
+ const parts = [];
288
+ parts.push(`Tested ${results.length} cross-capability interaction(s).`);
289
+ if (vulnerabilitiesFound > 0) {
290
+ parts.push(`Found ${vulnerabilitiesFound} potential vulnerability(ies).`);
291
+ const byType = results.reduce((acc, r) => {
292
+ if (r.vulnerable) {
293
+ acc[r.testType] = (acc[r.testType] || 0) + 1;
294
+ }
295
+ return acc;
296
+ }, {});
297
+ for (const [type, count] of Object.entries(byType)) {
298
+ parts.push(`${type}: ${count}`);
299
+ }
300
+ }
301
+ else {
302
+ parts.push("No cross-capability vulnerabilities detected.");
303
+ }
304
+ return parts.join(" ");
305
+ }
306
+ generateRecommendations(results) {
307
+ const recommendations = [];
308
+ // Tool->Resource recommendations
309
+ const toolResourceVulns = results.filter((r) => r.testType === "tool_to_resource" && r.vulnerable);
310
+ if (toolResourceVulns.length > 0) {
311
+ recommendations.push("Implement resource access controls to prevent tools from accessing sensitive resources. Consider allowlisting accessible resource paths.");
312
+ }
313
+ // Prompt->Tool recommendations
314
+ const promptToolVulns = results.filter((r) => r.testType === "prompt_to_tool" && r.vulnerable);
315
+ if (promptToolVulns.length > 0) {
316
+ recommendations.push("Add confirmation prompts before dangerous tool execution. Implement tool invocation policies in prompts.");
317
+ }
318
+ // Data flow recommendations
319
+ const dataFlowVulns = results.filter((r) => r.testType === "resource_to_tool" && r.vulnerable);
320
+ if (dataFlowVulns.length > 0) {
321
+ recommendations.push("Implement data loss prevention controls. Validate and sanitize resource content before passing to external-facing tools.");
322
+ }
323
+ // Privilege escalation recommendations
324
+ const escalationVulns = results.filter((r) => r.testType === "privilege_escalation" && r.vulnerable);
325
+ if (escalationVulns.length > 0) {
326
+ recommendations.push("CRITICAL: Review and fix privilege escalation paths. Implement capability-based access control and principle of least privilege.");
327
+ }
328
+ return recommendations;
329
+ }
330
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"FunctionalityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/FunctionalityAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,uBAAuB,EAGxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAM9D,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,OAAO,CAAC,cAAc,CAAwB;IAE9C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoCvB,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,uBAAuB,CAAC;YAkI5D,QAAQ;IAiFtB,OAAO,CAAC,qBAAqB;IA0D7B,OAAO,CAAC,kBAAkB;IAwF1B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAe7C;IAEF;;;OAGG;IACH,OAAO,CAAC,mCAAmC;IAsF3C;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAWlB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO;IAI9C,OAAO,CAAC,mBAAmB;CA+B5B"}
1
+ {"version":3,"file":"FunctionalityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/FunctionalityAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,uBAAuB,EAGxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAS9D,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,OAAO,CAAC,cAAc,CAAwB;IAE9C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoCvB,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,uBAAuB,CAAC;YAkI5D,QAAQ;IAgGtB,OAAO,CAAC,qBAAqB;IAmE7B,OAAO,CAAC,kBAAkB;IA4G1B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAe7C;IAEF;;;OAGG;IACH,OAAO,CAAC,mCAAmC;IAsF3C;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAWlB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO;IAI9C,OAAO,CAAC,mBAAmB;CA+B5B"}
@@ -7,6 +7,8 @@ import { ResponseValidator } from "../ResponseValidator.js";
7
7
  import { createConcurrencyLimit } from "../lib/concurrencyLimit.js";
8
8
  import { ToolClassifier, ToolCategory } from "../ToolClassifier.js";
9
9
  import { TestDataGenerator } from "../TestDataGenerator.js";
10
+ import { cleanParams } from "../../../utils/paramUtils.js";
11
+ import { resolveRef, normalizeUnionType } from "../../../utils/schemaUtils.js";
10
12
  export class FunctionalityAssessor extends BaseAssessor {
11
13
  toolClassifier = new ToolClassifier();
12
14
  /**
@@ -131,21 +133,30 @@ export class FunctionalityAssessor extends BaseAssessor {
131
133
  // Generate minimal valid parameters with metadata
132
134
  const { params: testParams, metadata } = this.generateMinimalParams(tool);
133
135
  try {
134
- this.log(`Testing tool: ${tool.name} with params: ${JSON.stringify(testParams)}`);
136
+ // Clean parameters to remove empty/null/undefined values for optional fields
137
+ // This prevents false negatives where tools reject empty optional values
138
+ const schema = tool.inputSchema;
139
+ const cleanedParams = schema
140
+ ? cleanParams(testParams, schema)
141
+ : testParams;
142
+ this.log(`Testing tool: ${tool.name} with params: ${JSON.stringify(cleanedParams)}`);
135
143
  // Execute tool with timeout
136
- const response = await this.executeWithTimeout(callTool(tool.name, testParams), this.config.testTimeout);
144
+ const response = await this.executeWithTimeout(callTool(tool.name, cleanedParams), this.config.testTimeout);
137
145
  const executionTime = Date.now() - startTime;
146
+ // Create validation context for response analysis
147
+ const validationContext = {
148
+ tool,
149
+ input: cleanedParams,
150
+ response,
151
+ };
152
+ // Extract response metadata (content types, structuredContent, etc.)
153
+ const responseMetadata = ResponseValidator.extractResponseMetadata(validationContext);
138
154
  // Check if response indicates an error using base class method
139
155
  // Use strict mode for functionality testing - only check explicit error indicators
140
156
  // This prevents false positives where valid responses mention "error" in their content
141
157
  if (this.isErrorResponse(response, true)) {
142
158
  // Check if this is a business logic error (validation error)
143
159
  // Tools that correctly validate inputs should be marked as "working"
144
- const validationContext = {
145
- tool,
146
- input: testParams,
147
- response,
148
- };
149
160
  if (ResponseValidator.isBusinessLogicError(validationContext)) {
150
161
  // Tool is correctly validating inputs - this is expected behavior
151
162
  return {
@@ -153,9 +164,10 @@ export class FunctionalityAssessor extends BaseAssessor {
153
164
  tested: true,
154
165
  status: "working",
155
166
  executionTime,
156
- testParameters: testParams,
167
+ testParameters: cleanedParams,
157
168
  response,
158
169
  testInputMetadata: metadata,
170
+ responseMetadata,
159
171
  };
160
172
  }
161
173
  // Real tool failure (not just validation)
@@ -165,9 +177,10 @@ export class FunctionalityAssessor extends BaseAssessor {
165
177
  status: "broken",
166
178
  error: this.extractErrorMessage(response),
167
179
  executionTime,
168
- testParameters: testParams,
180
+ testParameters: cleanedParams,
169
181
  response,
170
182
  testInputMetadata: metadata,
183
+ responseMetadata,
171
184
  };
172
185
  }
173
186
  return {
@@ -175,9 +188,10 @@ export class FunctionalityAssessor extends BaseAssessor {
175
188
  tested: true,
176
189
  status: "working",
177
190
  executionTime,
178
- testParameters: testParams,
191
+ testParameters: cleanedParams,
179
192
  response,
180
193
  testInputMetadata: metadata,
194
+ responseMetadata,
181
195
  };
182
196
  }
183
197
  catch (error) {
@@ -215,9 +229,16 @@ export class FunctionalityAssessor extends BaseAssessor {
215
229
  const required = schema.required || [];
216
230
  // For functionality testing, only generate REQUIRED parameters
217
231
  // This avoids triggering validation errors on optional parameters with complex rules
218
- for (const [key, prop] of Object.entries(schema.properties)) {
232
+ for (const [key, rawProp] of Object.entries(schema.properties)) {
219
233
  // Only include required parameters for basic functionality testing
220
234
  if (required.includes(key)) {
235
+ // P2 Enhancement: Resolve $ref references in the property schema
236
+ let prop = rawProp;
237
+ if (prop.$ref) {
238
+ prop = resolveRef(prop, schema);
239
+ }
240
+ // P2 Enhancement: Normalize union types (e.g., string|null from FastMCP)
241
+ prop = normalizeUnionType(prop);
221
242
  const { value, source, reason } = this.generateSmartParamValueWithMetadata(prop, key, primaryCategory);
222
243
  params[key] = value;
223
244
  fieldSources[key] = { field: key, value, source, reason };
@@ -271,8 +292,14 @@ export class FunctionalityAssessor extends BaseAssessor {
271
292
  case "array":
272
293
  // Generate array with sample items based on items schema
273
294
  if (prop.items) {
295
+ // Resolve $ref and normalize union types for items schema
296
+ let itemsSchema = prop.items;
297
+ if (itemsSchema.$ref) {
298
+ itemsSchema = resolveRef(itemsSchema, prop);
299
+ }
300
+ itemsSchema = normalizeUnionType(itemsSchema);
274
301
  return [
275
- this.generateParamValue(prop.items, undefined, includeOptional),
302
+ this.generateParamValue(itemsSchema, undefined, includeOptional),
276
303
  ];
277
304
  }
278
305
  return [];
@@ -284,8 +311,14 @@ export class FunctionalityAssessor extends BaseAssessor {
284
311
  // Generate properties based on includeOptional flag
285
312
  // includeOptional=false: Only required properties (for functionality testing)
286
313
  // includeOptional=true: All properties (for test input generation)
287
- for (const [key, subProp] of Object.entries(prop.properties)) {
314
+ for (const [key, rawSubProp] of Object.entries(prop.properties)) {
288
315
  if (includeOptional || requiredProps.includes(key)) {
316
+ // Resolve $ref and normalize union types for nested properties
317
+ let subProp = rawSubProp;
318
+ if (subProp.$ref) {
319
+ subProp = resolveRef(subProp, prop);
320
+ }
321
+ subProp = normalizeUnionType(subProp);
289
322
  obj[key] = this.generateParamValue(subProp, key, includeOptional);
290
323
  }
291
324
  }
@@ -37,6 +37,11 @@ export declare class MCPSpecComplianceAssessor extends BaseAssessor {
37
37
  * Check if tools have structured output support (2025-06-18 feature)
38
38
  */
39
39
  private checkStructuredOutputSupport;
40
+ /**
41
+ * Check if declared server capabilities match actual behavior
42
+ * Tests that capabilities advertised via serverCapabilities are actually implemented
43
+ */
44
+ private checkCapabilitiesCompliance;
40
45
  /**
41
46
  * Assess transport compliance (basic check)
42
47
  */
@@ -1 +1 @@
1
- {"version":3,"file":"MCPSpecComplianceAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/MCPSpecComplianceAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,2BAA2B,EAM3B,uBAAuB,EAGxB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,OAAO,CAAC,GAAG,CAAc;gBAEb,MAAM,EAAE,uBAAuB;IAK3C;;;OAGG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,2BAA2B,CAAC;IAqGvC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAwB9B;;OAEG;YACW,sBAAsB;IAkBpC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAyB/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAyC7B;;OAEG;YACW,mBAAmB;IAwBjC;;OAEG;IACH,OAAO,CAAC,4BAA4B;IAiBpC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAyFjC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA4B9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA2C7B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAoF5B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAyBjC;;OAEG;IACH,OAAO,CAAC,6BAA6B;CA0DtC"}
1
+ {"version":3,"file":"MCPSpecComplianceAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/MCPSpecComplianceAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,2BAA2B,EAM3B,uBAAuB,EAGxB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,OAAO,CAAC,GAAG,CAAc;gBAEb,MAAM,EAAE,uBAAuB;IAK3C;;;OAGG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,2BAA2B,CAAC;IA6GvC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAwB9B;;OAEG;YACW,sBAAsB;IAkBpC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAyB/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAyC7B;;OAEG;YACW,mBAAmB;IAwBjC;;OAEG;IACH,OAAO,CAAC,4BAA4B;IAiBpC;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IA0FnC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAyFjC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA4B9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA2C7B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAoF5B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAyBjC;;OAEG;IACH,OAAO,CAAC,6BAA6B;CA0DtC"}
@@ -22,6 +22,7 @@ export class MCPSpecComplianceAssessor extends BaseAssessor {
22
22
  const schemaCheck = this.checkSchemaCompliance(tools);
23
23
  const jsonRpcCheck = await this.checkJsonRpcCompliance(callTool);
24
24
  const errorCheck = await this.checkErrorResponses(tools, callTool);
25
+ const capabilitiesCheck = this.checkCapabilitiesCompliance(context);
25
26
  const protocolChecks = {
26
27
  jsonRpcCompliance: {
27
28
  passed: jsonRpcCheck.passed,
@@ -60,6 +61,13 @@ export class MCPSpecComplianceAssessor extends BaseAssessor {
60
61
  outputSchema: t.outputSchema,
61
62
  })),
62
63
  },
64
+ capabilitiesCompliance: {
65
+ passed: capabilitiesCheck.passed,
66
+ confidence: capabilitiesCheck.confidence,
67
+ evidence: capabilitiesCheck.evidence,
68
+ warnings: capabilitiesCheck.warnings,
69
+ rawResponse: capabilitiesCheck.rawResponse,
70
+ },
63
71
  };
64
72
  // SECTION 2: Metadata Hints (LOW CONFIDENCE - not tested, just parsed)
65
73
  const metadataHints = this.extractMetadataHints(context);
@@ -231,6 +239,79 @@ export class MCPSpecComplianceAssessor extends BaseAssessor {
231
239
  // Consider it supported if at least some tools use it
232
240
  return toolsWithOutputSchema > 0;
233
241
  }
242
+ /**
243
+ * Check if declared server capabilities match actual behavior
244
+ * Tests that capabilities advertised via serverCapabilities are actually implemented
245
+ */
246
+ checkCapabilitiesCompliance(context) {
247
+ const warnings = [];
248
+ const capabilities = context.serverCapabilities;
249
+ // If no capabilities declared, that's fine - it's optional
250
+ if (!capabilities) {
251
+ return {
252
+ passed: true,
253
+ confidence: "medium",
254
+ evidence: "No server capabilities declared (optional)",
255
+ rawResponse: undefined,
256
+ };
257
+ }
258
+ // Check tools capability
259
+ if (capabilities.tools) {
260
+ if (context.tools.length === 0) {
261
+ warnings.push("Declared tools capability but no tools registered");
262
+ }
263
+ this.testCount++;
264
+ }
265
+ // Check resources capability
266
+ if (capabilities.resources) {
267
+ if (!context.resources || context.resources.length === 0) {
268
+ // Resources declared but not provided - could be valid if not fetched
269
+ if (!context.readResource) {
270
+ warnings.push("Declared resources capability but no resources data provided for validation");
271
+ }
272
+ }
273
+ // Check listChanged notification support
274
+ if (capabilities.resources.listChanged) {
275
+ this.log("Server declares resources.listChanged notification support");
276
+ }
277
+ // Check subscribe support
278
+ if (capabilities.resources.subscribe) {
279
+ this.log("Server declares resource subscription support");
280
+ }
281
+ this.testCount++;
282
+ }
283
+ // Check prompts capability
284
+ if (capabilities.prompts) {
285
+ if (!context.prompts || context.prompts.length === 0) {
286
+ // Prompts declared but not provided
287
+ if (!context.getPrompt) {
288
+ warnings.push("Declared prompts capability but no prompts data provided for validation");
289
+ }
290
+ }
291
+ // Check listChanged notification support
292
+ if (capabilities.prompts.listChanged) {
293
+ this.log("Server declares prompts.listChanged notification support");
294
+ }
295
+ this.testCount++;
296
+ }
297
+ // Check logging capability
298
+ if (capabilities.logging) {
299
+ this.log("Server declares logging capability");
300
+ this.testCount++;
301
+ }
302
+ // Determine pass/fail
303
+ const passed = warnings.length === 0;
304
+ const confidence = warnings.length === 0 ? "high" : "medium";
305
+ return {
306
+ passed,
307
+ confidence,
308
+ evidence: passed
309
+ ? "All declared capabilities have corresponding implementations"
310
+ : `Capability validation issues: ${warnings.join("; ")}`,
311
+ warnings: warnings.length > 0 ? warnings : undefined,
312
+ rawResponse: capabilities,
313
+ };
314
+ }
234
315
  /**
235
316
  * Assess transport compliance (basic check)
236
317
  */
@@ -437,7 +437,7 @@ export class ManifestValidationAssessor extends BaseAssessor {
437
437
  contentType: response.headers.get("content-type") || undefined,
438
438
  });
439
439
  }
440
- catch (error) {
440
+ catch {
441
441
  // Try GET request as fallback (some servers reject HEAD)
442
442
  try {
443
443
  const controller = new AbortController();
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Prompt Assessor Module
3
+ * Tests MCP server prompts for security, AUP compliance, and injection vulnerabilities
4
+ *
5
+ * Tests include:
6
+ * - Prompt argument validation
7
+ * - AUP compliance (no harmful content generation instructions)
8
+ * - Injection vulnerability testing
9
+ * - Required vs optional argument handling
10
+ */
11
+ import { PromptAssessment } from "../../../lib/assessmentTypes.js";
12
+ import { BaseAssessor } from "./BaseAssessor.js";
13
+ import { AssessmentContext } from "../AssessmentOrchestrator.js";
14
+ export declare class PromptAssessor extends BaseAssessor {
15
+ assess(context: AssessmentContext): Promise<PromptAssessment>;
16
+ private createNoPromptsResponse;
17
+ private testPrompt;
18
+ private hasRequiredArguments;
19
+ private checkAUPViolations;
20
+ private createNormalArguments;
21
+ private testPromptExecution;
22
+ private checkMessagesForUnsafeContent;
23
+ private testPromptInjection;
24
+ private isInjectionSuccessful;
25
+ private testArgumentValidation;
26
+ private determinePromptStatus;
27
+ private generateExplanation;
28
+ private generateRecommendations;
29
+ }
30
+ //# sourceMappingURL=PromptAssessor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PromptAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/PromptAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,gBAAgB,EAGjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAa,MAAM,2BAA2B,CAAC;AA6DzE,qBAAa,cAAe,SAAQ,YAAY;IACxC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqDnE,OAAO,CAAC,uBAAuB;YAajB,UAAU;IA+ExB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,qBAAqB;YAuBf,mBAAmB;IAuCjC,OAAO,CAAC,6BAA6B;YAqBvB,mBAAmB;IA6CjC,OAAO,CAAC,qBAAqB;YAsCf,sBAAsB;IA+BpC,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,mBAAmB;IAmC3B,OAAO,CAAC,uBAAuB;CAoChC"}