@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.
- package/dist/assets/{OAuthCallback-BleN4Jjs.js → OAuthCallback-KwMiy-L3.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-C__lzEyx.js → OAuthDebugCallback-hckdJlo3.js} +1 -1
- package/dist/assets/{index-CPXmfP9b.js → index-C89umkGV.js} +745 -4350
- package/dist/index.html +1 -1
- package/lib/lib/assessmentTypes.d.ts +123 -0
- package/lib/lib/assessmentTypes.d.ts.map +1 -1
- package/lib/lib/assessmentTypes.js +20 -0
- package/lib/lib/securityPatterns.d.ts +2 -2
- package/lib/lib/securityPatterns.d.ts.map +1 -1
- package/lib/lib/securityPatterns.js +290 -15
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +67 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
- package/lib/services/assessment/AssessmentOrchestrator.js +91 -1
- package/lib/services/assessment/ResponseValidator.d.ts +7 -34
- package/lib/services/assessment/ResponseValidator.d.ts.map +1 -1
- package/lib/services/assessment/ResponseValidator.js +100 -704
- package/lib/services/assessment/config/annotationPatterns.js +1 -1
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts +67 -0
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts.map +1 -0
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.js +191 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts +1 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -1
- package/lib/services/assessment/lib/claudeCodeBridge.js +5 -4
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts +4 -0
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/AuthenticationAssessor.js +97 -1
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts +39 -0
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.js +330 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/FunctionalityAssessor.js +46 -13
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +5 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +81 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +1 -1
- package/lib/services/assessment/modules/PromptAssessor.d.ts +30 -0
- package/lib/services/assessment/modules/PromptAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/PromptAssessor.js +367 -0
- package/lib/services/assessment/modules/ResourceAssessor.d.ts +28 -0
- package/lib/services/assessment/modules/ResourceAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ResourceAssessor.js +296 -0
- package/lib/services/assessment/modules/SecurityAssessor.d.ts +4 -2
- package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/SecurityAssessor.js +10 -41
- package/lib/services/assessment/modules/TemporalAssessor.d.ts +1 -0
- package/lib/services/assessment/modules/TemporalAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/TemporalAssessor.js +35 -4
- package/lib/utils/jsonUtils.d.ts +68 -0
- package/lib/utils/jsonUtils.d.ts.map +1 -0
- package/lib/utils/jsonUtils.js +141 -0
- package/lib/utils/paramUtils.d.ts +11 -0
- package/lib/utils/paramUtils.d.ts.map +1 -0
- package/lib/utils/paramUtils.js +37 -0
- package/lib/utils/schemaUtils.d.ts +74 -0
- package/lib/utils/schemaUtils.d.ts.map +1 -0
- package/lib/utils/schemaUtils.js +268 -0
- 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;
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
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(
|
|
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,
|
|
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;
|
|
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
|
|
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"}
|