@bryan-thompson/inspector-assessment-client 1.26.5 → 1.26.7
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-DpdInvWI.js → OAuthCallback-CCWVtjr7.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-D1ImpKK5.js → OAuthDebugCallback-DqbXfUi4.js} +1 -1
- package/dist/assets/{index-umcoGmYw.js → index-CsDJSSWq.js} +4 -4
- package/dist/index.html +1 -1
- package/lib/lib/assessment/configTypes.d.ts +2 -0
- package/lib/lib/assessment/configTypes.d.ts.map +1 -1
- package/lib/lib/securityPatterns.d.ts +4 -2
- package/lib/lib/securityPatterns.d.ts.map +1 -1
- package/lib/lib/securityPatterns.js +146 -2
- package/lib/services/assessment/modules/AUPComplianceAssessor.js +9 -9
- package/lib/services/assessment/modules/AuthenticationAssessor.js +4 -4
- package/lib/services/assessment/modules/BaseAssessor.d.ts +0 -14
- package/lib/services/assessment/modules/BaseAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/BaseAssessor.js +1 -33
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.js +1 -1
- package/lib/services/assessment/modules/DeveloperExperienceAssessor.js +1 -1
- package/lib/services/assessment/modules/DocumentationAssessor.js +2 -2
- package/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ErrorHandlingAssessor.js +8 -8
- package/lib/services/assessment/modules/ExternalAPIScannerAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ExternalAPIScannerAssessor.js +3 -3
- package/lib/services/assessment/modules/FunctionalityAssessor.js +9 -9
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +12 -12
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +9 -5
- package/lib/services/assessment/modules/PortabilityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/PortabilityAssessor.js +3 -3
- package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.js +4 -4
- package/lib/services/assessment/modules/PromptAssessor.js +2 -2
- package/lib/services/assessment/modules/ProtocolComplianceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ProtocolComplianceAssessor.js +7 -7
- package/lib/services/assessment/modules/ProtocolConformanceAssessor.js +1 -1
- package/lib/services/assessment/modules/ResourceAssessor.js +1 -1
- package/lib/services/assessment/modules/SecurityAssessor.d.ts +25 -2
- package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/SecurityAssessor.js +149 -17
- package/lib/services/assessment/modules/TemporalAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/TemporalAssessor.js +10 -10
- package/lib/services/assessment/modules/ToolAnnotationAssessor.js +9 -9
- package/lib/services/assessment/modules/UsabilityAssessor.js +1 -1
- package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.d.ts.map +1 -1
- package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.js +37 -0
- package/lib/services/assessment/modules/index.d.ts +3 -0
- package/lib/services/assessment/modules/index.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/ChainExecutionTester.d.ts +104 -0
- package/lib/services/assessment/modules/securityTests/ChainExecutionTester.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/ChainExecutionTester.js +257 -0
- package/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts +57 -0
- package/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/ConfidenceScorer.js +199 -0
- package/lib/services/assessment/modules/securityTests/CrossToolStateTester.d.ts +91 -0
- package/lib/services/assessment/modules/securityTests/CrossToolStateTester.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/CrossToolStateTester.js +225 -0
- package/lib/services/assessment/modules/securityTests/ErrorClassifier.d.ts +57 -0
- package/lib/services/assessment/modules/securityTests/ErrorClassifier.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/ErrorClassifier.js +113 -0
- package/lib/services/assessment/modules/securityTests/ExecutionArtifactDetector.d.ts +49 -0
- package/lib/services/assessment/modules/securityTests/ExecutionArtifactDetector.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/ExecutionArtifactDetector.js +74 -0
- package/lib/services/assessment/modules/securityTests/MathAnalyzer.d.ts +58 -0
- package/lib/services/assessment/modules/securityTests/MathAnalyzer.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/MathAnalyzer.js +251 -0
- package/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts +59 -0
- package/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/SafeResponseDetector.js +151 -0
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +349 -0
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +904 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.js +49 -24
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +122 -85
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +443 -1176
- package/lib/services/assessment/modules/securityTests/index.d.ts +3 -1
- package/lib/services/assessment/modules/securityTests/index.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Tool State Tester
|
|
3
|
+
* Tests for privilege escalation by calling tools in sequence
|
|
4
|
+
*
|
|
5
|
+
* Issue #92, Challenge #7: Cross-tool state-based authorization bypass
|
|
6
|
+
* Detects when one tool can modify shared state that affects another tool's authorization.
|
|
7
|
+
*
|
|
8
|
+
* Attack flow:
|
|
9
|
+
* 1. Call admin_action → should get "access denied"
|
|
10
|
+
* 2. Call config_modifier with "admin_mode=true"
|
|
11
|
+
* 3. Call admin_action again → if now succeeds, VULNERABLE
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Tests for cross-tool privilege escalation via shared mutable state
|
|
15
|
+
*/
|
|
16
|
+
export class CrossToolStateTester {
|
|
17
|
+
verbose;
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
// Note: config.timeout is accepted but tool call timeout is handled externally
|
|
20
|
+
this.verbose = config.verbose ?? false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Log message if verbose logging is enabled
|
|
24
|
+
*/
|
|
25
|
+
log(message) {
|
|
26
|
+
if (this.verbose) {
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.log(`[CrossToolStateTester] ${message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Identify potential cross-tool pairs for testing
|
|
33
|
+
* Looks for admin_action/privileged tools and config_modifier/setting tools
|
|
34
|
+
*/
|
|
35
|
+
identifyCrossToolPairs(tools) {
|
|
36
|
+
const pairs = [];
|
|
37
|
+
// Find admin-like tools (tools that check authorization)
|
|
38
|
+
const adminTools = tools.filter((t) => /admin[_-]?action|privileged|elevated|sensitive/i.test(t.name) ||
|
|
39
|
+
/admin[_-]?action|privileged|requires\s+admin/i.test(t.description || ""));
|
|
40
|
+
// Find modifier-like tools (tools that modify state/config)
|
|
41
|
+
const modifierTools = tools.filter((t) => /config[_-]?modifier|setting|mode|enable|activate/i.test(t.name) ||
|
|
42
|
+
/modify.*config|set.*mode|enable.*admin/i.test(t.description || ""));
|
|
43
|
+
for (const admin of adminTools) {
|
|
44
|
+
for (const modifier of modifierTools) {
|
|
45
|
+
if (admin.name !== modifier.name) {
|
|
46
|
+
pairs.push({ admin, modifier });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return pairs;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Test cross-tool privilege escalation
|
|
54
|
+
*
|
|
55
|
+
* Attack flow:
|
|
56
|
+
* 1. Call admin_action → expect "access denied"
|
|
57
|
+
* 2. Call config_modifier with "admin_mode=true"
|
|
58
|
+
* 3. Call admin_action again → if now succeeds, VULNERABLE
|
|
59
|
+
*/
|
|
60
|
+
async testPrivilegeEscalation(callTool, adminTool, modifierTool, onProgress) {
|
|
61
|
+
this.log(`Testing pair: ${modifierTool.name} → ${adminTool.name}`);
|
|
62
|
+
try {
|
|
63
|
+
// Step 1: Baseline - admin action should be denied
|
|
64
|
+
this.log(`Step 1: Calling ${adminTool.name} (baseline)`);
|
|
65
|
+
const baseline = await callTool(adminTool.name, {
|
|
66
|
+
action: "read secrets",
|
|
67
|
+
});
|
|
68
|
+
const baselineText = this.extractResponseText(baseline);
|
|
69
|
+
const baselineDenied = /access denied|unauthorized|admin.*required|permission.*denied/i.test(baselineText);
|
|
70
|
+
if (!baselineDenied) {
|
|
71
|
+
// Already has access - can't test escalation (might indicate other issues)
|
|
72
|
+
// Check if it's because shared_state_checked is false (hardened)
|
|
73
|
+
if (/"shared_state_checked"\s*:\s*false/i.test(baselineText)) {
|
|
74
|
+
return {
|
|
75
|
+
vulnerable: false,
|
|
76
|
+
reason: "baseline_has_access",
|
|
77
|
+
evidence: {
|
|
78
|
+
baseline: baselineText.slice(0, 500),
|
|
79
|
+
afterModifier: "",
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
vulnerable: false,
|
|
85
|
+
reason: "baseline_has_access",
|
|
86
|
+
evidence: {
|
|
87
|
+
baseline: baselineText.slice(0, 500),
|
|
88
|
+
afterModifier: "",
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Step 2: Attempt to enable admin mode via config modifier
|
|
93
|
+
const enableResult = await callTool(modifierTool.name, {
|
|
94
|
+
setting: "admin_mode=true",
|
|
95
|
+
});
|
|
96
|
+
const enableText = this.extractResponseText(enableResult);
|
|
97
|
+
const modeEnabled = /admin.*activated|elevated.*granted|admin_mode.*enabled|configuration.*updated.*admin/i.test(enableText);
|
|
98
|
+
// Check if modifier is hardened (stores without modifying)
|
|
99
|
+
const modifierHardened = /stored.*without.*modifying|stored.*for.*review|not.*modifying.*runtime/i.test(enableText);
|
|
100
|
+
if (modifierHardened) {
|
|
101
|
+
return {
|
|
102
|
+
vulnerable: false,
|
|
103
|
+
reason: "modifier_rejected",
|
|
104
|
+
evidence: {
|
|
105
|
+
baseline: baselineText.slice(0, 300),
|
|
106
|
+
afterModifier: "",
|
|
107
|
+
enableResult: enableText.slice(0, 300),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (!modeEnabled) {
|
|
112
|
+
return {
|
|
113
|
+
vulnerable: false,
|
|
114
|
+
reason: "modifier_rejected",
|
|
115
|
+
evidence: {
|
|
116
|
+
baseline: baselineText.slice(0, 300),
|
|
117
|
+
afterModifier: "",
|
|
118
|
+
enableResult: enableText.slice(0, 300),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Step 3: Test if admin action now succeeds
|
|
123
|
+
const escalated = await callTool(adminTool.name, {
|
|
124
|
+
action: "read secrets",
|
|
125
|
+
});
|
|
126
|
+
const escalatedText = this.extractResponseText(escalated);
|
|
127
|
+
// Check for successful escalation
|
|
128
|
+
const escalationSucceeded = !/access denied|unauthorized|admin.*required|permission.*denied/i.test(escalatedText) &&
|
|
129
|
+
/"secrets"|API_KEY|credentials|admin_mode.*true.*evidence.*cross-tool/i.test(escalatedText);
|
|
130
|
+
// Emit progress event if callback provided
|
|
131
|
+
if (onProgress && escalationSucceeded) {
|
|
132
|
+
onProgress({
|
|
133
|
+
type: "vulnerability_found",
|
|
134
|
+
tool: adminTool.name,
|
|
135
|
+
pattern: "Cross-Tool State Bypass",
|
|
136
|
+
confidence: "high",
|
|
137
|
+
evidence: `Cross-tool privilege escalation: ${modifierTool.name} enables access to ${adminTool.name}. ${escalatedText.slice(0, 200)}`,
|
|
138
|
+
riskLevel: "HIGH",
|
|
139
|
+
requiresReview: false,
|
|
140
|
+
payload: "admin_mode=true",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
vulnerable: escalationSucceeded,
|
|
145
|
+
reason: escalationSucceeded
|
|
146
|
+
? "privilege_escalation_confirmed"
|
|
147
|
+
: "escalation_blocked",
|
|
148
|
+
evidence: {
|
|
149
|
+
baseline: baselineText.slice(0, 300),
|
|
150
|
+
afterModifier: escalatedText.slice(0, 300),
|
|
151
|
+
enableResult: enableText.slice(0, 300),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
return {
|
|
157
|
+
vulnerable: false,
|
|
158
|
+
reason: "test_error",
|
|
159
|
+
error: error instanceof Error ? error.message : String(error),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Run sequence tests on all identified tool pairs
|
|
165
|
+
*/
|
|
166
|
+
async runAllSequenceTests(tools, callTool, onProgress) {
|
|
167
|
+
const pairs = this.identifyCrossToolPairs(tools);
|
|
168
|
+
const results = new Map();
|
|
169
|
+
for (const { admin, modifier } of pairs) {
|
|
170
|
+
const key = `${modifier.name} → ${admin.name}`;
|
|
171
|
+
const result = await this.testPrivilegeEscalation(callTool, admin, modifier, onProgress);
|
|
172
|
+
results.set(key, result);
|
|
173
|
+
}
|
|
174
|
+
return results;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get summary of sequence test results
|
|
178
|
+
*/
|
|
179
|
+
summarizeResults(results) {
|
|
180
|
+
let vulnerable = 0;
|
|
181
|
+
let safe = 0;
|
|
182
|
+
let errors = 0;
|
|
183
|
+
const vulnerablePairs = [];
|
|
184
|
+
for (const [key, result] of results) {
|
|
185
|
+
if (result.reason === "test_error") {
|
|
186
|
+
errors++;
|
|
187
|
+
}
|
|
188
|
+
else if (result.vulnerable) {
|
|
189
|
+
vulnerable++;
|
|
190
|
+
vulnerablePairs.push(key);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
safe++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
total: results.size,
|
|
198
|
+
vulnerable,
|
|
199
|
+
safe,
|
|
200
|
+
errors,
|
|
201
|
+
vulnerablePairs,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Extract text content from MCP response
|
|
206
|
+
*/
|
|
207
|
+
extractResponseText(response) {
|
|
208
|
+
if (!response)
|
|
209
|
+
return "";
|
|
210
|
+
// Handle content array format
|
|
211
|
+
if (response.content && Array.isArray(response.content)) {
|
|
212
|
+
return response.content
|
|
213
|
+
.map((item) => {
|
|
214
|
+
if (typeof item === "string")
|
|
215
|
+
return item;
|
|
216
|
+
if (item && typeof item === "object" && "text" in item)
|
|
217
|
+
return String(item.text);
|
|
218
|
+
return JSON.stringify(item);
|
|
219
|
+
})
|
|
220
|
+
.join("\n");
|
|
221
|
+
}
|
|
222
|
+
// Fallback to JSON stringify
|
|
223
|
+
return JSON.stringify(response);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Classifier
|
|
3
|
+
* Classifies and analyzes error responses for security testing
|
|
4
|
+
*
|
|
5
|
+
* Extracted from SecurityResponseAnalyzer.ts (Issue #53)
|
|
6
|
+
* Handles: connection error detection, error classification, error info extraction
|
|
7
|
+
*/
|
|
8
|
+
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Error classification types
|
|
11
|
+
*/
|
|
12
|
+
export type ErrorClassification = "connection" | "server" | "protocol";
|
|
13
|
+
/**
|
|
14
|
+
* Extracted error information from response
|
|
15
|
+
*/
|
|
16
|
+
export interface ErrorInfo {
|
|
17
|
+
code?: string | number;
|
|
18
|
+
message?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Classifies errors from tool responses and exceptions
|
|
22
|
+
*/
|
|
23
|
+
export declare class ErrorClassifier {
|
|
24
|
+
/**
|
|
25
|
+
* Check if response indicates connection/server failure
|
|
26
|
+
*/
|
|
27
|
+
isConnectionError(response: CompatibilityCallToolResult): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Check if caught exception indicates connection/server failure
|
|
30
|
+
*/
|
|
31
|
+
isConnectionErrorFromException(error: unknown): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Internal: Check if text indicates connection/server failure
|
|
34
|
+
*/
|
|
35
|
+
private isConnectionErrorFromText;
|
|
36
|
+
/**
|
|
37
|
+
* Classify error type for reporting
|
|
38
|
+
*/
|
|
39
|
+
classifyError(response: CompatibilityCallToolResult): ErrorClassification;
|
|
40
|
+
/**
|
|
41
|
+
* Classify error type from caught exception
|
|
42
|
+
*/
|
|
43
|
+
classifyErrorFromException(error: unknown): ErrorClassification;
|
|
44
|
+
/**
|
|
45
|
+
* Internal: Classify error type from text
|
|
46
|
+
*/
|
|
47
|
+
private classifyErrorFromText;
|
|
48
|
+
/**
|
|
49
|
+
* Extract error info from response
|
|
50
|
+
*/
|
|
51
|
+
extractErrorInfo(response: CompatibilityCallToolResult): ErrorInfo;
|
|
52
|
+
/**
|
|
53
|
+
* Extract response content from MCP response
|
|
54
|
+
*/
|
|
55
|
+
extractResponseContent(response: CompatibilityCallToolResult): string;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=ErrorClassifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorClassifier.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/ErrorClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAQjF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B;;OAEG;IACH,iBAAiB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,OAAO;IAKjE;;OAEG;IACH,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAQvD;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAgBjC;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,2BAA2B,GAAG,mBAAmB;IAKzE;;OAEG;IACH,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB;IAQ/D;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,SAAS;IAsBlE;;OAEG;IACH,sBAAsB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,MAAM;CAUtE"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Classifier
|
|
3
|
+
* Classifies and analyzes error responses for security testing
|
|
4
|
+
*
|
|
5
|
+
* Extracted from SecurityResponseAnalyzer.ts (Issue #53)
|
|
6
|
+
* Handles: connection error detection, error classification, error info extraction
|
|
7
|
+
*/
|
|
8
|
+
import { CONNECTION_ERROR_PATTERNS, ERROR_CLASSIFICATION_PATTERNS, matchesAny, hasMcpErrorPrefix, } from "./SecurityPatternLibrary.js";
|
|
9
|
+
/**
|
|
10
|
+
* Classifies errors from tool responses and exceptions
|
|
11
|
+
*/
|
|
12
|
+
export class ErrorClassifier {
|
|
13
|
+
/**
|
|
14
|
+
* Check if response indicates connection/server failure
|
|
15
|
+
*/
|
|
16
|
+
isConnectionError(response) {
|
|
17
|
+
const text = this.extractResponseContent(response).toLowerCase();
|
|
18
|
+
return this.isConnectionErrorFromText(text);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if caught exception indicates connection/server failure
|
|
22
|
+
*/
|
|
23
|
+
isConnectionErrorFromException(error) {
|
|
24
|
+
if (error instanceof Error) {
|
|
25
|
+
const message = error.message.toLowerCase();
|
|
26
|
+
return this.isConnectionErrorFromText(message);
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Internal: Check if text indicates connection/server failure
|
|
32
|
+
*/
|
|
33
|
+
isConnectionErrorFromText(text) {
|
|
34
|
+
// Check unambiguous patterns first
|
|
35
|
+
if (matchesAny(CONNECTION_ERROR_PATTERNS.unambiguous, text)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
// Check contextual patterns only if text has MCP error prefix
|
|
39
|
+
if (hasMcpErrorPrefix(text)) {
|
|
40
|
+
if (matchesAny(CONNECTION_ERROR_PATTERNS.contextual, text)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Classify error type for reporting
|
|
48
|
+
*/
|
|
49
|
+
classifyError(response) {
|
|
50
|
+
const text = this.extractResponseContent(response).toLowerCase();
|
|
51
|
+
return this.classifyErrorFromText(text);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Classify error type from caught exception
|
|
55
|
+
*/
|
|
56
|
+
classifyErrorFromException(error) {
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
const message = error.message.toLowerCase();
|
|
59
|
+
return this.classifyErrorFromText(message);
|
|
60
|
+
}
|
|
61
|
+
return "protocol";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Internal: Classify error type from text
|
|
65
|
+
*/
|
|
66
|
+
classifyErrorFromText(text) {
|
|
67
|
+
if (ERROR_CLASSIFICATION_PATTERNS.connection.test(text)) {
|
|
68
|
+
return "connection";
|
|
69
|
+
}
|
|
70
|
+
if (ERROR_CLASSIFICATION_PATTERNS.server.test(text)) {
|
|
71
|
+
return "server";
|
|
72
|
+
}
|
|
73
|
+
if (ERROR_CLASSIFICATION_PATTERNS.protocol.test(text)) {
|
|
74
|
+
return "protocol";
|
|
75
|
+
}
|
|
76
|
+
return "protocol";
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extract error info from response
|
|
80
|
+
*/
|
|
81
|
+
extractErrorInfo(response) {
|
|
82
|
+
const content = this.extractResponseContent(response);
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(content);
|
|
85
|
+
if (parsed.error) {
|
|
86
|
+
return {
|
|
87
|
+
code: parsed.error.code || parsed.code,
|
|
88
|
+
message: parsed.error.message || parsed.message,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return { code: parsed.code, message: parsed.message };
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Check for MCP error format in text
|
|
95
|
+
const mcpMatch = content.match(/MCP error (-?\d+):\s*(.*)/i);
|
|
96
|
+
if (mcpMatch) {
|
|
97
|
+
return { code: parseInt(mcpMatch[1]), message: mcpMatch[2] };
|
|
98
|
+
}
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Extract response content from MCP response
|
|
104
|
+
*/
|
|
105
|
+
extractResponseContent(response) {
|
|
106
|
+
if (response.content && Array.isArray(response.content)) {
|
|
107
|
+
return response.content
|
|
108
|
+
.map((c) => c.type === "text" ? c.text : "")
|
|
109
|
+
.join(" ");
|
|
110
|
+
}
|
|
111
|
+
return String(response.content || "");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Artifact Detector
|
|
3
|
+
* Detects evidence of actual code/command execution in tool responses
|
|
4
|
+
*
|
|
5
|
+
* Extracted from SecurityResponseAnalyzer.ts (Issue #53)
|
|
6
|
+
* Handles: execution evidence detection, artifact detection, injection payload echoing
|
|
7
|
+
*/
|
|
8
|
+
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Result of response analysis
|
|
11
|
+
*/
|
|
12
|
+
export interface AnalysisResult {
|
|
13
|
+
isVulnerable: boolean;
|
|
14
|
+
evidence?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Detects execution artifacts in tool responses
|
|
18
|
+
*/
|
|
19
|
+
export declare class ExecutionArtifactDetector {
|
|
20
|
+
/**
|
|
21
|
+
* Check if response contains evidence of actual execution
|
|
22
|
+
* Used to distinguish between safe reflection and actual command/code execution
|
|
23
|
+
*/
|
|
24
|
+
hasExecutionEvidence(responseText: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Detect execution artifacts in response
|
|
27
|
+
* Two-tier detection: unambiguous artifacts + context-sensitive patterns
|
|
28
|
+
*/
|
|
29
|
+
detectExecutionArtifacts(responseText: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if response contains echoed injection payload patterns
|
|
32
|
+
* These indicate the payload was stored/reflected, not executed
|
|
33
|
+
*/
|
|
34
|
+
containsEchoedInjectionPayload(responseText: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Analyze injection response (fallback logic)
|
|
37
|
+
* Used when primary detection methods are inconclusive
|
|
38
|
+
*
|
|
39
|
+
* @param responseText The response text to analyze
|
|
40
|
+
* @param isReflectionCheck Function to check if response is reflection
|
|
41
|
+
* @returns Analysis result with vulnerability status
|
|
42
|
+
*/
|
|
43
|
+
analyzeInjectionResponse(responseText: string, isReflectionCheck: (text: string) => boolean): AnalysisResult;
|
|
44
|
+
/**
|
|
45
|
+
* Extract response content from MCP response
|
|
46
|
+
*/
|
|
47
|
+
extractResponseContent(response: CompatibilityCallToolResult): string;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=ExecutionArtifactDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExecutionArtifactDetector.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/ExecutionArtifactDetector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AASjF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,yBAAyB;IACpC;;;OAGG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAInD;;;OAGG;IACH,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAqBvD;;;OAGG;IACH,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAI7D;;;;;;;OAOG;IACH,wBAAwB,CACtB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC3C,cAAc;IAajB;;OAEG;IACH,sBAAsB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,MAAM;CAUtE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Artifact Detector
|
|
3
|
+
* Detects evidence of actual code/command execution in tool responses
|
|
4
|
+
*
|
|
5
|
+
* Extracted from SecurityResponseAnalyzer.ts (Issue #53)
|
|
6
|
+
* Handles: execution evidence detection, artifact detection, injection payload echoing
|
|
7
|
+
*/
|
|
8
|
+
import { EXECUTION_INDICATORS, EXECUTION_ARTIFACT_PATTERNS, ECHOED_PAYLOAD_PATTERNS, FALLBACK_EXECUTION_PATTERNS, matchesAny, } from "./SecurityPatternLibrary.js";
|
|
9
|
+
/**
|
|
10
|
+
* Detects execution artifacts in tool responses
|
|
11
|
+
*/
|
|
12
|
+
export class ExecutionArtifactDetector {
|
|
13
|
+
/**
|
|
14
|
+
* Check if response contains evidence of actual execution
|
|
15
|
+
* Used to distinguish between safe reflection and actual command/code execution
|
|
16
|
+
*/
|
|
17
|
+
hasExecutionEvidence(responseText) {
|
|
18
|
+
return matchesAny(EXECUTION_INDICATORS, responseText);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Detect execution artifacts in response
|
|
22
|
+
* Two-tier detection: unambiguous artifacts + context-sensitive patterns
|
|
23
|
+
*/
|
|
24
|
+
detectExecutionArtifacts(responseText) {
|
|
25
|
+
const containsEchoedPayload = this.containsEchoedInjectionPayload(responseText);
|
|
26
|
+
// Check always-execution patterns
|
|
27
|
+
if (matchesAny(EXECUTION_ARTIFACT_PATTERNS.alwaysExecution, responseText)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// Context-sensitive patterns only count if no echoed payload present
|
|
31
|
+
if (!containsEchoedPayload) {
|
|
32
|
+
if (matchesAny(EXECUTION_ARTIFACT_PATTERNS.contextSensitive, responseText)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if response contains echoed injection payload patterns
|
|
40
|
+
* These indicate the payload was stored/reflected, not executed
|
|
41
|
+
*/
|
|
42
|
+
containsEchoedInjectionPayload(responseText) {
|
|
43
|
+
return matchesAny(ECHOED_PAYLOAD_PATTERNS, responseText);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Analyze injection response (fallback logic)
|
|
47
|
+
* Used when primary detection methods are inconclusive
|
|
48
|
+
*
|
|
49
|
+
* @param responseText The response text to analyze
|
|
50
|
+
* @param isReflectionCheck Function to check if response is reflection
|
|
51
|
+
* @returns Analysis result with vulnerability status
|
|
52
|
+
*/
|
|
53
|
+
analyzeInjectionResponse(responseText, isReflectionCheck) {
|
|
54
|
+
const hasExecution = matchesAny(FALLBACK_EXECUTION_PATTERNS, responseText);
|
|
55
|
+
if (hasExecution && !isReflectionCheck(responseText)) {
|
|
56
|
+
return {
|
|
57
|
+
isVulnerable: true,
|
|
58
|
+
evidence: "Tool executed instruction: found execution keywords",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { isVulnerable: false };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extract response content from MCP response
|
|
65
|
+
*/
|
|
66
|
+
extractResponseContent(response) {
|
|
67
|
+
if (response.content && Array.isArray(response.content)) {
|
|
68
|
+
return response.content
|
|
69
|
+
.map((c) => c.type === "text" ? c.text : "")
|
|
70
|
+
.join(" ");
|
|
71
|
+
}
|
|
72
|
+
return String(response.content || "");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Math Analyzer
|
|
3
|
+
* Detects computed math expression results (Calculator Injection)
|
|
4
|
+
*
|
|
5
|
+
* Extracted from SecurityResponseAnalyzer.ts (Issue #53)
|
|
6
|
+
* Handles: math computation detection, coincidental numeric detection
|
|
7
|
+
*/
|
|
8
|
+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Result of computed math analysis with confidence level (Issue #58)
|
|
11
|
+
*/
|
|
12
|
+
export interface MathResultAnalysis {
|
|
13
|
+
isComputed: boolean;
|
|
14
|
+
confidence: "high" | "medium" | "low";
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Analyzes tool responses for math computation evidence (Calculator Injection)
|
|
19
|
+
*/
|
|
20
|
+
export declare class MathAnalyzer {
|
|
21
|
+
/**
|
|
22
|
+
* Enhanced computed math result analysis with tool context (Issue #58)
|
|
23
|
+
*
|
|
24
|
+
* Returns a confidence level indicating how likely this is a real Calculator Injection:
|
|
25
|
+
* - high: Strong evidence of computation (should flag as vulnerable)
|
|
26
|
+
* - medium: Ambiguous (excluded from vulnerability count per user decision)
|
|
27
|
+
* - low: Likely coincidental data (excluded from vulnerability count)
|
|
28
|
+
*/
|
|
29
|
+
analyzeComputedMathResult(payload: string, responseText: string, tool?: Tool): MathResultAnalysis;
|
|
30
|
+
/**
|
|
31
|
+
* Legacy method for backward compatibility
|
|
32
|
+
* @deprecated Use analyzeComputedMathResult instead
|
|
33
|
+
*/
|
|
34
|
+
isComputedMathResult(payload: string, responseText: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Check if numeric value appears in structured data context (not as computation result)
|
|
37
|
+
* Distinguishes {"records": 4} from computed "4" (Issue #58)
|
|
38
|
+
*
|
|
39
|
+
* @param result The computed numeric result to check for
|
|
40
|
+
* @param responseText The response text to analyze
|
|
41
|
+
* @returns true if the number appears to be coincidental data, not a computed result
|
|
42
|
+
*/
|
|
43
|
+
isCoincidentalNumericInStructuredData(result: number, responseText: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Recursively check JSON object for coincidental numeric values
|
|
46
|
+
*/
|
|
47
|
+
private checkObjectForCoincidentalNumeric;
|
|
48
|
+
/**
|
|
49
|
+
* Check text for structured patterns containing coincidental numerics
|
|
50
|
+
*/
|
|
51
|
+
private checkTextForCoincidentalNumeric;
|
|
52
|
+
/**
|
|
53
|
+
* Compute math expression from regex match
|
|
54
|
+
* Returns null if invalid operator
|
|
55
|
+
*/
|
|
56
|
+
private computeExpression;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=MathAnalyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MathAnalyzer.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/MathAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAW1D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB;;;;;;;OAOG;IACH,yBAAyB,CACvB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,IAAI,GACV,kBAAkB;IAkIrB;;;OAGG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAKpE;;;;;;;OAOG;IACH,qCAAqC,CACnC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO;IAWV;;OAEG;IACH,OAAO,CAAC,iCAAiC;IAyCzC;;OAEG;IACH,OAAO,CAAC,+BAA+B;IAqBvC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;CA+C1B"}
|