@bryan-thompson/inspector-assessment-client 1.25.4 → 1.25.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/assets/{OAuthCallback-DE62cdTZ.js → OAuthCallback-D6y8tFfF.js} +1 -1
  2. package/dist/assets/{OAuthDebugCallback-CWjFdCIE.js → OAuthDebugCallback-DHegnqTa.js} +1 -1
  3. package/dist/assets/{index-PCQVSwHa.js → index-Cu02Ah3g.js} +4 -4
  4. package/dist/assets/{index-Df9Sx1jt.css → index-cHhcEXbr.css} +4 -0
  5. package/dist/index.html +2 -2
  6. package/lib/lib/assessment/coreTypes.d.ts +65 -0
  7. package/lib/lib/assessment/coreTypes.d.ts.map +1 -1
  8. package/lib/lib/assessment/extendedTypes.d.ts +127 -0
  9. package/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
  10. package/lib/lib/assessment/resultTypes.d.ts +45 -0
  11. package/lib/lib/assessment/resultTypes.d.ts.map +1 -1
  12. package/lib/lib/moduleScoring.d.ts +2 -2
  13. package/lib/lib/moduleScoring.d.ts.map +1 -1
  14. package/lib/lib/moduleScoring.js +3 -2
  15. package/lib/services/assessment/AssessmentOrchestrator.d.ts +3 -7
  16. package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
  17. package/lib/services/assessment/AssessmentOrchestrator.js +13 -2
  18. package/lib/services/assessment/TestDataGenerator.d.ts +9 -1
  19. package/lib/services/assessment/TestDataGenerator.d.ts.map +1 -1
  20. package/lib/services/assessment/TestDataGenerator.js +32 -6
  21. package/lib/services/assessment/TestScenarioEngine.d.ts +9 -1
  22. package/lib/services/assessment/TestScenarioEngine.d.ts.map +1 -1
  23. package/lib/services/assessment/TestScenarioEngine.js +17 -14
  24. package/lib/services/assessment/config/annotationPatterns.d.ts +3 -1
  25. package/lib/services/assessment/config/annotationPatterns.d.ts.map +1 -1
  26. package/lib/services/assessment/config/annotationPatterns.js +5 -2
  27. package/lib/services/assessment/config/architecturePatterns.d.ts +101 -0
  28. package/lib/services/assessment/config/architecturePatterns.d.ts.map +1 -0
  29. package/lib/services/assessment/config/architecturePatterns.js +248 -0
  30. package/lib/services/assessment/config/performanceConfig.d.ts +122 -0
  31. package/lib/services/assessment/config/performanceConfig.d.ts.map +1 -0
  32. package/lib/services/assessment/config/performanceConfig.js +154 -0
  33. package/lib/services/assessment/config/sanitizationPatterns.d.ts +63 -0
  34. package/lib/services/assessment/config/sanitizationPatterns.d.ts.map +1 -0
  35. package/lib/services/assessment/config/sanitizationPatterns.js +223 -0
  36. package/lib/services/assessment/lib/claudeCodeBridge.d.ts +40 -3
  37. package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -1
  38. package/lib/services/assessment/lib/claudeCodeBridge.js +149 -8
  39. package/lib/services/assessment/lib/concurrencyLimit.d.ts +6 -2
  40. package/lib/services/assessment/lib/concurrencyLimit.d.ts.map +1 -1
  41. package/lib/services/assessment/lib/concurrencyLimit.js +13 -6
  42. package/lib/services/assessment/lib/errors.d.ts +90 -0
  43. package/lib/services/assessment/lib/errors.d.ts.map +1 -0
  44. package/lib/services/assessment/lib/errors.js +136 -0
  45. package/lib/services/assessment/lib/timeoutUtils.d.ts +69 -0
  46. package/lib/services/assessment/lib/timeoutUtils.d.ts.map +1 -0
  47. package/lib/services/assessment/lib/timeoutUtils.js +103 -0
  48. package/lib/services/assessment/modules/BaseAssessor.d.ts +43 -8
  49. package/lib/services/assessment/modules/BaseAssessor.d.ts.map +1 -1
  50. package/lib/services/assessment/modules/BaseAssessor.js +103 -34
  51. package/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts +38 -1
  52. package/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts.map +1 -1
  53. package/lib/services/assessment/modules/DeveloperExperienceAssessor.js +185 -19
  54. package/lib/services/assessment/modules/DocumentationAssessor.d.ts +5 -0
  55. package/lib/services/assessment/modules/DocumentationAssessor.d.ts.map +1 -1
  56. package/lib/services/assessment/modules/DocumentationAssessor.js +11 -0
  57. package/lib/services/assessment/modules/ErrorHandlingAssessor.js +1 -1
  58. package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -1
  59. package/lib/services/assessment/modules/FunctionalityAssessor.js +6 -3
  60. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +3 -0
  61. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
  62. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +14 -2
  63. package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
  64. package/lib/services/assessment/modules/ManifestValidationAssessor.js +7 -2
  65. package/lib/services/assessment/modules/PromptAssessor.d.ts +1 -0
  66. package/lib/services/assessment/modules/PromptAssessor.d.ts.map +1 -1
  67. package/lib/services/assessment/modules/PromptAssessor.js +26 -16
  68. package/lib/services/assessment/modules/ProtocolComplianceAssessor.d.ts.map +1 -1
  69. package/lib/services/assessment/modules/ProtocolComplianceAssessor.js +6 -2
  70. package/lib/services/assessment/modules/ProtocolConformanceAssessor.d.ts +5 -0
  71. package/lib/services/assessment/modules/ProtocolConformanceAssessor.d.ts.map +1 -1
  72. package/lib/services/assessment/modules/ProtocolConformanceAssessor.js +15 -0
  73. package/lib/services/assessment/modules/ResourceAssessor.d.ts.map +1 -1
  74. package/lib/services/assessment/modules/ResourceAssessor.js +8 -2
  75. package/lib/services/assessment/modules/SecurityAssessor.d.ts +3 -171
  76. package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
  77. package/lib/services/assessment/modules/SecurityAssessor.js +25 -1480
  78. package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts +27 -28
  79. package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts.map +1 -1
  80. package/lib/services/assessment/modules/ToolAnnotationAssessor.js +340 -863
  81. package/lib/services/assessment/modules/UsabilityAssessor.d.ts +5 -0
  82. package/lib/services/assessment/modules/UsabilityAssessor.d.ts.map +1 -1
  83. package/lib/services/assessment/modules/UsabilityAssessor.js +11 -0
  84. package/lib/services/assessment/modules/annotations/AnnotationDeceptionDetector.d.ts +57 -0
  85. package/lib/services/assessment/modules/annotations/AnnotationDeceptionDetector.d.ts.map +1 -0
  86. package/lib/services/assessment/modules/annotations/AnnotationDeceptionDetector.js +176 -0
  87. package/lib/services/assessment/modules/annotations/ArchitectureDetector.d.ts +67 -0
  88. package/lib/services/assessment/modules/annotations/ArchitectureDetector.d.ts.map +1 -0
  89. package/lib/services/assessment/modules/annotations/ArchitectureDetector.js +239 -0
  90. package/lib/services/assessment/modules/annotations/BehaviorInference.d.ts +46 -0
  91. package/lib/services/assessment/modules/annotations/BehaviorInference.d.ts.map +1 -0
  92. package/lib/services/assessment/modules/annotations/BehaviorInference.js +394 -0
  93. package/lib/services/assessment/modules/annotations/DescriptionAnalyzer.d.ts +64 -0
  94. package/lib/services/assessment/modules/annotations/DescriptionAnalyzer.d.ts.map +1 -0
  95. package/lib/services/assessment/modules/annotations/DescriptionAnalyzer.js +304 -0
  96. package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.d.ts +43 -0
  97. package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.d.ts.map +1 -0
  98. package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.js +276 -0
  99. package/lib/services/assessment/modules/annotations/SchemaAnalyzer.d.ts +122 -0
  100. package/lib/services/assessment/modules/annotations/SchemaAnalyzer.d.ts.map +1 -0
  101. package/lib/services/assessment/modules/annotations/SchemaAnalyzer.js +388 -0
  102. package/lib/services/assessment/modules/annotations/index.d.ts +13 -0
  103. package/lib/services/assessment/modules/annotations/index.d.ts.map +1 -0
  104. package/lib/services/assessment/modules/annotations/index.js +15 -0
  105. package/lib/services/assessment/modules/index.d.ts +10 -0
  106. package/lib/services/assessment/modules/index.d.ts.map +1 -1
  107. package/lib/services/assessment/modules/index.js +13 -0
  108. package/lib/services/assessment/modules/securityTests/SanitizationDetector.d.ts +125 -0
  109. package/lib/services/assessment/modules/securityTests/SanitizationDetector.d.ts.map +1 -0
  110. package/lib/services/assessment/modules/securityTests/SanitizationDetector.js +345 -0
  111. package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.d.ts +33 -0
  112. package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.d.ts.map +1 -0
  113. package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.js +128 -0
  114. package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts +67 -0
  115. package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -0
  116. package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +372 -0
  117. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +178 -0
  118. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -0
  119. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +1207 -0
  120. package/lib/services/assessment/modules/securityTests/index.d.ts +8 -0
  121. package/lib/services/assessment/modules/securityTests/index.d.ts.map +1 -0
  122. package/lib/services/assessment/modules/securityTests/index.js +7 -0
  123. package/lib/services/assessment/tool-classifier-patterns.d.ts +1 -0
  124. package/lib/services/assessment/tool-classifier-patterns.d.ts.map +1 -1
  125. package/lib/services/assessment/tool-classifier-patterns.js +17 -0
  126. 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 { getAllAttackPatterns, getPayloadsForAttack, } from "../../../lib/securityPatterns.js";
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
- languageGenerator = new LanguageAwarePayloadGenerator();
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 - test selected tools with ALL attack types
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
  }