@bryan-thompson/inspector-assessment-client 1.25.1 → 1.25.5
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-CkzX_H4T.js → OAuthCallback-Dl4GYls3.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-jZEkm74B.js → OAuthDebugCallback-BdJ38Z-r.js} +1 -1
- package/dist/assets/{index-Df9Sx1jt.css → index-cHhcEXbr.css} +4 -0
- package/dist/assets/{index-BVx1dGJT.js → index-pfUiTdQb.js} +4 -4
- package/dist/index.html +2 -2
- package/lib/lib/assessment/configTypes.d.ts +3 -0
- package/lib/lib/assessment/configTypes.d.ts.map +1 -1
- package/lib/lib/assessment/configTypes.js +11 -6
- package/lib/lib/assessment/coreTypes.d.ts +65 -0
- package/lib/lib/assessment/coreTypes.d.ts.map +1 -1
- package/lib/lib/assessment/extendedTypes.d.ts +127 -0
- package/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
- package/lib/lib/assessment/resultTypes.d.ts +45 -0
- package/lib/lib/assessment/resultTypes.d.ts.map +1 -1
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +4 -12
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
- package/lib/services/assessment/AssessmentOrchestrator.js +49 -238
- package/lib/services/assessment/TestDataGenerator.d.ts +9 -1
- package/lib/services/assessment/TestDataGenerator.d.ts.map +1 -1
- package/lib/services/assessment/TestDataGenerator.js +32 -6
- package/lib/services/assessment/TestScenarioEngine.d.ts +9 -1
- package/lib/services/assessment/TestScenarioEngine.d.ts.map +1 -1
- package/lib/services/assessment/TestScenarioEngine.js +17 -14
- package/lib/services/assessment/ToolClassifier.d.ts +154 -27
- package/lib/services/assessment/ToolClassifier.d.ts.map +1 -1
- package/lib/services/assessment/ToolClassifier.js +171 -318
- package/lib/services/assessment/config/annotationPatterns.d.ts +3 -1
- package/lib/services/assessment/config/annotationPatterns.d.ts.map +1 -1
- package/lib/services/assessment/config/annotationPatterns.js +5 -2
- package/lib/services/assessment/config/architecturePatterns.d.ts +101 -0
- package/lib/services/assessment/config/architecturePatterns.d.ts.map +1 -0
- package/lib/services/assessment/config/architecturePatterns.js +248 -0
- package/lib/services/assessment/config/performanceConfig.d.ts +122 -0
- package/lib/services/assessment/config/performanceConfig.d.ts.map +1 -0
- package/lib/services/assessment/config/performanceConfig.js +154 -0
- package/lib/services/assessment/config/sanitizationPatterns.d.ts +63 -0
- package/lib/services/assessment/config/sanitizationPatterns.d.ts.map +1 -0
- package/lib/services/assessment/config/sanitizationPatterns.js +223 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts +3 -1
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -1
- package/lib/services/assessment/lib/claudeCodeBridge.js +5 -3
- package/lib/services/assessment/lib/concurrencyLimit.d.ts +6 -2
- package/lib/services/assessment/lib/concurrencyLimit.d.ts.map +1 -1
- package/lib/services/assessment/lib/concurrencyLimit.js +13 -6
- package/lib/services/assessment/lib/errors.d.ts +90 -0
- package/lib/services/assessment/lib/errors.d.ts.map +1 -0
- package/lib/services/assessment/lib/errors.js +136 -0
- package/lib/services/assessment/lib/timeoutUtils.d.ts +69 -0
- package/lib/services/assessment/lib/timeoutUtils.d.ts.map +1 -0
- package/lib/services/assessment/lib/timeoutUtils.js +103 -0
- package/lib/services/assessment/modules/BaseAssessor.d.ts +43 -8
- package/lib/services/assessment/modules/BaseAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/BaseAssessor.js +103 -34
- package/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts +38 -1
- package/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/DeveloperExperienceAssessor.js +185 -19
- package/lib/services/assessment/modules/DocumentationAssessor.d.ts +5 -0
- package/lib/services/assessment/modules/DocumentationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/DocumentationAssessor.js +11 -0
- package/lib/services/assessment/modules/ErrorHandlingAssessor.js +1 -1
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/FunctionalityAssessor.js +6 -3
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +3 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +14 -2
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +7 -2
- package/lib/services/assessment/modules/PromptAssessor.d.ts +1 -0
- package/lib/services/assessment/modules/PromptAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/PromptAssessor.js +26 -16
- package/lib/services/assessment/modules/ProtocolComplianceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ProtocolComplianceAssessor.js +6 -2
- package/lib/services/assessment/modules/ProtocolConformanceAssessor.d.ts +5 -0
- package/lib/services/assessment/modules/ProtocolConformanceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ProtocolConformanceAssessor.js +15 -0
- package/lib/services/assessment/modules/ResourceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ResourceAssessor.js +8 -2
- package/lib/services/assessment/modules/SecurityAssessor.d.ts +3 -171
- package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/SecurityAssessor.js +25 -1480
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts +27 -28
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ToolAnnotationAssessor.js +340 -863
- package/lib/services/assessment/modules/UsabilityAssessor.d.ts +5 -0
- package/lib/services/assessment/modules/UsabilityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/UsabilityAssessor.js +11 -0
- package/lib/services/assessment/modules/annotations/AnnotationDeceptionDetector.d.ts +57 -0
- package/lib/services/assessment/modules/annotations/AnnotationDeceptionDetector.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/AnnotationDeceptionDetector.js +176 -0
- package/lib/services/assessment/modules/annotations/ArchitectureDetector.d.ts +67 -0
- package/lib/services/assessment/modules/annotations/ArchitectureDetector.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/ArchitectureDetector.js +239 -0
- package/lib/services/assessment/modules/annotations/BehaviorInference.d.ts +46 -0
- package/lib/services/assessment/modules/annotations/BehaviorInference.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/BehaviorInference.js +394 -0
- package/lib/services/assessment/modules/annotations/DescriptionAnalyzer.d.ts +64 -0
- package/lib/services/assessment/modules/annotations/DescriptionAnalyzer.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/DescriptionAnalyzer.js +304 -0
- package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.d.ts +43 -0
- package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.js +276 -0
- package/lib/services/assessment/modules/annotations/SchemaAnalyzer.d.ts +122 -0
- package/lib/services/assessment/modules/annotations/SchemaAnalyzer.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/SchemaAnalyzer.js +388 -0
- package/lib/services/assessment/modules/annotations/index.d.ts +13 -0
- package/lib/services/assessment/modules/annotations/index.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/index.js +15 -0
- package/lib/services/assessment/modules/index.d.ts +10 -0
- package/lib/services/assessment/modules/index.d.ts.map +1 -1
- package/lib/services/assessment/modules/index.js +13 -0
- package/lib/services/assessment/modules/securityTests/SanitizationDetector.d.ts +125 -0
- package/lib/services/assessment/modules/securityTests/SanitizationDetector.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/SanitizationDetector.js +345 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.d.ts +33 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.js +128 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts +67 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +372 -0
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +178 -0
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +1207 -0
- package/lib/services/assessment/modules/securityTests/index.d.ts +8 -0
- package/lib/services/assessment/modules/securityTests/index.d.ts.map +1 -0
- package/lib/services/assessment/modules/securityTests/index.js +7 -0
- package/lib/services/assessment/orchestratorHelpers.d.ts +83 -0
- package/lib/services/assessment/orchestratorHelpers.d.ts.map +1 -0
- package/lib/services/assessment/orchestratorHelpers.js +212 -0
- package/lib/services/assessment/tool-classifier-patterns.d.ts +85 -0
- package/lib/services/assessment/tool-classifier-patterns.d.ts.map +1 -0
- package/lib/services/assessment/tool-classifier-patterns.js +365 -0
- package/package.json +1 -1
|
@@ -16,17 +16,35 @@
|
|
|
16
16
|
* - Deserialization (1): Insecure Deserialization
|
|
17
17
|
*/
|
|
18
18
|
import { BaseAssessor } from "./BaseAssessor.js";
|
|
19
|
-
import {
|
|
19
|
+
import { SecurityPayloadTester, SecurityPayloadGenerator, } from "./securityTests/index.js";
|
|
20
20
|
import { ToolClassifier, ToolCategory } from "../ToolClassifier.js";
|
|
21
|
-
import { createConcurrencyLimit } from "../lib/concurrencyLimit.js";
|
|
22
|
-
import { LanguageAwarePayloadGenerator } from "../LanguageAwarePayloadGenerator.js";
|
|
23
21
|
export class SecurityAssessor extends BaseAssessor {
|
|
24
|
-
|
|
22
|
+
payloadTester;
|
|
23
|
+
payloadGenerator;
|
|
24
|
+
constructor(config) {
|
|
25
|
+
super(config);
|
|
26
|
+
// Initialize payload generator for additional checks
|
|
27
|
+
this.payloadGenerator = new SecurityPayloadGenerator();
|
|
28
|
+
// Create payload tester config from assessment config
|
|
29
|
+
const payloadConfig = {
|
|
30
|
+
enableDomainTesting: config.enableDomainTesting,
|
|
31
|
+
maxParallelTests: config.maxParallelTests,
|
|
32
|
+
securityTestTimeout: config.securityTestTimeout,
|
|
33
|
+
selectedToolsForTesting: config.selectedToolsForTesting,
|
|
34
|
+
};
|
|
35
|
+
// Create logger adapter
|
|
36
|
+
const testLogger = {
|
|
37
|
+
log: (message) => this.log(message),
|
|
38
|
+
logError: (message, error) => this.logError(message, error),
|
|
39
|
+
};
|
|
40
|
+
// Initialize payload tester with config, logger, and timeout function
|
|
41
|
+
this.payloadTester = new SecurityPayloadTester(payloadConfig, testLogger, this.executeWithTimeout.bind(this));
|
|
42
|
+
}
|
|
25
43
|
async assess(context) {
|
|
26
44
|
// Select tools for testing first
|
|
27
45
|
const toolsToTest = this.selectToolsForTesting(context.tools);
|
|
28
|
-
// Run universal security testing
|
|
29
|
-
const allTests = await this.runUniversalSecurityTests(context);
|
|
46
|
+
// Run universal security testing via extracted payload tester
|
|
47
|
+
const allTests = await this.payloadTester.runUniversalSecurityTests(toolsToTest, context.callTool, context.onProgress);
|
|
30
48
|
// Separate connection errors from valid tests
|
|
31
49
|
const connectionErrors = allTests.filter((t) => t.connectionError === true);
|
|
32
50
|
const validTests = allTests.filter((t) => !t.connectionError);
|
|
@@ -107,887 +125,6 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
107
125
|
this.log(`Testing all ${tools.length} tools for security`);
|
|
108
126
|
return tools;
|
|
109
127
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Run comprehensive security tests (advanced mode)
|
|
112
|
-
* Tests selected tools with ALL 23 security patterns using diverse payloads
|
|
113
|
-
* Includes injection tests, validation tests, and protocol compliance checks
|
|
114
|
-
*/
|
|
115
|
-
async runUniversalSecurityTests(context) {
|
|
116
|
-
// Check if advanced security testing is enabled
|
|
117
|
-
if (!this.config.enableDomainTesting) {
|
|
118
|
-
return this.runBasicSecurityTests(context);
|
|
119
|
-
}
|
|
120
|
-
const results = [];
|
|
121
|
-
const attackPatterns = getAllAttackPatterns();
|
|
122
|
-
// Select tools for testing
|
|
123
|
-
const toolsToTest = this.selectToolsForTesting(context.tools);
|
|
124
|
-
// Parallel tool testing with concurrency limit
|
|
125
|
-
const concurrency = this.config.maxParallelTests ?? 5;
|
|
126
|
-
const limit = createConcurrencyLimit(concurrency);
|
|
127
|
-
// Progress tracking for batched events - pre-calculate exact payload count
|
|
128
|
-
let totalPayloads = 0;
|
|
129
|
-
for (const pattern of attackPatterns) {
|
|
130
|
-
totalPayloads += getPayloadsForAttack(pattern.attackName).length;
|
|
131
|
-
}
|
|
132
|
-
const totalEstimate = toolsToTest.length * totalPayloads;
|
|
133
|
-
let completedTests = 0;
|
|
134
|
-
let lastBatchTime = Date.now();
|
|
135
|
-
const startTime = Date.now();
|
|
136
|
-
const BATCH_INTERVAL = 500; // ms
|
|
137
|
-
const BATCH_SIZE = 10; // tests
|
|
138
|
-
let batchCount = 0;
|
|
139
|
-
// Helper to emit batched progress
|
|
140
|
-
const emitProgressBatch = () => {
|
|
141
|
-
if (context.onProgress) {
|
|
142
|
-
context.onProgress({
|
|
143
|
-
type: "test_batch",
|
|
144
|
-
module: "security",
|
|
145
|
-
completed: completedTests,
|
|
146
|
-
total: totalEstimate,
|
|
147
|
-
batchSize: batchCount,
|
|
148
|
-
elapsed: Date.now() - startTime,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
batchCount = 0;
|
|
152
|
-
lastBatchTime = Date.now();
|
|
153
|
-
};
|
|
154
|
-
this.log(`Starting ADVANCED security assessment - testing ${toolsToTest.length} tools with ${attackPatterns.length} security patterns (~${totalEstimate} tests) [concurrency: ${concurrency}]`);
|
|
155
|
-
const allToolResults = await Promise.all(toolsToTest.map((tool) => limit(async () => {
|
|
156
|
-
const toolResults = [];
|
|
157
|
-
// Tools with no input parameters can't be exploited via payload injection
|
|
158
|
-
// Add passing results so they appear in the UI
|
|
159
|
-
if (!this.hasInputParameters(tool)) {
|
|
160
|
-
this.log(`${tool.name} has no input parameters - adding passing results`);
|
|
161
|
-
// Add a passing result for each attack pattern so tool appears in UI
|
|
162
|
-
for (const attackPattern of attackPatterns) {
|
|
163
|
-
const payloads = getPayloadsForAttack(attackPattern.attackName);
|
|
164
|
-
// Add one passing result per payload type
|
|
165
|
-
for (const payload of payloads) {
|
|
166
|
-
toolResults.push({
|
|
167
|
-
testName: attackPattern.attackName,
|
|
168
|
-
description: payload.description,
|
|
169
|
-
payload: payload.payload,
|
|
170
|
-
riskLevel: payload.riskLevel,
|
|
171
|
-
toolName: tool.name,
|
|
172
|
-
vulnerable: false,
|
|
173
|
-
evidence: "Tool has no input parameters - cannot be exploited via payload injection",
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return toolResults;
|
|
178
|
-
}
|
|
179
|
-
this.log(`Testing ${tool.name} with all attack patterns`);
|
|
180
|
-
// Test with each attack type (all patterns in advanced mode)
|
|
181
|
-
for (const attackPattern of attackPatterns) {
|
|
182
|
-
// Get ALL payloads for this attack pattern
|
|
183
|
-
const payloads = getPayloadsForAttack(attackPattern.attackName);
|
|
184
|
-
// Test tool with each payload variation
|
|
185
|
-
for (const payload of payloads) {
|
|
186
|
-
this.testCount++;
|
|
187
|
-
completedTests++;
|
|
188
|
-
batchCount++;
|
|
189
|
-
try {
|
|
190
|
-
const result = await this.testPayload(tool, attackPattern.attackName, payload, context.callTool);
|
|
191
|
-
toolResults.push(result);
|
|
192
|
-
if (result.vulnerable) {
|
|
193
|
-
this.log(`🚨 VULNERABILITY: ${tool.name} - ${attackPattern.attackName} (${payload.payloadType}: ${payload.description})`);
|
|
194
|
-
// Emit real-time vulnerability_found event
|
|
195
|
-
if (context.onProgress) {
|
|
196
|
-
context.onProgress({
|
|
197
|
-
type: "vulnerability_found",
|
|
198
|
-
tool: tool.name,
|
|
199
|
-
pattern: attackPattern.attackName,
|
|
200
|
-
confidence: result.confidence || "medium",
|
|
201
|
-
evidence: result.evidence || "Vulnerability detected",
|
|
202
|
-
riskLevel: payload.riskLevel,
|
|
203
|
-
requiresReview: result.requiresManualReview || false,
|
|
204
|
-
payload: payload.payload,
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
this.logError(`Error testing ${tool.name} with ${attackPattern.attackName}`, error);
|
|
211
|
-
}
|
|
212
|
-
// Emit progress batch if threshold reached
|
|
213
|
-
const timeSinceLastBatch = Date.now() - lastBatchTime;
|
|
214
|
-
if (batchCount >= BATCH_SIZE ||
|
|
215
|
-
timeSinceLastBatch >= BATCH_INTERVAL) {
|
|
216
|
-
emitProgressBatch();
|
|
217
|
-
}
|
|
218
|
-
// Rate limiting
|
|
219
|
-
if (this.testCount % 5 === 0) {
|
|
220
|
-
await this.sleep(100);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return toolResults;
|
|
225
|
-
})));
|
|
226
|
-
// Flatten all tool results into the main results array
|
|
227
|
-
for (const toolResults of allToolResults) {
|
|
228
|
-
results.push(...toolResults);
|
|
229
|
-
}
|
|
230
|
-
// Final flush of any remaining progress
|
|
231
|
-
if (batchCount > 0) {
|
|
232
|
-
emitProgressBatch();
|
|
233
|
-
}
|
|
234
|
-
this.log(`ADVANCED security assessment complete: ${results.length} tests executed, ${results.filter((r) => r.vulnerable).length} vulnerabilities found`);
|
|
235
|
-
return results;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Run basic security tests (fast mode)
|
|
239
|
-
* Tests only 3 critical injection patterns with 1 generic payload each
|
|
240
|
-
* Used when enableDomainTesting = false
|
|
241
|
-
*/
|
|
242
|
-
async runBasicSecurityTests(context) {
|
|
243
|
-
const results = [];
|
|
244
|
-
// Only test 5 critical injection patterns
|
|
245
|
-
const criticalPatterns = [
|
|
246
|
-
"Command Injection",
|
|
247
|
-
"Calculator Injection",
|
|
248
|
-
"SQL Injection",
|
|
249
|
-
"Path Traversal",
|
|
250
|
-
"Unicode Bypass",
|
|
251
|
-
];
|
|
252
|
-
const allPatterns = getAllAttackPatterns();
|
|
253
|
-
const basicPatterns = allPatterns.filter((p) => criticalPatterns.includes(p.attackName));
|
|
254
|
-
// Select tools for testing
|
|
255
|
-
const toolsToTest = this.selectToolsForTesting(context.tools);
|
|
256
|
-
// Progress tracking for batched events
|
|
257
|
-
const totalEstimate = toolsToTest.length * basicPatterns.length;
|
|
258
|
-
let completedTests = 0;
|
|
259
|
-
let lastBatchTime = Date.now();
|
|
260
|
-
const startTime = Date.now();
|
|
261
|
-
const BATCH_INTERVAL = 500;
|
|
262
|
-
const BATCH_SIZE = 10;
|
|
263
|
-
let batchCount = 0;
|
|
264
|
-
const emitProgressBatch = () => {
|
|
265
|
-
if (context.onProgress) {
|
|
266
|
-
context.onProgress({
|
|
267
|
-
type: "test_batch",
|
|
268
|
-
module: "security",
|
|
269
|
-
completed: completedTests,
|
|
270
|
-
total: totalEstimate,
|
|
271
|
-
batchSize: batchCount,
|
|
272
|
-
elapsed: Date.now() - startTime,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
batchCount = 0;
|
|
276
|
-
lastBatchTime = Date.now();
|
|
277
|
-
};
|
|
278
|
-
this.log(`Starting BASIC security assessment - testing ${toolsToTest.length} tools with ${basicPatterns.length} critical injection patterns (~${totalEstimate} tests)`);
|
|
279
|
-
for (const tool of toolsToTest) {
|
|
280
|
-
// Tools with no input parameters can't be exploited via payload injection
|
|
281
|
-
// Add passing results so they appear in the UI
|
|
282
|
-
if (!this.hasInputParameters(tool)) {
|
|
283
|
-
this.log(`${tool.name} has no input parameters - adding passing results`);
|
|
284
|
-
// Add a passing result for each basic pattern so tool appears in UI
|
|
285
|
-
for (const attackPattern of basicPatterns) {
|
|
286
|
-
const allPayloads = getPayloadsForAttack(attackPattern.attackName);
|
|
287
|
-
const payload = allPayloads[0];
|
|
288
|
-
if (payload) {
|
|
289
|
-
results.push({
|
|
290
|
-
testName: attackPattern.attackName,
|
|
291
|
-
description: payload.description,
|
|
292
|
-
payload: payload.payload,
|
|
293
|
-
riskLevel: payload.riskLevel,
|
|
294
|
-
toolName: tool.name,
|
|
295
|
-
vulnerable: false,
|
|
296
|
-
evidence: "Tool has no input parameters - cannot be exploited via payload injection",
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
this.log(`Testing ${tool.name} with ${basicPatterns.length} critical patterns`);
|
|
303
|
-
// Test with each critical pattern
|
|
304
|
-
for (const attackPattern of basicPatterns) {
|
|
305
|
-
// Get only the FIRST (most generic) payload for basic testing
|
|
306
|
-
const allPayloads = getPayloadsForAttack(attackPattern.attackName);
|
|
307
|
-
const payload = allPayloads[0]; // Just use first payload
|
|
308
|
-
if (!payload)
|
|
309
|
-
continue;
|
|
310
|
-
this.testCount++;
|
|
311
|
-
completedTests++;
|
|
312
|
-
batchCount++;
|
|
313
|
-
try {
|
|
314
|
-
const result = await this.testPayload(tool, attackPattern.attackName, payload, context.callTool);
|
|
315
|
-
results.push(result);
|
|
316
|
-
if (result.vulnerable) {
|
|
317
|
-
this.log(`🚨 VULNERABILITY: ${tool.name} - ${attackPattern.attackName}`);
|
|
318
|
-
// Emit real-time vulnerability_found event
|
|
319
|
-
if (context.onProgress) {
|
|
320
|
-
context.onProgress({
|
|
321
|
-
type: "vulnerability_found",
|
|
322
|
-
tool: tool.name,
|
|
323
|
-
pattern: attackPattern.attackName,
|
|
324
|
-
confidence: result.confidence || "medium",
|
|
325
|
-
evidence: result.evidence || "Vulnerability detected",
|
|
326
|
-
riskLevel: payload.riskLevel,
|
|
327
|
-
requiresReview: result.requiresManualReview || false,
|
|
328
|
-
payload: payload.payload,
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
this.logError(`Error testing ${tool.name} with ${attackPattern.attackName}`, error);
|
|
335
|
-
}
|
|
336
|
-
// Emit progress batch if threshold reached
|
|
337
|
-
const timeSinceLastBatch = Date.now() - lastBatchTime;
|
|
338
|
-
if (batchCount >= BATCH_SIZE || timeSinceLastBatch >= BATCH_INTERVAL) {
|
|
339
|
-
emitProgressBatch();
|
|
340
|
-
}
|
|
341
|
-
// Rate limiting
|
|
342
|
-
if (this.testCount % 5 === 0) {
|
|
343
|
-
await this.sleep(100);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
// Final flush of any remaining progress
|
|
348
|
-
if (batchCount > 0) {
|
|
349
|
-
emitProgressBatch();
|
|
350
|
-
}
|
|
351
|
-
this.log(`BASIC security assessment complete: ${results.length} tests executed, ${results.filter((r) => r.vulnerable).length} vulnerabilities found`);
|
|
352
|
-
return results;
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Test tool with a specific payload
|
|
356
|
-
*/
|
|
357
|
-
async testPayload(tool, attackName, payload, callTool) {
|
|
358
|
-
// Skip execution-based tests for API wrappers (they return data, not execute code)
|
|
359
|
-
if (this.isApiWrapper(tool) && this.isExecutionTest(attackName)) {
|
|
360
|
-
return {
|
|
361
|
-
testName: attackName,
|
|
362
|
-
description: payload.description,
|
|
363
|
-
payload: payload.payload,
|
|
364
|
-
riskLevel: payload.riskLevel,
|
|
365
|
-
toolName: tool.name,
|
|
366
|
-
vulnerable: false,
|
|
367
|
-
evidence: "API wrapper tool - skips execution tests (returns external data as text, does not execute it as code)",
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
try {
|
|
371
|
-
// Create parameters using payload
|
|
372
|
-
const params = this.createTestParameters(payload, tool);
|
|
373
|
-
if (Object.keys(params).length === 0) {
|
|
374
|
-
return {
|
|
375
|
-
testName: attackName,
|
|
376
|
-
description: payload.description,
|
|
377
|
-
payload: payload.payload,
|
|
378
|
-
riskLevel: payload.riskLevel,
|
|
379
|
-
toolName: tool.name,
|
|
380
|
-
vulnerable: false,
|
|
381
|
-
evidence: "No compatible parameters for testing",
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
// Execute tool call with configurable timeout (default 5000ms for fast payload testing)
|
|
385
|
-
const securityTimeout = this.config.securityTestTimeout ?? 5000;
|
|
386
|
-
const response = await this.executeWithTimeout(callTool(tool.name, params), securityTimeout);
|
|
387
|
-
// Check for connection errors FIRST (before vulnerability analysis)
|
|
388
|
-
if (this.isConnectionError(response)) {
|
|
389
|
-
return {
|
|
390
|
-
testName: attackName,
|
|
391
|
-
description: payload.description,
|
|
392
|
-
payload: payload.payload,
|
|
393
|
-
riskLevel: payload.riskLevel,
|
|
394
|
-
toolName: tool.name,
|
|
395
|
-
vulnerable: true, // Mark as failed (test could not complete)
|
|
396
|
-
evidence: `CONNECTION ERROR: Test could not complete due to server/network failure`,
|
|
397
|
-
response: this.extractResponseContent(response),
|
|
398
|
-
connectionError: true,
|
|
399
|
-
errorType: this.classifyError(response),
|
|
400
|
-
testReliability: "failed",
|
|
401
|
-
confidence: "high",
|
|
402
|
-
requiresManualReview: true,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
// Analyze with evidence-based detection
|
|
406
|
-
const { isVulnerable, evidence } = this.analyzeResponse(response, payload, tool);
|
|
407
|
-
// Calculate confidence and manual review requirements
|
|
408
|
-
const confidenceResult = this.calculateConfidence(tool, isVulnerable, evidence || "", this.extractResponseContent(response), payload);
|
|
409
|
-
return {
|
|
410
|
-
testName: attackName,
|
|
411
|
-
description: payload.description,
|
|
412
|
-
payload: payload.payload,
|
|
413
|
-
riskLevel: payload.riskLevel,
|
|
414
|
-
toolName: tool.name,
|
|
415
|
-
vulnerable: isVulnerable,
|
|
416
|
-
evidence,
|
|
417
|
-
response: this.extractResponseContent(response),
|
|
418
|
-
...confidenceResult,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
catch (error) {
|
|
422
|
-
// Check if error is a connection/server failure
|
|
423
|
-
if (this.isConnectionErrorFromException(error)) {
|
|
424
|
-
return {
|
|
425
|
-
testName: attackName,
|
|
426
|
-
description: payload.description,
|
|
427
|
-
payload: payload.payload,
|
|
428
|
-
riskLevel: payload.riskLevel,
|
|
429
|
-
toolName: tool.name,
|
|
430
|
-
vulnerable: false,
|
|
431
|
-
evidence: `CONNECTION ERROR: Test could not complete due to server/network failure`,
|
|
432
|
-
response: this.extractErrorMessage(error),
|
|
433
|
-
connectionError: true,
|
|
434
|
-
errorType: this.classifyErrorFromException(error),
|
|
435
|
-
testReliability: "failed",
|
|
436
|
-
confidence: "high",
|
|
437
|
-
requiresManualReview: true,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
return {
|
|
441
|
-
testName: attackName,
|
|
442
|
-
description: payload.description,
|
|
443
|
-
payload: payload.payload,
|
|
444
|
-
riskLevel: payload.riskLevel,
|
|
445
|
-
toolName: tool.name,
|
|
446
|
-
vulnerable: false,
|
|
447
|
-
evidence: `Tool rejected input: ${this.extractErrorMessage(error)}`,
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Check if response indicates connection/server failure
|
|
453
|
-
* Returns true if test couldn't complete due to infrastructure issues
|
|
454
|
-
*
|
|
455
|
-
* CRITICAL: Only match transport/infrastructure errors, NOT tool business logic
|
|
456
|
-
*/
|
|
457
|
-
isConnectionError(response) {
|
|
458
|
-
const text = this.extractResponseContent(response).toLowerCase();
|
|
459
|
-
// UNAMBIGUOUS patterns - only match infrastructure failures
|
|
460
|
-
const unambiguousPatterns = [
|
|
461
|
-
/MCP error -32001/i, // MCP transport errors
|
|
462
|
-
/MCP error -32603/i, // MCP internal error
|
|
463
|
-
/MCP error -32000/i, // MCP server error
|
|
464
|
-
/MCP error -32700/i, // MCP parse error
|
|
465
|
-
/socket hang up/i, // Network socket errors
|
|
466
|
-
/ECONNREFUSED/i, // Connection refused
|
|
467
|
-
/ETIMEDOUT/i, // Network timeout
|
|
468
|
-
/ERR_CONNECTION/i, // Connection errors
|
|
469
|
-
/fetch failed/i, // HTTP fetch failures
|
|
470
|
-
/connection reset/i, // Connection reset
|
|
471
|
-
/error POSTing to endpoint/i, // Transport layer POST errors
|
|
472
|
-
/error GETting.*endpoint/i, // Transport layer GET errors (requires 'endpoint' to avoid false positives)
|
|
473
|
-
/service unavailable/i, // HTTP 503 (server down)
|
|
474
|
-
/gateway timeout/i, // HTTP 504 (gateway timeout)
|
|
475
|
-
/unknown tool:/i, // MCP spec format: "Unknown tool: <name>"
|
|
476
|
-
/no such tool/i, // Alternative phrasing for missing tool
|
|
477
|
-
];
|
|
478
|
-
// Check unambiguous patterns first
|
|
479
|
-
if (unambiguousPatterns.some((pattern) => pattern.test(text))) {
|
|
480
|
-
return true;
|
|
481
|
-
}
|
|
482
|
-
// CONTEXTUAL patterns - only match if in MCP error context
|
|
483
|
-
// These words can appear in legitimate tool responses, so require MCP prefix
|
|
484
|
-
const mcpPrefix = /^mcp error -\d+:/i.test(text);
|
|
485
|
-
if (mcpPrefix) {
|
|
486
|
-
const contextualPatterns = [
|
|
487
|
-
/bad request/i, // HTTP 400 (only if in MCP error)
|
|
488
|
-
/unauthorized/i, // HTTP 401 (only if in MCP error)
|
|
489
|
-
/forbidden/i, // HTTP 403 (only if in MCP error)
|
|
490
|
-
/no valid session/i, // Session errors (only if in MCP error)
|
|
491
|
-
/session.*expired/i, // Session expiration (only if in MCP error)
|
|
492
|
-
/internal server error/i, // HTTP 500 (only if in MCP error)
|
|
493
|
-
/HTTP [45]\d\d/i, // HTTP status codes (only if in MCP error)
|
|
494
|
-
];
|
|
495
|
-
return contextualPatterns.some((pattern) => pattern.test(text));
|
|
496
|
-
}
|
|
497
|
-
return false;
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Check if caught exception indicates connection/server failure
|
|
501
|
-
* CRITICAL: Only match transport/infrastructure errors, NOT tool business logic
|
|
502
|
-
*/
|
|
503
|
-
isConnectionErrorFromException(error) {
|
|
504
|
-
if (error instanceof Error) {
|
|
505
|
-
const message = error.message.toLowerCase();
|
|
506
|
-
// UNAMBIGUOUS patterns - only match infrastructure failures
|
|
507
|
-
const unambiguousPatterns = [
|
|
508
|
-
/MCP error -32001/i, // MCP transport errors
|
|
509
|
-
/MCP error -32603/i, // MCP internal error
|
|
510
|
-
/MCP error -32000/i, // MCP server error
|
|
511
|
-
/MCP error -32700/i, // MCP parse error
|
|
512
|
-
/socket hang up/i, // Network socket errors
|
|
513
|
-
/ECONNREFUSED/i, // Connection refused
|
|
514
|
-
/ETIMEDOUT/i, // Network timeout
|
|
515
|
-
/network error/i, // Generic network errors
|
|
516
|
-
/ERR_CONNECTION/i, // Connection errors
|
|
517
|
-
/fetch failed/i, // HTTP fetch failures
|
|
518
|
-
/connection reset/i, // Connection reset
|
|
519
|
-
/error POSTing to endpoint/i, // Transport layer POST errors
|
|
520
|
-
/error GETting/i, // Transport layer GET errors
|
|
521
|
-
/service unavailable/i, // HTTP 503 (server down)
|
|
522
|
-
/gateway timeout/i, // HTTP 504 (gateway timeout)
|
|
523
|
-
/unknown tool:/i, // MCP spec format: "Unknown tool: <name>"
|
|
524
|
-
/no such tool/i, // Alternative phrasing for missing tool
|
|
525
|
-
];
|
|
526
|
-
// Check unambiguous patterns first
|
|
527
|
-
if (unambiguousPatterns.some((pattern) => pattern.test(message))) {
|
|
528
|
-
return true;
|
|
529
|
-
}
|
|
530
|
-
// CONTEXTUAL patterns - only match if in MCP error context
|
|
531
|
-
const mcpPrefix = /^mcp error -\d+:/i.test(message);
|
|
532
|
-
if (mcpPrefix) {
|
|
533
|
-
const contextualPatterns = [
|
|
534
|
-
/bad request/i,
|
|
535
|
-
/unauthorized/i,
|
|
536
|
-
/forbidden/i,
|
|
537
|
-
/no valid session/i,
|
|
538
|
-
/session.*expired/i,
|
|
539
|
-
/internal server error/i,
|
|
540
|
-
/HTTP [45]\d\d/i,
|
|
541
|
-
];
|
|
542
|
-
return contextualPatterns.some((pattern) => pattern.test(message));
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
return false;
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* Classify error type for reporting
|
|
549
|
-
*/
|
|
550
|
-
classifyError(response) {
|
|
551
|
-
const text = this.extractResponseContent(response).toLowerCase();
|
|
552
|
-
// Connection-level errors (network, transport)
|
|
553
|
-
if (/socket|ECONNREFUSED|ETIMEDOUT|network|fetch failed|connection reset/i.test(text)) {
|
|
554
|
-
return "connection";
|
|
555
|
-
}
|
|
556
|
-
// Server-level errors (backend issues)
|
|
557
|
-
if (/-32603|-32000|-32700|internal server error|service unavailable|gateway timeout|HTTP 5\d\d|error POSTing.*endpoint|error GETting.*endpoint|bad request|HTTP 400|unauthorized|forbidden|no valid session|session.*expired/i.test(text)) {
|
|
558
|
-
return "server";
|
|
559
|
-
}
|
|
560
|
-
// Protocol errors (MCP-specific)
|
|
561
|
-
if (/-32001/i.test(text)) {
|
|
562
|
-
return "protocol";
|
|
563
|
-
}
|
|
564
|
-
return "protocol";
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Classify error type from caught exception
|
|
568
|
-
*/
|
|
569
|
-
classifyErrorFromException(error) {
|
|
570
|
-
if (error instanceof Error) {
|
|
571
|
-
const message = error.message.toLowerCase();
|
|
572
|
-
// Connection-level errors (network, transport)
|
|
573
|
-
if (/socket|ECONNREFUSED|ETIMEDOUT|network|fetch failed|connection reset/i.test(message)) {
|
|
574
|
-
return "connection";
|
|
575
|
-
}
|
|
576
|
-
// Server-level errors (backend issues)
|
|
577
|
-
if (/-32603|-32000|-32700|internal server error|service unavailable|gateway timeout|HTTP 5\d\d|error POSTing|error GETting|bad request|HTTP 400|unauthorized|forbidden|no valid session|session.*expired/i.test(message)) {
|
|
578
|
-
return "server";
|
|
579
|
-
}
|
|
580
|
-
// Protocol errors (MCP-specific)
|
|
581
|
-
if (/-32001/i.test(message)) {
|
|
582
|
-
return "protocol";
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
return "protocol";
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Analyze response with evidence-based detection
|
|
589
|
-
* CRITICAL: Distinguish between safe reflection and actual execution
|
|
590
|
-
*/
|
|
591
|
-
analyzeResponse(response, payload, tool) {
|
|
592
|
-
const responseText = this.extractResponseContent(response).toLowerCase();
|
|
593
|
-
const payloadText = payload.payload.toLowerCase();
|
|
594
|
-
// ✅ STEP 1: Check for MCP validation errors (HIGHEST PRIORITY)
|
|
595
|
-
// Tools that reject invalid input before processing are SECURE
|
|
596
|
-
const errorInfo = this.extractErrorInfo(response);
|
|
597
|
-
if (this.isMCPValidationError(errorInfo, responseText)) {
|
|
598
|
-
const errorCode = errorInfo.code ? ` (${errorInfo.code})` : "";
|
|
599
|
-
return {
|
|
600
|
-
isVulnerable: false,
|
|
601
|
-
evidence: `MCP validation error${errorCode}: Tool properly rejected invalid input before processing`,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
// ✅ STEP 1.1: Check for HTTP error responses (Issue #26)
|
|
605
|
-
// HTTP 4xx/5xx errors indicate tool rejection, not vulnerability
|
|
606
|
-
// This prevents false positives like "404: Not Found" being flagged for Calculator Injection
|
|
607
|
-
if (this.isHttpErrorResponse(responseText)) {
|
|
608
|
-
return {
|
|
609
|
-
isVulnerable: false,
|
|
610
|
-
evidence: "HTTP error response indicates tool rejection (not vulnerability)",
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
// ✅ STEP 1.5: Classify tool and check for safe categories (prevents false positives)
|
|
614
|
-
// Check tool category before generic pattern matching to avoid false positives
|
|
615
|
-
const classifier = new ToolClassifier();
|
|
616
|
-
const classification = classifier.classify(tool.name, tool.description);
|
|
617
|
-
// Check if tool is in a safe category and response matches expected format
|
|
618
|
-
if (classification.categories.includes(ToolCategory.SEARCH_RETRIEVAL)) {
|
|
619
|
-
// Search tools should return search results, not execute code
|
|
620
|
-
if (this.isSearchResultResponse(responseText)) {
|
|
621
|
-
return {
|
|
622
|
-
isVulnerable: false,
|
|
623
|
-
evidence: "Search tool returned query results (not code execution)",
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
if (classification.categories.includes(ToolCategory.CRUD_CREATION)) {
|
|
628
|
-
// Creation tools should return creation confirmations, not execute code
|
|
629
|
-
if (this.isCreationResponse(responseText)) {
|
|
630
|
-
return {
|
|
631
|
-
isVulnerable: false,
|
|
632
|
-
evidence: "CRUD tool created/modified resource (not code execution)",
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
if (classification.categories.includes(ToolCategory.READ_ONLY_INFO)) {
|
|
637
|
-
// Info tools should return user/workspace data (intended exposure)
|
|
638
|
-
return {
|
|
639
|
-
isVulnerable: false,
|
|
640
|
-
evidence: "Read-only info tool returned user/workspace data (intended data exposure, not vulnerability)",
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
if (classification.categories.includes(ToolCategory.SAFE_STORAGE)) {
|
|
644
|
-
// Safe storage control tools should NEVER be flagged (validated safe implementations)
|
|
645
|
-
return {
|
|
646
|
-
isVulnerable: false,
|
|
647
|
-
evidence: "Safe storage control tool (validated safe implementation, control group for testing)",
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
// ✅ STEP 1: Check if response is just reflecting/echoing input (SAFE)
|
|
651
|
-
// This prevents false positives from tools that echo malicious input as data
|
|
652
|
-
// IMPORTANT: Check reflection BEFORE execution evidence (priority order matters)
|
|
653
|
-
// This handles escaping differences (e.g., single vs double backslashes)
|
|
654
|
-
const isJustReflection = this.isReflectionResponse(responseText);
|
|
655
|
-
if (isJustReflection) {
|
|
656
|
-
return {
|
|
657
|
-
isVulnerable: false,
|
|
658
|
-
evidence: "Tool safely reflected input without execution",
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
// Response doesn't match reflection patterns - continue checking
|
|
662
|
-
// Falls through - might be execution (with or without echo of input)
|
|
663
|
-
// ✅ STEP 1.7: Check for computed math results (Issue #14 fix)
|
|
664
|
-
// If payload is a math expression and response contains the computed result
|
|
665
|
-
// (but NOT the original expression), that's evidence of execution
|
|
666
|
-
if (this.isComputedMathResult(payload.payload, responseText)) {
|
|
667
|
-
return {
|
|
668
|
-
isVulnerable: true,
|
|
669
|
-
evidence: `Tool computed math expression result instead of storing/echoing it (payload: ${payload.payload})`,
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
// ✅ STEP 2: Check if tool explicitly rejected the input (SAFE)
|
|
673
|
-
// CRITICAL: Check this BEFORE evidence matching to prevent false positives
|
|
674
|
-
// Tools that reject invalid input are secure, regardless of error message content
|
|
675
|
-
if (this.isValidationRejection(response)) {
|
|
676
|
-
return {
|
|
677
|
-
isVulnerable: false,
|
|
678
|
-
evidence: "Tool correctly rejected invalid input with validation error",
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
// ✅ STEP 3: Check for expected evidence of execution
|
|
682
|
-
// Only flag as vulnerable if we find execution evidence AND it's not just reflection
|
|
683
|
-
if (payload.evidence && payload.evidence.test(responseText)) {
|
|
684
|
-
// Double-check: is the evidence pattern matching the input or the output?
|
|
685
|
-
const evidenceMatchesInput = payload.evidence.test(payloadText);
|
|
686
|
-
if (evidenceMatchesInput && responseText.includes(payloadText)) {
|
|
687
|
-
// Evidence matches input and response contains input - probably just echoing
|
|
688
|
-
return {
|
|
689
|
-
isVulnerable: false,
|
|
690
|
-
evidence: "Evidence pattern matched echoed input, not execution",
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
// NEW: For validation-type patterns, require execution evidence
|
|
694
|
-
// This prevents false positives from validation error messages
|
|
695
|
-
if (this.isValidationPattern(payload.evidence)) {
|
|
696
|
-
const hasExecutionEvidence = this.hasExecutionEvidence(responseText);
|
|
697
|
-
if (!hasExecutionEvidence) {
|
|
698
|
-
return {
|
|
699
|
-
isVulnerable: false,
|
|
700
|
-
evidence: "Pattern matched in error message but no execution evidence - tool properly validated input",
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
// Evidence doesn't match input OR response doesn't contain input - real execution
|
|
705
|
-
return {
|
|
706
|
-
isVulnerable: true,
|
|
707
|
-
evidence: `Tool executed malicious instruction: found evidence matching ${payload.evidence} - ${payload.description}`,
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
// ✅ STEP 4: Fall back to existing detection logic for comprehensive analysis
|
|
711
|
-
return this.analyzeInjectionResponse(response, payload.payload);
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* Check if tool explicitly rejected input with validation error (SAFE)
|
|
715
|
-
* Returns true if tool properly rejected invalid input (SECURE)
|
|
716
|
-
*
|
|
717
|
-
* Distinguishes between:
|
|
718
|
-
* - SAFE: {"valid": false, "errors": ["Input too long"]} (rejection)
|
|
719
|
-
* - VULNERABLE: {"result": "deleted 5 rows"} (execution)
|
|
720
|
-
*/
|
|
721
|
-
isValidationRejection(response) {
|
|
722
|
-
const responseText = this.extractResponseContent(response);
|
|
723
|
-
try {
|
|
724
|
-
const parsed = JSON.parse(responseText);
|
|
725
|
-
// Explicit rejection indicators (highest priority)
|
|
726
|
-
if (parsed.valid === false ||
|
|
727
|
-
parsed.error === true ||
|
|
728
|
-
parsed.error === "true" || // Handle string boolean
|
|
729
|
-
(parsed.error && parsed.error !== false) || // Any truthy error value
|
|
730
|
-
parsed.status === "rejected" ||
|
|
731
|
-
parsed.status === "invalid" ||
|
|
732
|
-
parsed.status === "failed") {
|
|
733
|
-
return true;
|
|
734
|
-
}
|
|
735
|
-
// Has explicit error details (errors array, error field)
|
|
736
|
-
if (parsed.errors &&
|
|
737
|
-
Array.isArray(parsed.errors) &&
|
|
738
|
-
parsed.errors.length > 0) {
|
|
739
|
-
return true;
|
|
740
|
-
}
|
|
741
|
-
if (parsed.error && typeof parsed.error === "string") {
|
|
742
|
-
return true;
|
|
743
|
-
}
|
|
744
|
-
// Result field indicates validation failure
|
|
745
|
-
if (typeof parsed.result === "string") {
|
|
746
|
-
const resultRejectionPatterns = [
|
|
747
|
-
/validation (failed|error)/i,
|
|
748
|
-
/rejected/i,
|
|
749
|
-
/not.*approved/i,
|
|
750
|
-
/not.*in.*list/i,
|
|
751
|
-
/invalid.*input/i,
|
|
752
|
-
/error:.*invalid/i,
|
|
753
|
-
];
|
|
754
|
-
if (resultRejectionPatterns.some((p) => p.test(parsed.result))) {
|
|
755
|
-
return true;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
catch {
|
|
760
|
-
// Not JSON, check text patterns
|
|
761
|
-
}
|
|
762
|
-
// Text-based rejection patterns (fallback for non-JSON responses)
|
|
763
|
-
const rejectionPatterns = [
|
|
764
|
-
/validation failed/i,
|
|
765
|
-
/rejected/i,
|
|
766
|
-
/not.*approved/i,
|
|
767
|
-
/not.*in.*list/i,
|
|
768
|
-
/invalid.*input/i,
|
|
769
|
-
/error:.*invalid/i,
|
|
770
|
-
];
|
|
771
|
-
return rejectionPatterns.some((pattern) => pattern.test(responseText));
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* Check if response is an MCP validation error (safe rejection)
|
|
775
|
-
* Returns true if tool rejected input before processing (SECURE)
|
|
776
|
-
*
|
|
777
|
-
* Validation errors indicate proper input sanitization and are NOT vulnerabilities.
|
|
778
|
-
* Examples:
|
|
779
|
-
* - MCP error -32602 (JSON-RPC Invalid params standard)
|
|
780
|
-
* - "parameter validation failed: invalid url"
|
|
781
|
-
* - "schema validation error: must be a string"
|
|
782
|
-
*/
|
|
783
|
-
isMCPValidationError(errorInfo, responseText) {
|
|
784
|
-
// Check for JSON-RPC -32602 (Invalid params)
|
|
785
|
-
// This is the standard error code for parameter validation failures
|
|
786
|
-
if (errorInfo.code === -32602 || errorInfo.code === "-32602") {
|
|
787
|
-
return true;
|
|
788
|
-
}
|
|
789
|
-
// Check for explicit validation rejection patterns
|
|
790
|
-
// These indicate the tool properly validated input BEFORE processing
|
|
791
|
-
const validationPatterns = [
|
|
792
|
-
/parameter validation failed/i,
|
|
793
|
-
/schema validation (error|failed)/i,
|
|
794
|
-
/invalid (url|email|format|parameter|input|data)/i,
|
|
795
|
-
/must be a valid/i,
|
|
796
|
-
/must have a valid/i,
|
|
797
|
-
/failed to validate/i,
|
|
798
|
-
/validation error/i,
|
|
799
|
-
/does not match (pattern|schema)/i,
|
|
800
|
-
/not a valid (url|email|number|string)/i,
|
|
801
|
-
/expected.*but (got|received)/i,
|
|
802
|
-
/type mismatch/i,
|
|
803
|
-
/\brequired\b.*\bmissing\b/i,
|
|
804
|
-
// Boundary validation patterns (empty strings, required fields)
|
|
805
|
-
/cannot.*be.*empty/i,
|
|
806
|
-
/must.*not.*be.*empty/i,
|
|
807
|
-
/empty.*not.*allowed/i,
|
|
808
|
-
/\brequired\b/i, // Generic required field validation
|
|
809
|
-
/missing.*required/i,
|
|
810
|
-
/field.*required/i,
|
|
811
|
-
];
|
|
812
|
-
return validationPatterns.some((pattern) => pattern.test(responseText));
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* Check if response is an HTTP error (Issue #26)
|
|
816
|
-
* HTTP 4xx/5xx error responses indicate tool rejection, not vulnerability.
|
|
817
|
-
*
|
|
818
|
-
* This prevents false positives like "404: Not Found" being flagged for
|
|
819
|
-
* Calculator Injection (the digit "4" in "404" was incorrectly matching
|
|
820
|
-
* as the computed result of "2+2").
|
|
821
|
-
*/
|
|
822
|
-
isHttpErrorResponse(responseText) {
|
|
823
|
-
// Pattern 1: HTTP status code followed by error description
|
|
824
|
-
// e.g., "404 Not Found", "500 Internal Server Error", "503 Service Unavailable"
|
|
825
|
-
const httpErrorPattern = /\b(4\d{2}|5\d{2})\b.*?(not found|error|bad request|unauthorized|forbidden|internal server|unavailable|timeout|service)/i;
|
|
826
|
-
// Pattern 2: Response starts with HTTP status code
|
|
827
|
-
// e.g., "404: Not Found", "500: Error"
|
|
828
|
-
const simpleHttpPattern = /^(4\d{2}|5\d{2})[\s:]/;
|
|
829
|
-
// Pattern 3: Short "not found" messages (common API error response)
|
|
830
|
-
// e.g., "Not Found", "Resource not found"
|
|
831
|
-
const notFoundPattern = /not found/i;
|
|
832
|
-
const isShortNotFound = notFoundPattern.test(responseText) && responseText.length < 100;
|
|
833
|
-
// Pattern 4: HTTP status in JSON error structure
|
|
834
|
-
// e.g., {"status": 404, "error": "Not Found"}
|
|
835
|
-
const jsonStatusPattern = /"status":\s*(4\d{2}|5\d{2})/;
|
|
836
|
-
return (httpErrorPattern.test(responseText) ||
|
|
837
|
-
simpleHttpPattern.test(responseText) ||
|
|
838
|
-
isShortNotFound ||
|
|
839
|
-
jsonStatusPattern.test(responseText));
|
|
840
|
-
}
|
|
841
|
-
/**
|
|
842
|
-
* Check if evidence pattern is ambiguous (can match both validation and execution)
|
|
843
|
-
* These patterns appear in BOTH:
|
|
844
|
-
* - Secure validation errors: "parameter validation failed: invalid type"
|
|
845
|
-
* - Vulnerable execution errors: "SQL executed: type error in result"
|
|
846
|
-
*/
|
|
847
|
-
isValidationPattern(evidencePattern) {
|
|
848
|
-
const patternStr = evidencePattern.toString().toLowerCase();
|
|
849
|
-
// Patterns that are ambiguous - match both validation AND execution errors
|
|
850
|
-
const ambiguousPatterns = [
|
|
851
|
-
"type.*error",
|
|
852
|
-
"invalid.*type",
|
|
853
|
-
"error",
|
|
854
|
-
"invalid",
|
|
855
|
-
"failed",
|
|
856
|
-
"negative.*not.*allowed",
|
|
857
|
-
"must.*be.*positive",
|
|
858
|
-
"invalid.*value",
|
|
859
|
-
"overflow",
|
|
860
|
-
"out.*of.*range",
|
|
861
|
-
];
|
|
862
|
-
return ambiguousPatterns.some((ambiguous) => patternStr.includes(ambiguous));
|
|
863
|
-
}
|
|
864
|
-
/**
|
|
865
|
-
* Check if response contains evidence of actual execution
|
|
866
|
-
* Returns true only if malicious input was actually PROCESSED/EXECUTED
|
|
867
|
-
*
|
|
868
|
-
* Distinguishes between:
|
|
869
|
-
* - SECURE: "Parameter validation failed: invalid type" (rejected BEFORE execution)
|
|
870
|
-
* - VULNERABLE: "SQL executed: syntax error" (error DURING execution)
|
|
871
|
-
*/
|
|
872
|
-
hasExecutionEvidence(responseText) {
|
|
873
|
-
const executionIndicators = [
|
|
874
|
-
// Explicit execution verbs
|
|
875
|
-
/\bexecuted\b/i,
|
|
876
|
-
/\bprocessed\b/i,
|
|
877
|
-
/\bran\b.*command/i,
|
|
878
|
-
/\bcompleted\b/i,
|
|
879
|
-
/\bcomputed\b/i,
|
|
880
|
-
/\bcalculated\b/i,
|
|
881
|
-
// System-level errors (only occur during execution)
|
|
882
|
-
/NullPointerException/i,
|
|
883
|
-
/SegmentationFault/i,
|
|
884
|
-
/StackOverflow/i,
|
|
885
|
-
/OutOfMemory/i,
|
|
886
|
-
/syntax error in executed/i,
|
|
887
|
-
/error while executing/i,
|
|
888
|
-
/failed during execution/i,
|
|
889
|
-
/error in query execution/i,
|
|
890
|
-
/runtime error/i,
|
|
891
|
-
// Side effects (prove execution occurred)
|
|
892
|
-
/deleted \d+ (rows|files|records)/i,
|
|
893
|
-
/(file|resource) (opened|accessed|modified|deleted)/i,
|
|
894
|
-
/query returned \d+ results/i,
|
|
895
|
-
/modified \d+ records/i,
|
|
896
|
-
/\d+ rows affected/i,
|
|
897
|
-
/command output:/i,
|
|
898
|
-
/execution result:/i,
|
|
899
|
-
];
|
|
900
|
-
return executionIndicators.some((pattern) => pattern.test(responseText));
|
|
901
|
-
}
|
|
902
|
-
/**
|
|
903
|
-
* Check if a math expression payload was computed (execution evidence)
|
|
904
|
-
* Returns true if the response contains the computed result but NOT the original expression.
|
|
905
|
-
*
|
|
906
|
-
* This is key evidence of execution:
|
|
907
|
-
* - VULNERABLE: payload "15*37+100" → response contains "655" (computed result)
|
|
908
|
-
* - SAFE: payload "15*37+100" → response contains "15*37+100" (echoed, not computed)
|
|
909
|
-
*
|
|
910
|
-
* Added for Issue #14: False positives on safe input reflection
|
|
911
|
-
*/
|
|
912
|
-
isComputedMathResult(payload, responseText) {
|
|
913
|
-
// Issue #26: Skip HTTP error responses - they indicate tool rejection, not computation
|
|
914
|
-
// HTTP 4xx/5xx status codes (e.g., "404: Not Found") should not trigger false positives
|
|
915
|
-
// The digit "4" in "404" was incorrectly matching as computed result of "2+2"
|
|
916
|
-
const httpErrorPattern = /\b(4\d{2}|5\d{2})\b.*?(not found|error|bad request|unauthorized|forbidden|internal server|unavailable|timeout)/i;
|
|
917
|
-
const simpleHttpPattern = /^(4\d{2}|5\d{2})[\s:]/; // Starts with HTTP status code
|
|
918
|
-
const notFoundPattern = /not found/i;
|
|
919
|
-
if (httpErrorPattern.test(responseText) ||
|
|
920
|
-
simpleHttpPattern.test(responseText) ||
|
|
921
|
-
(notFoundPattern.test(responseText) && responseText.length < 100)) {
|
|
922
|
-
return false; // HTTP error response, not a computed result
|
|
923
|
-
}
|
|
924
|
-
// Check if payload looks like a simple math expression
|
|
925
|
-
// Matches: "2+2", "15*37+100", "10/2", "5-3", etc.
|
|
926
|
-
const simpleMathPattern = /^\s*(\d+)\s*([+\-*/])\s*(\d+)(?:\s*([+\-*/])\s*(\d+))?\s*$/;
|
|
927
|
-
const match = payload.match(simpleMathPattern);
|
|
928
|
-
if (!match) {
|
|
929
|
-
return false; // Not a simple math expression
|
|
930
|
-
}
|
|
931
|
-
// Try to safely evaluate the expression
|
|
932
|
-
try {
|
|
933
|
-
// Parse numbers and operators manually (avoid eval)
|
|
934
|
-
const num1 = parseInt(match[1], 10);
|
|
935
|
-
const op1 = match[2];
|
|
936
|
-
const num2 = parseInt(match[3], 10);
|
|
937
|
-
const op2 = match[4];
|
|
938
|
-
const num3 = match[5] ? parseInt(match[5], 10) : undefined;
|
|
939
|
-
let result;
|
|
940
|
-
// Calculate first operation
|
|
941
|
-
switch (op1) {
|
|
942
|
-
case "+":
|
|
943
|
-
result = num1 + num2;
|
|
944
|
-
break;
|
|
945
|
-
case "-":
|
|
946
|
-
result = num1 - num2;
|
|
947
|
-
break;
|
|
948
|
-
case "*":
|
|
949
|
-
result = num1 * num2;
|
|
950
|
-
break;
|
|
951
|
-
case "/":
|
|
952
|
-
result = Math.floor(num1 / num2);
|
|
953
|
-
break;
|
|
954
|
-
default:
|
|
955
|
-
return false;
|
|
956
|
-
}
|
|
957
|
-
// Calculate second operation if present (left-to-right, no precedence)
|
|
958
|
-
if (op2 && num3 !== undefined) {
|
|
959
|
-
switch (op2) {
|
|
960
|
-
case "+":
|
|
961
|
-
result = result + num3;
|
|
962
|
-
break;
|
|
963
|
-
case "-":
|
|
964
|
-
result = result - num3;
|
|
965
|
-
break;
|
|
966
|
-
case "*":
|
|
967
|
-
result = result * num3;
|
|
968
|
-
break;
|
|
969
|
-
case "/":
|
|
970
|
-
result = Math.floor(result / num3);
|
|
971
|
-
break;
|
|
972
|
-
default:
|
|
973
|
-
return false;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
// Check if response contains the computed result
|
|
977
|
-
const resultStr = result.toString();
|
|
978
|
-
const hasComputedResult = responseText.includes(resultStr);
|
|
979
|
-
// Check if response also contains the original expression (reflection)
|
|
980
|
-
const normalizedPayload = payload.replace(/\s+/g, "");
|
|
981
|
-
const hasOriginalExpression = responseText.includes(payload) ||
|
|
982
|
-
responseText.includes(normalizedPayload);
|
|
983
|
-
// Vulnerable if: has computed result AND does NOT have original expression
|
|
984
|
-
// This means the tool executed the expression instead of just echoing it
|
|
985
|
-
return hasComputedResult && !hasOriginalExpression;
|
|
986
|
-
}
|
|
987
|
-
catch {
|
|
988
|
-
return false;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
128
|
/**
|
|
992
129
|
* Perform additional security checks
|
|
993
130
|
*/
|
|
@@ -1005,7 +142,7 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
1005
142
|
continue; // These tools are designed to return data, skip the check
|
|
1006
143
|
}
|
|
1007
144
|
if (/key|secret|credential|password|token|auth/.test(toolText) &&
|
|
1008
|
-
!this.hasInputParameters(tool)) {
|
|
145
|
+
!this.payloadGenerator.hasInputParameters(tool)) {
|
|
1009
146
|
vulnerabilities.push(`${tool.name} may expose sensitive data (security-related tool with no input validation)`);
|
|
1010
147
|
}
|
|
1011
148
|
}
|
|
@@ -1087,596 +224,4 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
1087
224
|
`Flagged ${lowConfidenceCount} uncertain detection${lowConfidenceCount !== 1 ? "s" : ""} across ${testCount} security tests. Manual verification needed to confirm if these are actual vulnerabilities or false positives.`);
|
|
1088
225
|
}
|
|
1089
226
|
}
|
|
1090
|
-
/**
|
|
1091
|
-
* Calculate confidence level and manual review requirements
|
|
1092
|
-
* Detects ambiguous patterns that need human verification
|
|
1093
|
-
*/
|
|
1094
|
-
calculateConfidence(tool, isVulnerable, evidence, responseText, payload) {
|
|
1095
|
-
const toolDescription = (tool.description || "").toLowerCase();
|
|
1096
|
-
const toolName = tool.name.toLowerCase();
|
|
1097
|
-
const responseLower = responseText.toLowerCase();
|
|
1098
|
-
const payloadLower = payload.payload.toLowerCase();
|
|
1099
|
-
// HIGH CONFIDENCE: Clear cases
|
|
1100
|
-
// 1. Not vulnerable with clear safety indicators
|
|
1101
|
-
if (!isVulnerable &&
|
|
1102
|
-
(evidence.includes("safely reflected") ||
|
|
1103
|
-
evidence.includes("API wrapper") ||
|
|
1104
|
-
evidence.includes("safe: true"))) {
|
|
1105
|
-
return {
|
|
1106
|
-
confidence: "high",
|
|
1107
|
-
requiresManualReview: false,
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
// 2. Vulnerable with unambiguous execution evidence
|
|
1111
|
-
if (isVulnerable &&
|
|
1112
|
-
evidence.includes("executed") &&
|
|
1113
|
-
!this.isStructuredDataTool(toolName, toolDescription)) {
|
|
1114
|
-
return {
|
|
1115
|
-
confidence: "high",
|
|
1116
|
-
requiresManualReview: false,
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
// LOW CONFIDENCE: Ambiguous pattern matches in structured data
|
|
1120
|
-
if (isVulnerable) {
|
|
1121
|
-
// Check if tool returns structured data (search, lookup, retrieval)
|
|
1122
|
-
const isDataTool = this.isStructuredDataTool(toolName, toolDescription);
|
|
1123
|
-
// Check if response contains structured data indicators
|
|
1124
|
-
const hasStructuredData = /title:|name:|description:|trust score:|id:|snippets:/i.test(responseText) ||
|
|
1125
|
-
/^\s*-\s+/m.test(responseText) || // Bullet points
|
|
1126
|
-
/"[^"]+"\s*:\s*"[^"]+"/g.test(responseText); // JSON-like structure
|
|
1127
|
-
// Check if evidence pattern appears in input query
|
|
1128
|
-
const patternInInput = payload.evidence?.test(payloadLower);
|
|
1129
|
-
// Check if response echoes the input
|
|
1130
|
-
const echosInput = responseLower.includes(payloadLower);
|
|
1131
|
-
if (isDataTool && (hasStructuredData || echosInput) && patternInInput) {
|
|
1132
|
-
return {
|
|
1133
|
-
confidence: "low",
|
|
1134
|
-
requiresManualReview: true,
|
|
1135
|
-
manualReviewReason: "Pattern matched in structured data response. Tool may be legitimately " +
|
|
1136
|
-
"returning data containing search terms rather than executing malicious code.",
|
|
1137
|
-
reviewGuidance: "Verify: 1) Does the tool actually execute/compute the input? " +
|
|
1138
|
-
"2) Or does it just return pre-existing data that happens to contain the pattern? " +
|
|
1139
|
-
`3) Check if '${payload.evidence}' appears in legitimate tool output vs. execution results.`,
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
// Arithmetic patterns in numeric data (scores, counts, IDs)
|
|
1143
|
-
if (payload.evidence &&
|
|
1144
|
-
/\b\d\b/.test(payload.evidence.toString()) &&
|
|
1145
|
-
/\b(score|count|trust|rating|id|version)\b/i.test(responseText)) {
|
|
1146
|
-
return {
|
|
1147
|
-
confidence: "low",
|
|
1148
|
-
requiresManualReview: true,
|
|
1149
|
-
manualReviewReason: "Numeric pattern found in response with numeric metadata (scores, counts, etc.). " +
|
|
1150
|
-
"May be coincidental data rather than arithmetic execution.",
|
|
1151
|
-
reviewGuidance: "Verify: 1) Did the tool actually compute an arithmetic result? " +
|
|
1152
|
-
"2) Or does the number appear in metadata like trust scores, version numbers, or counts? " +
|
|
1153
|
-
"3) Compare pattern location in response with tool's expected output format.",
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
// Role/admin patterns in tool that deals with admin-related content
|
|
1157
|
-
if (/admin|role|privilege|elevated/i.test(payload.payload) &&
|
|
1158
|
-
/\b(library|search|documentation|api|wrapper)\b/i.test(toolDescription)) {
|
|
1159
|
-
return {
|
|
1160
|
-
confidence: "low",
|
|
1161
|
-
requiresManualReview: true,
|
|
1162
|
-
manualReviewReason: "Admin-related keywords found in search/retrieval tool results. " +
|
|
1163
|
-
"Tool may be returning data about admin-related libraries/APIs rather than elevating privileges.",
|
|
1164
|
-
reviewGuidance: "Verify: 1) Did the tool actually change behavior or assume admin role? " +
|
|
1165
|
-
"2) Or did it return search results for admin-related content? " +
|
|
1166
|
-
"3) Test if tool behavior actually changed after this request.",
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
// MEDIUM CONFIDENCE: Execution evidence but some ambiguity
|
|
1171
|
-
if (isVulnerable && evidence.includes("executed")) {
|
|
1172
|
-
return {
|
|
1173
|
-
confidence: "medium",
|
|
1174
|
-
requiresManualReview: true,
|
|
1175
|
-
manualReviewReason: "Execution indicators found but context suggests possible ambiguity.",
|
|
1176
|
-
reviewGuidance: "Verify: 1) Review the full response to confirm actual code execution. " +
|
|
1177
|
-
"2) Check if tool's intended function involves execution. " +
|
|
1178
|
-
"3) Test with variations to confirm consistency.",
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
// Default: HIGH confidence for clear safe cases
|
|
1182
|
-
return {
|
|
1183
|
-
confidence: "high",
|
|
1184
|
-
requiresManualReview: false,
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
/**
|
|
1188
|
-
* Check if tool is a structured data tool (search, lookup, retrieval)
|
|
1189
|
-
* These tools naturally echo input patterns in their results
|
|
1190
|
-
*/
|
|
1191
|
-
isStructuredDataTool(toolName, toolDescription) {
|
|
1192
|
-
const dataToolPatterns = [
|
|
1193
|
-
/search/i,
|
|
1194
|
-
/find/i,
|
|
1195
|
-
/lookup/i,
|
|
1196
|
-
/query/i,
|
|
1197
|
-
/retrieve/i,
|
|
1198
|
-
/fetch/i,
|
|
1199
|
-
/get/i,
|
|
1200
|
-
/list/i,
|
|
1201
|
-
/resolve/i,
|
|
1202
|
-
/discover/i,
|
|
1203
|
-
/browse/i,
|
|
1204
|
-
];
|
|
1205
|
-
const combined = `${toolName} ${toolDescription}`;
|
|
1206
|
-
return dataToolPatterns.some((pattern) => pattern.test(combined));
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Check if response is just reflection (safe)
|
|
1210
|
-
* Expanded to catch more reflection patterns including echo, repeat, display
|
|
1211
|
-
* IMPROVED: Bidirectional patterns, safety indicators, and two-layer defense
|
|
1212
|
-
*
|
|
1213
|
-
* CRITICAL: This check distinguishes between:
|
|
1214
|
-
* - SAFE: Tool stores/echoes malicious input as data (reflection)
|
|
1215
|
-
* - VULNERABLE: Tool executes malicious input and returns results (execution)
|
|
1216
|
-
*
|
|
1217
|
-
* Two-layer defense:
|
|
1218
|
-
* Layer 1: Match reflection/status patterns
|
|
1219
|
-
* Layer 2: Verify NO execution evidence (defense-in-depth)
|
|
1220
|
-
*/
|
|
1221
|
-
isReflectionResponse(responseText) {
|
|
1222
|
-
// Status message patterns (NEW)
|
|
1223
|
-
const statusPatterns = [
|
|
1224
|
-
// Issue #27: Tool statistics/metrics messages (not command execution)
|
|
1225
|
-
// Prevents false positives from "1000 total in memory" style responses
|
|
1226
|
-
/\d+\s+total\s+(in\s+)?(memory|storage|items|results)/i,
|
|
1227
|
-
/\d+\s+(results|items|records),?\s+\d+\s+total/i,
|
|
1228
|
-
// "Action executed successfully: <anything>" (generic status message)
|
|
1229
|
-
/action\s+executed\s+successfully:/i,
|
|
1230
|
-
/command\s+executed\s+successfully:/i,
|
|
1231
|
-
// "Action executed successfully" (generic status - in JSON or standalone)
|
|
1232
|
-
/"result":\s*"action\s+executed\s+successfully"/i,
|
|
1233
|
-
/result.*action\s+executed\s+successfully/i,
|
|
1234
|
-
/successfully\s+(executed|completed|processed):/i,
|
|
1235
|
-
/successfully\s+(executed|completed|processed)"/i,
|
|
1236
|
-
// "Action received:" - safe echo/acknowledgment pattern (DVMCP testbed)
|
|
1237
|
-
/action\s+received:/i,
|
|
1238
|
-
/input\s+received:/i,
|
|
1239
|
-
/request\s+received:/i,
|
|
1240
|
-
// Explicit safety indicators in JSON responses (context-aware to avoid matching unrelated fields)
|
|
1241
|
-
// Require safety-related context: message, result, status, stored, reflected, etc.
|
|
1242
|
-
// Bounded quantifiers prevent ReDoS attacks from malicious server responses
|
|
1243
|
-
/"safe"\s*:\s*true[^}]{0,500}("message"|"result"|"status"|"response")/i,
|
|
1244
|
-
/("message"|"result"|"status"|"response")[^}]{0,500}"safe"\s*:\s*true/i,
|
|
1245
|
-
/"vulnerable"\s*:\s*false[^}]{0,500}("safe"|"stored"|"reflected"|"status")/i,
|
|
1246
|
-
/("safe"|"stored"|"reflected"|"status")[^}]{0,500}"vulnerable"\s*:\s*false/i,
|
|
1247
|
-
/"status"\s*:\s*"acknowledged"[^}]{0,500}("message"|"result"|"safe")/i,
|
|
1248
|
-
/("message"|"result"|"safe")[^}]{0,500}"status"\s*:\s*"acknowledged"/i,
|
|
1249
|
-
];
|
|
1250
|
-
const reflectionPatterns = [
|
|
1251
|
-
...statusPatterns,
|
|
1252
|
-
// Original patterns (keep all existing)
|
|
1253
|
-
/stored.*query/i,
|
|
1254
|
-
/saved.*input/i,
|
|
1255
|
-
/received.*parameter/i,
|
|
1256
|
-
/processing.*request/i,
|
|
1257
|
-
/storing.*data/i,
|
|
1258
|
-
/added.*to.*collection/i,
|
|
1259
|
-
/echo:/i,
|
|
1260
|
-
/echoing/i,
|
|
1261
|
-
/repeating/i,
|
|
1262
|
-
/displaying/i,
|
|
1263
|
-
/showing.*input/i,
|
|
1264
|
-
/message.*echoed/i,
|
|
1265
|
-
/safely.*as.*data/i,
|
|
1266
|
-
// NEW: Bidirectional patterns (catch "Query stored" and "stored query")
|
|
1267
|
-
/query.*stored/i,
|
|
1268
|
-
/stored.*query/i, // Bidirectional: "Stored query"
|
|
1269
|
-
/input.*saved/i,
|
|
1270
|
-
/parameter.*received/i,
|
|
1271
|
-
/command.*stored/i,
|
|
1272
|
-
/stored.*command/i, // Bidirectional: "Stored command"
|
|
1273
|
-
/data.*stored/i,
|
|
1274
|
-
/stored.*data/i, // Bidirectional: "Stored data"
|
|
1275
|
-
/action.*stored/i,
|
|
1276
|
-
/stored.*action/i, // Bidirectional: "Stored action"
|
|
1277
|
-
/text.*stored/i,
|
|
1278
|
-
/stored.*text/i, // Bidirectional: "Stored text"
|
|
1279
|
-
/setting.*stored/i,
|
|
1280
|
-
/stored.*setting/i, // Bidirectional: "Stored setting"
|
|
1281
|
-
/instruction.*stored/i,
|
|
1282
|
-
/stored.*instruction/i, // Bidirectional: "Stored instruction"
|
|
1283
|
-
/url.*stored/i,
|
|
1284
|
-
/stored.*url/i, // Bidirectional: "Stored URL"
|
|
1285
|
-
/package.*stored/i,
|
|
1286
|
-
/stored.*package/i, // Bidirectional: "Stored package"
|
|
1287
|
-
// NEW: Safety indicators (common in hardened implementations)
|
|
1288
|
-
/stored.*safely/i,
|
|
1289
|
-
/safely.*stored/i,
|
|
1290
|
-
/without\s+execut/i,
|
|
1291
|
-
/not\s+executed/i,
|
|
1292
|
-
/never\s+executed/i,
|
|
1293
|
-
/stored.*as.*data/i,
|
|
1294
|
-
/treated.*as.*data/i,
|
|
1295
|
-
/stored\s+in\s+(collection|database)/i,
|
|
1296
|
-
// NEW: Common safe storage responses
|
|
1297
|
-
/stored.*successfully/i,
|
|
1298
|
-
/saved.*to/i,
|
|
1299
|
-
/recorded\s+in/i,
|
|
1300
|
-
/added\s+to/i,
|
|
1301
|
-
// NEW: Storage/logging confirmations (high confidence)
|
|
1302
|
-
/logged successfully:/i,
|
|
1303
|
-
/queued for processing:/i,
|
|
1304
|
-
/saved (for|successfully)/i,
|
|
1305
|
-
/stored for (admin review|configuration|processing)/i,
|
|
1306
|
-
// NEW: Processing confirmations (high confidence)
|
|
1307
|
-
/processed successfully/i,
|
|
1308
|
-
/validated successfully/i,
|
|
1309
|
-
/parsed successfully/i,
|
|
1310
|
-
/(validation|processing) (passed|completed)/i,
|
|
1311
|
-
// NEW: Error messages with input reflection (common safe pattern)
|
|
1312
|
-
/error:.*not (found|in approved list|recognized)/i,
|
|
1313
|
-
/error getting info for ['"].*['"]/i,
|
|
1314
|
-
/invalid .* format.*stored as text/i,
|
|
1315
|
-
/error:.*too (long|short|large)/i,
|
|
1316
|
-
// NEW: DoS/Resource safe rejection patterns
|
|
1317
|
-
// These indicate the tool properly rejected resource-intensive input
|
|
1318
|
-
/payload.?rejected/i,
|
|
1319
|
-
/input.?exceeds.?limit/i,
|
|
1320
|
-
/resource.?limit.?enforced/i,
|
|
1321
|
-
/size.?limit/i,
|
|
1322
|
-
/maximum.?length/i,
|
|
1323
|
-
/rate.?limit/i,
|
|
1324
|
-
/request.?throttled/i,
|
|
1325
|
-
/input.?too.?large/i,
|
|
1326
|
-
/exceeds.?maximum.?size/i,
|
|
1327
|
-
/depth.?limit.?exceeded/i,
|
|
1328
|
-
/nesting.?limit/i,
|
|
1329
|
-
/complexity.?limit/i,
|
|
1330
|
-
// NEW: Insecure Deserialization safe rejection patterns
|
|
1331
|
-
// These indicate the tool properly rejected serialized data without deserializing
|
|
1332
|
-
/serialization.?not.?supported/i,
|
|
1333
|
-
/pickle.?disabled/i,
|
|
1334
|
-
/deserialization.?blocked/i,
|
|
1335
|
-
/unsafe.?format.?rejected/i,
|
|
1336
|
-
/binary.?data.?not.?accepted/i,
|
|
1337
|
-
/data.?stored.?safely/i,
|
|
1338
|
-
/without.?deserialization/i,
|
|
1339
|
-
/no.?pickle/i,
|
|
1340
|
-
/stored.?without.?deserializ/i,
|
|
1341
|
-
// NEW: Hash-based sanitization patterns (Issue #14 fix)
|
|
1342
|
-
// These indicate the tool replaced dangerous input with safe hash identifiers
|
|
1343
|
-
/\[ref-[a-f0-9]+\]/i, // Hash-based sanitization: [ref-a1b2c3d4]
|
|
1344
|
-
/stored.*\[ref-/i, // "Expression stored: [ref-...]"
|
|
1345
|
-
/\[sanitized\]/i, // [sanitized] placeholder
|
|
1346
|
-
/\[redacted\]/i, // [redacted] placeholder
|
|
1347
|
-
/\[filtered\]/i, // [filtered] placeholder
|
|
1348
|
-
/\[blocked\]/i, // [blocked] placeholder
|
|
1349
|
-
/expression.*stored:/i, // "Expression stored:" prefix
|
|
1350
|
-
/input.*sanitized/i, // "Input sanitized"
|
|
1351
|
-
/content.*replaced/i, // "Content replaced with hash"
|
|
1352
|
-
];
|
|
1353
|
-
// LAYER 1: Check for reflection/status patterns
|
|
1354
|
-
const hasReflection = reflectionPatterns.some((pattern) => pattern.test(responseText));
|
|
1355
|
-
if (hasReflection) {
|
|
1356
|
-
// LAYER 2: Defense-in-depth - verify NO execution evidence
|
|
1357
|
-
// For JSON responses, check execution artifacts only in result/output fields
|
|
1358
|
-
try {
|
|
1359
|
-
const parsed = JSON.parse(responseText);
|
|
1360
|
-
const resultText = String(parsed.result || "");
|
|
1361
|
-
const outputFields = [
|
|
1362
|
-
parsed.stdout,
|
|
1363
|
-
parsed.stderr,
|
|
1364
|
-
parsed.output,
|
|
1365
|
-
parsed.contents,
|
|
1366
|
-
parsed.execution_log,
|
|
1367
|
-
parsed.command_output,
|
|
1368
|
-
]
|
|
1369
|
-
.filter(Boolean)
|
|
1370
|
-
.join(" ");
|
|
1371
|
-
// Only check resultText for execution if it's NOT purely a status message
|
|
1372
|
-
// Status messages like "Action executed successfully: X" just echo the payload
|
|
1373
|
-
const resultIsStatusOnly = statusPatterns.some((pattern) => pattern.test(resultText));
|
|
1374
|
-
const hasExecutionInOutput = resultIsStatusOnly
|
|
1375
|
-
? this.detectExecutionArtifacts(outputFields) // Skip result, check only output fields
|
|
1376
|
-
: this.detectExecutionArtifacts(resultText) ||
|
|
1377
|
-
this.detectExecutionArtifacts(outputFields);
|
|
1378
|
-
if (hasExecutionInOutput) {
|
|
1379
|
-
return false; // Reflection + Execution in output = VULNERABLE
|
|
1380
|
-
}
|
|
1381
|
-
return true; // Reflection + clean output = SAFE
|
|
1382
|
-
}
|
|
1383
|
-
catch {
|
|
1384
|
-
// Not JSON, check entire response for execution
|
|
1385
|
-
const hasExecution = this.detectExecutionArtifacts(responseText);
|
|
1386
|
-
if (hasExecution) {
|
|
1387
|
-
return false; // Reflection + Execution = VULNERABLE
|
|
1388
|
-
}
|
|
1389
|
-
return true; // Reflection only = SAFE
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
// JSON Structural Analysis with execution verification
|
|
1393
|
-
try {
|
|
1394
|
-
const parsed = JSON.parse(responseText);
|
|
1395
|
-
// Check placeholder action with safe result
|
|
1396
|
-
if (parsed.action === "test" || parsed.action === "placeholder") {
|
|
1397
|
-
const resultText = String(parsed.result || "");
|
|
1398
|
-
if (!this.detectExecutionArtifacts(resultText)) {
|
|
1399
|
-
return true; // Placeholder action with clean result
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
// Check generic status without execution
|
|
1403
|
-
if (parsed.status && /(completed|success|ok|done)/.test(parsed.status)) {
|
|
1404
|
-
if (!this.detectExecutionArtifacts(responseText)) {
|
|
1405
|
-
return true; // Status indicator with no execution
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
catch {
|
|
1410
|
-
// Not JSON, continue with text-only analysis
|
|
1411
|
-
}
|
|
1412
|
-
return false;
|
|
1413
|
-
}
|
|
1414
|
-
/**
|
|
1415
|
-
* Detect execution artifacts in response
|
|
1416
|
-
* Returns true if response contains evidence of actual code execution
|
|
1417
|
-
*
|
|
1418
|
-
* HIGH confidence: System files, commands, directory listings
|
|
1419
|
-
* MEDIUM confidence: Contextual patterns (root alone, paths)
|
|
1420
|
-
*
|
|
1421
|
-
* IMPORTANT: Excludes patterns that appear within echoed injection payloads
|
|
1422
|
-
* (e.g., /etc/passwd within an XXE entity definition is NOT execution evidence)
|
|
1423
|
-
*/
|
|
1424
|
-
detectExecutionArtifacts(responseText) {
|
|
1425
|
-
// First, check if this response contains echoed injection payload patterns
|
|
1426
|
-
// If so, we need to be more careful about what we consider "execution evidence"
|
|
1427
|
-
const containsEchoedPayload = this.containsEchoedInjectionPayload(responseText);
|
|
1428
|
-
const executionIndicators = [
|
|
1429
|
-
// HIGH CONFIDENCE - System files (requires format)
|
|
1430
|
-
/[a-z]+:x:\d+:\d+:/i, // passwd: "root:x:0:0:"
|
|
1431
|
-
/uid=\d+\([^)]+\)\s+gid=\d+/i, // id: "uid=0(root) gid=0(root)"
|
|
1432
|
-
// HIGH CONFIDENCE - Directory listings (full format)
|
|
1433
|
-
/[d-][rwx-]{9}\s+\d+\s+[a-z]+/i, // ls -la: "drwxr-xr-x 2 root"
|
|
1434
|
-
/total\s+\d+\s*$/m, // ls total line
|
|
1435
|
-
// HIGH CONFIDENCE - Command execution fields
|
|
1436
|
-
/command_executed:\s*[^"\s]/i, // "command_executed: whoami"
|
|
1437
|
-
/stdout:\s*["']?[^"'\s]/i, // "stdout: root"
|
|
1438
|
-
/(execution|output)_log:/i, // Log fields
|
|
1439
|
-
// HIGH CONFIDENCE - Shell paths
|
|
1440
|
-
/\/bin\/(bash|sh|zsh|dash)/i, // Shell executables
|
|
1441
|
-
// MEDIUM CONFIDENCE - System identity (contextual only)
|
|
1442
|
-
/\b(root|administrator)\s*$/im, // "root" alone on line (whoami)
|
|
1443
|
-
/\/root\//i, // Path: "/root/"
|
|
1444
|
-
// MEDIUM CONFIDENCE - Process info
|
|
1445
|
-
/PID:\s*\d{3,}/i, // Process ID
|
|
1446
|
-
];
|
|
1447
|
-
// Patterns that indicate execution ONLY if NOT in an echoed payload context
|
|
1448
|
-
// These patterns can appear in injection payloads (XXE, SSRF, etc.)
|
|
1449
|
-
const contextSensitiveIndicators = [
|
|
1450
|
-
/\/etc\/passwd/i, // Sensitive file - appears in XXE payloads
|
|
1451
|
-
/\/etc\/shadow/i, // Sensitive file - appears in XXE payloads
|
|
1452
|
-
/file:\/\/\//i, // File protocol - appears in XXE/SSRF payloads
|
|
1453
|
-
];
|
|
1454
|
-
// Check high-confidence indicators first (always count as execution)
|
|
1455
|
-
if (executionIndicators.some((pattern) => pattern.test(responseText))) {
|
|
1456
|
-
return true;
|
|
1457
|
-
}
|
|
1458
|
-
// Check context-sensitive indicators only if NOT in echoed payload context
|
|
1459
|
-
if (!containsEchoedPayload) {
|
|
1460
|
-
if (contextSensitiveIndicators.some((pattern) => pattern.test(responseText))) {
|
|
1461
|
-
return true;
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
return false;
|
|
1465
|
-
}
|
|
1466
|
-
/**
|
|
1467
|
-
* Check if response contains echoed injection payload patterns
|
|
1468
|
-
* These indicate the tool is safely echoing/storing input rather than executing it
|
|
1469
|
-
*/
|
|
1470
|
-
containsEchoedInjectionPayload(responseText) {
|
|
1471
|
-
const echoedPayloadPatterns = [
|
|
1472
|
-
// XXE payload markers (echoed XML entity definitions)
|
|
1473
|
-
/<!DOCTYPE\s+\w+\s+\[/i, // DOCTYPE with internal subset
|
|
1474
|
-
/<!ENTITY\s+\w+\s+SYSTEM/i, // External entity definition
|
|
1475
|
-
/<!ENTITY\s+%\s*\w+/i, // Parameter entity
|
|
1476
|
-
// SSRF/URL payload markers (echoed in storage context)
|
|
1477
|
-
/stored.*http:\/\//i, // "Stored query: http://..."
|
|
1478
|
-
/saved.*http:\/\//i, // "Saved: http://..."
|
|
1479
|
-
// SQL injection payload markers (echoed)
|
|
1480
|
-
/stored.*union\s+select/i, // "Stored query: UNION SELECT..."
|
|
1481
|
-
/stored.*drop\s+table/i, // "Stored query: DROP TABLE..."
|
|
1482
|
-
// Common echo/storage patterns with payload content
|
|
1483
|
-
/stored\s+query:\s*[<'"]/i, // "Stored query: <xml..." or "Stored query: '..."
|
|
1484
|
-
/saved\s+data:\s*[<'"]/i, // "Saved data: <xml..."
|
|
1485
|
-
];
|
|
1486
|
-
return echoedPayloadPatterns.some((pattern) => pattern.test(responseText));
|
|
1487
|
-
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Analyze injection response (existing logic)
|
|
1490
|
-
* Note: payload parameter unused after refactoring to two-layer defense
|
|
1491
|
-
*/
|
|
1492
|
-
analyzeInjectionResponse(response, _payload) {
|
|
1493
|
-
const responseText = this.extractResponseContent(response);
|
|
1494
|
-
// Look for signs of execution vs reflection
|
|
1495
|
-
const executionPatterns = [
|
|
1496
|
-
/executed/i,
|
|
1497
|
-
/command.*ran/i,
|
|
1498
|
-
/result.*is/i,
|
|
1499
|
-
/output.*:/i,
|
|
1500
|
-
/returned.*value/i,
|
|
1501
|
-
];
|
|
1502
|
-
const hasExecution = executionPatterns.some((pattern) => pattern.test(responseText));
|
|
1503
|
-
// CRITICAL: Check reflection BEFORE flagging as vulnerable
|
|
1504
|
-
if (hasExecution && !this.isReflectionResponse(responseText)) {
|
|
1505
|
-
return {
|
|
1506
|
-
isVulnerable: true,
|
|
1507
|
-
evidence: "Tool executed instruction: found execution keywords",
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
return { isVulnerable: false };
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Extract response content
|
|
1514
|
-
*/
|
|
1515
|
-
extractResponseContent(response) {
|
|
1516
|
-
if (response.content && Array.isArray(response.content)) {
|
|
1517
|
-
return response.content
|
|
1518
|
-
.map((c) => (c.type === "text" ? c.text : ""))
|
|
1519
|
-
.join(" ");
|
|
1520
|
-
}
|
|
1521
|
-
return String(response.content || "");
|
|
1522
|
-
}
|
|
1523
|
-
/**
|
|
1524
|
-
* Check if tool has input parameters
|
|
1525
|
-
*/
|
|
1526
|
-
hasInputParameters(tool) {
|
|
1527
|
-
const schema = tool.inputSchema?.type === "object" ? tool.inputSchema : tool.inputSchema;
|
|
1528
|
-
return schema?.properties && Object.keys(schema.properties).length > 0;
|
|
1529
|
-
}
|
|
1530
|
-
createTestParameters(payload, tool) {
|
|
1531
|
-
// Extract tool schema
|
|
1532
|
-
const schema = tool.inputSchema?.type === "object" ? tool.inputSchema : tool.inputSchema;
|
|
1533
|
-
if (!schema?.properties) {
|
|
1534
|
-
return {};
|
|
1535
|
-
}
|
|
1536
|
-
const params = {};
|
|
1537
|
-
const targetParamTypes = payload.parameterTypes || [];
|
|
1538
|
-
let payloadInjected = false;
|
|
1539
|
-
// NEW: Check for language-specific code execution parameters first
|
|
1540
|
-
// This enables detection of vulnerabilities in tools expecting Python/JS/SQL code
|
|
1541
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1542
|
-
const propSchema = prop;
|
|
1543
|
-
if (propSchema.type !== "string")
|
|
1544
|
-
continue;
|
|
1545
|
-
const detectedLanguage = this.languageGenerator.detectLanguage(key, tool.name, tool.description);
|
|
1546
|
-
// If we detect a specific language (not generic), use language-appropriate payloads
|
|
1547
|
-
if (detectedLanguage !== "generic" && !payloadInjected) {
|
|
1548
|
-
const languagePayloads = this.languageGenerator.getPayloadsForLanguage(detectedLanguage);
|
|
1549
|
-
if (languagePayloads.length > 0) {
|
|
1550
|
-
// Select a payload that targets similar behavior as the current attack pattern
|
|
1551
|
-
// (e.g., if testing command injection, use a command-executing payload)
|
|
1552
|
-
const payloadLower = payload.payload.toLowerCase();
|
|
1553
|
-
const isCommandTest = payloadLower.includes("whoami") ||
|
|
1554
|
-
payloadLower.includes("passwd") ||
|
|
1555
|
-
payloadLower.includes("id");
|
|
1556
|
-
// Find matching language payload based on test intent
|
|
1557
|
-
let selectedPayload = languagePayloads[0]; // Default to first
|
|
1558
|
-
if (isCommandTest) {
|
|
1559
|
-
// Prefer command execution payloads
|
|
1560
|
-
const cmdPayload = languagePayloads.find((lp) => lp.payload.includes("whoami") ||
|
|
1561
|
-
lp.payload.includes("subprocess") ||
|
|
1562
|
-
lp.payload.includes("execSync"));
|
|
1563
|
-
if (cmdPayload)
|
|
1564
|
-
selectedPayload = cmdPayload;
|
|
1565
|
-
}
|
|
1566
|
-
params[key] = selectedPayload.payload;
|
|
1567
|
-
payloadInjected = true;
|
|
1568
|
-
break;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
// Fall back to parameterTypes matching if no language-specific payload was used
|
|
1573
|
-
if (!payloadInjected && targetParamTypes.length > 0) {
|
|
1574
|
-
// Payload is parameter-specific (e.g., URLs only for "url" params)
|
|
1575
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1576
|
-
const propSchema = prop;
|
|
1577
|
-
const paramNameLower = key.toLowerCase();
|
|
1578
|
-
// Check if parameter name matches expected types
|
|
1579
|
-
if (propSchema.type === "string" &&
|
|
1580
|
-
targetParamTypes.some((type) => paramNameLower.includes(type))) {
|
|
1581
|
-
params[key] = payload.payload;
|
|
1582
|
-
payloadInjected = true;
|
|
1583
|
-
break;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
// Fall back to generic payload - inject into first string parameter (original behavior)
|
|
1588
|
-
if (!payloadInjected) {
|
|
1589
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1590
|
-
const propSchema = prop;
|
|
1591
|
-
if (propSchema.type === "string" && !payloadInjected) {
|
|
1592
|
-
params[key] = payload.payload;
|
|
1593
|
-
payloadInjected = true;
|
|
1594
|
-
break;
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
// Fill required parameters with safe defaults
|
|
1599
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1600
|
-
const propSchema = prop;
|
|
1601
|
-
if (schema.required?.includes(key) && !(key in params)) {
|
|
1602
|
-
if (propSchema.type === "string") {
|
|
1603
|
-
params[key] = "test";
|
|
1604
|
-
}
|
|
1605
|
-
else if (propSchema.type === "number") {
|
|
1606
|
-
params[key] = 1;
|
|
1607
|
-
}
|
|
1608
|
-
else if (propSchema.type === "boolean") {
|
|
1609
|
-
params[key] = true;
|
|
1610
|
-
}
|
|
1611
|
-
else if (propSchema.type === "object") {
|
|
1612
|
-
params[key] = {};
|
|
1613
|
-
}
|
|
1614
|
-
else if (propSchema.type === "array") {
|
|
1615
|
-
params[key] = [];
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
return params;
|
|
1620
|
-
}
|
|
1621
|
-
/**
|
|
1622
|
-
* Check if tool is an API wrapper (safe data-passing tool)
|
|
1623
|
-
*/
|
|
1624
|
-
isApiWrapper(tool) {
|
|
1625
|
-
const classifier = new ToolClassifier();
|
|
1626
|
-
const classification = classifier.classify(tool.name, tool.description || "");
|
|
1627
|
-
return classification.categories.includes(ToolCategory.API_WRAPPER);
|
|
1628
|
-
}
|
|
1629
|
-
/**
|
|
1630
|
-
* Check if attack is an execution-based test
|
|
1631
|
-
* These tests assume the tool executes input as code, which doesn't apply to API wrappers
|
|
1632
|
-
*/
|
|
1633
|
-
isExecutionTest(attackName) {
|
|
1634
|
-
const executionTests = [
|
|
1635
|
-
"Command Injection",
|
|
1636
|
-
"SQL Injection",
|
|
1637
|
-
"Path Traversal",
|
|
1638
|
-
];
|
|
1639
|
-
return executionTests.includes(attackName);
|
|
1640
|
-
}
|
|
1641
|
-
/**
|
|
1642
|
-
* Check if response is returning search results
|
|
1643
|
-
* Search tools return query results as data, not execute them
|
|
1644
|
-
*/
|
|
1645
|
-
isSearchResultResponse(responseText) {
|
|
1646
|
-
const searchResultPatterns = [
|
|
1647
|
-
/"results"\s*:\s*\[/i, // JSON results array
|
|
1648
|
-
/"type"\s*:\s*"search"/i, // Type indicator
|
|
1649
|
-
/"object"\s*:\s*"list"/i, // Notion list format
|
|
1650
|
-
/\bhighlight\b/i, // Search highlighting
|
|
1651
|
-
/search\s+results/i,
|
|
1652
|
-
/found\s+\d+\s+(results?|pages?|items?)/i,
|
|
1653
|
-
/query\s+(returned|matched)/i,
|
|
1654
|
-
/\d+\s+(results?|matches?|hits?)\s+for/i,
|
|
1655
|
-
/"has_more"\s*:/i, // Pagination indicator
|
|
1656
|
-
/next_cursor/i, // Pagination cursor
|
|
1657
|
-
];
|
|
1658
|
-
return searchResultPatterns.some((pattern) => pattern.test(responseText));
|
|
1659
|
-
}
|
|
1660
|
-
/**
|
|
1661
|
-
* Check if response is from a creation/modification operation
|
|
1662
|
-
* CRUD tools create/modify resources, not execute code
|
|
1663
|
-
*/
|
|
1664
|
-
isCreationResponse(responseText) {
|
|
1665
|
-
const creationPatterns = [
|
|
1666
|
-
/successfully\s+created/i,
|
|
1667
|
-
/database\s+created/i,
|
|
1668
|
-
/page\s+created/i,
|
|
1669
|
-
/resource\s+created/i,
|
|
1670
|
-
/\bcreate\s+table\b/i, // SQL creation
|
|
1671
|
-
/\binsert\s+into\b/i, // SQL insertion
|
|
1672
|
-
/"id"\s*:\s*"[a-f0-9-]{36}"/i, // UUID response (created resource)
|
|
1673
|
-
/"object"\s*:\s*"(page|database)"/i, // Notion object types
|
|
1674
|
-
/collection:\/\//i, // Collection URI
|
|
1675
|
-
/successfully\s+(added|inserted|updated|modified)/i,
|
|
1676
|
-
/resource\s+id:\s*[a-f0-9-]/i,
|
|
1677
|
-
/"created_time"/i, // Timestamp from creation
|
|
1678
|
-
/"last_edited_time"/i, // Timestamp from modification
|
|
1679
|
-
];
|
|
1680
|
-
return creationPatterns.some((pattern) => pattern.test(responseText));
|
|
1681
|
-
}
|
|
1682
227
|
}
|