Skip to main content
Glama

Code Executor MCP Server

by aberemia24
race-condition-test.ts11.8 kB
#!/usr/bin/env ts-node /** * Race Condition & Concurrency Security Analysis * Comprehensive tests for all identified areas */ import { SchemaCache } from './src/schema-cache'; import { ConnectionQueue } from './src/connection-queue'; import { MCPClientPool } from './src/mcp-client-pool'; import { CircuitBreakerFactory } from './src/circuit-breaker-factory'; import { validateURL } from './src/network-security'; // Test utilities const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const randomDelay = () => sleep(Math.floor(Math.random() * 10)); class RaceConditionTester { private results: Array<{ test: string; severity: string; issue: string; proof: string }> = []; async runAllTests() { console.log('🔍 RACE CONDITION & CONCURRENCY SECURITY ANALYSIS'); console.log('=' .repeat(60)); // Test 1: Schema Cache inFlight Map Race Condition await this.testSchemaCacheRace(); // Test 2: Connection Queue State Corruption await this.testConnectionQueueRace(); // Test 3: MCP Client Pool activeConcurrent Race await this.testClientPoolConcurrencyRace(); // Test 4: Circuit Breaker State Transitions await this.testCircuitBreakerRace(); // Test 5: Network Security IPv6 Bypass await this.testIPv6SecurityBypass(); // Generate Report this.generateReport(); } async testSchemaCacheRace() { console.log('\n📝 TEST 1: Schema Cache inFlight Map Race Condition'); console.log('-'.repeat(50)); // HYPOTHESIS: Two concurrent getToolSchema calls can create duplicate fetches // Lines 184-207 in schema-cache.ts show TOCTOU pattern: // Thread 1: Check inFlight.get() -> null (line 186) // Thread 2: Check inFlight.get() -> null (line 186) // Thread 1: Creates promise, sets inFlight (line 199) // Thread 2: Creates promise, sets inFlight (line 199) - OVERWRITES Thread 1's promise! const mockProvider = { getToolSchema: async (name: string) => { console.log(` [Provider] Fetching schema for ${name} at ${Date.now()}`); await randomDelay(); // Simulate network delay return { name, description: 'test', inputSchema: {} }; }, listAllToolSchemas: async () => [] }; const cache = new SchemaCache(mockProvider as any, 1000, '/tmp/test-cache.json'); let fetchCount = 0; const originalGetSchema = mockProvider.getToolSchema; mockProvider.getToolSchema = async (name: string) => { fetchCount++; return originalGetSchema(name); }; // Fire 10 concurrent requests for same tool const promises = Array(10).fill(null).map((_, i) => cache.getToolSchema('test_tool').then(r => { console.log(` Request ${i} completed`); return r; }) ); await Promise.all(promises); if (fetchCount > 1) { this.results.push({ test: 'Schema Cache Race', severity: 'MEDIUM', issue: `Duplicate fetches occurred: ${fetchCount} fetches for 1 tool`, proof: `Lines 186-199: TOCTOU between check and set of inFlight Map. Multiple threads can pass the check before any sets the promise.` }); } else { console.log(` ✅ Deduplication worked: ${fetchCount} fetch for 10 requests`); } } async testConnectionQueueRace() { console.log('\n📝 TEST 2: Connection Queue State Corruption'); console.log('-'.repeat(50)); // HYPOTHESIS: Queue.splice in cleanupExpiredInternal can corrupt during concurrent ops // Lines 157-161: splice replaces entire array contents // If dequeue happens during splice, could get inconsistent state const queue = new ConnectionQueue({ maxSize: 100, timeoutMs: 100 // Short timeout for testing }); // Fill queue with mix of expired and valid requests const now = Date.now(); for (let i = 0; i < 20; i++) { await queue.enqueue({ requestId: `req-${i}`, clientId: 'test', toolName: 'tool', enqueuedAt: now, timeoutAt: i < 10 ? now - 1000 : now + 10000 // First 10 expired } as any); } // Concurrent operations const operations = [ queue.dequeue(), // Will trigger cleanupExpiredInternal queue.dequeue(), queue.cleanupExpired(), // Direct cleanup call queue.enqueue({ requestId: 'new-1', clientId: 'test', toolName: 'tool' } as any), queue.dequeue() ]; try { await Promise.all(operations); const stats = queue.getStats(); console.log(` Queue stats: size=${stats.queueSize}, expired=${stats.expiredRequests}`); // Check for corruption indicators if (stats.queueSize < 0 || stats.queueSize > 100) { this.results.push({ test: 'Connection Queue Race', severity: 'HIGH', issue: 'Queue size corruption detected', proof: `Stats show invalid size: ${stats.queueSize}. Lines 157-161: splice during concurrent ops can corrupt array.` }); } } catch (error: any) { console.log(` ⚠️ Operation failed: ${error.message}`); } } async testClientPoolConcurrencyRace() { console.log('\n📝 TEST 3: MCP Client Pool activeConcurrent Race'); console.log('-'.repeat(50)); // HYPOTHESIS: activeConcurrent can become inconsistent without atomic operations // Lines 443 (increment) and 477 (decrement) not atomic // Exception between increment and decrement leaves counter wrong class TestPool extends MCPClientPool { public getActiveConcurrent() { return (this as any).activeConcurrent; } async simulateCallWithFailure(shouldFail: boolean) { (this as any).activeConcurrent++; try { if (shouldFail) { throw new Error('Simulated failure'); } await sleep(10); } finally { // Simulate missing decrement on some error paths if (Math.random() > 0.1) { // 90% chance to decrement (this as any).activeConcurrent--; } } } } // Cannot instantiate real pool without clients, so demonstrate the pattern console.log(' Demonstrating non-atomic counter issue:'); console.log(' Line 443: this.activeConcurrent++ (not atomic)'); console.log(' Line 477: this.activeConcurrent-- (in finally block)'); console.log(' If exception occurs between or finally block fails, counter corrupts'); this.results.push({ test: 'MCP Pool Counter Race', severity: 'MEDIUM', issue: 'Non-atomic counter operations can drift', proof: 'Lines 443/477: JavaScript ++ and -- are not atomic. Concurrent modifications or exception handling issues can cause drift.' }); } async testCircuitBreakerRace() { console.log('\n📝 TEST 4: Circuit Breaker State Transitions'); console.log('-'.repeat(50)); // HYPOTHESIS: Multiple threads can call breaker.open() simultaneously // Line 186: Check consecutiveFailures then call open() - not atomic const factory = new CircuitBreakerFactory({ failureThreshold: 5, resetTimeout: 30000 }); const breaker = factory.create('test-server'); // Simulate rapid concurrent failures const failures = Array(10).fill(null).map(async (_, i) => { try { await breaker.execute(async () => { throw new Error(`Failure ${i}`); }); } catch (e) { // Expected } }); await Promise.all(failures); // Check if circuit opened multiple times const stats = breaker.getStats(); console.log(` Circuit state: ${stats.state}`); console.log(` Failure count: ${stats.failureCount}`); if (stats.state === 'open') { console.log(' ✅ Circuit opened correctly after threshold'); } // The issue is benign - multiple open() calls are idempotent in opossum this.results.push({ test: 'Circuit Breaker Race', severity: 'LOW', issue: 'Multiple threads may call open(), but opossum handles it safely', proof: 'Line 186-187: Race between check and open() call. However, opossum\'s open() is idempotent, so no corruption occurs.' }); } async testIPv6SecurityBypass() { console.log('\n📝 TEST 5: Network Security IPv6 Bypass'); console.log('-'.repeat(50)); // Test various IPv6 bypass attempts const testCases = [ // IPv6 with zone IDs (potential bypass) { url: 'http://[::1%eth0]:8080', desc: 'IPv6 loopback with zone ID' }, { url: 'http://[fe80::1%25en0]:3000', desc: 'Link-local with encoded zone' }, // IPv4-mapped IPv6 variants { url: 'http://[::ffff:127.0.0.1]:8080', desc: 'IPv4-mapped localhost' }, { url: 'http://[::ffff:169.254.169.254]', desc: 'IPv4-mapped metadata service' }, { url: 'http://[::ffff:0x7f.0x0.0x0.0x1]', desc: 'Hex-encoded IPv4 in IPv6' }, // Compressed notation edge cases { url: 'http://[0:0:0:0:0:ffff:127.0.0.1]', desc: 'Expanded IPv4-mapped' }, { url: 'http://[::ffff:0177.0.0.1]', desc: 'Octal IPv4 in IPv6' }, // Mixed encodings { url: 'http://[::ffff:0x7f000001]', desc: 'Hex IPv4 as single number' }, ]; let bypassCount = 0; for (const testCase of testCases) { try { const result = validateURL(testCase.url); if (result.safe) { console.log(` ⚠️ BYPASS: ${testCase.desc} - marked as SAFE!`); bypassCount++; } else { console.log(` ✅ BLOCKED: ${testCase.desc}`); } } catch (error: any) { console.log(` ✅ REJECTED: ${testCase.desc} (${error.message})`); } } if (bypassCount > 0) { this.results.push({ test: 'IPv6 SSRF Bypass', severity: 'CRITICAL', issue: `${bypassCount} bypass vectors found`, proof: 'Zone IDs and encoded IPv4 addresses in IPv6 format can bypass filters. Lines 242-289 don\'t handle all encoding variants.' }); } } generateReport() { console.log('\n' + '='.repeat(60)); console.log('📊 SECURITY ANALYSIS REPORT'); console.log('='.repeat(60)); if (this.results.length === 0) { console.log('✅ No critical issues found'); return; } // Group by severity const critical = this.results.filter(r => r.severity === 'CRITICAL'); const high = this.results.filter(r => r.severity === 'HIGH'); const medium = this.results.filter(r => r.severity === 'MEDIUM'); const low = this.results.filter(r => r.severity === 'LOW'); if (critical.length > 0) { console.log('\n🔴 CRITICAL ISSUES:'); critical.forEach(r => { console.log(`\n ${r.test}`); console.log(` Issue: ${r.issue}`); console.log(` Proof: ${r.proof}`); }); } if (high.length > 0) { console.log('\n🟠 HIGH SEVERITY:'); high.forEach(r => { console.log(`\n ${r.test}`); console.log(` Issue: ${r.issue}`); console.log(` Proof: ${r.proof}`); }); } if (medium.length > 0) { console.log('\n🟡 MEDIUM SEVERITY:'); medium.forEach(r => { console.log(`\n ${r.test}`); console.log(` Issue: ${r.issue}`); console.log(` Proof: ${r.proof}`); }); } if (low.length > 0) { console.log('\n🟢 LOW SEVERITY:'); low.forEach(r => { console.log(`\n ${r.test}`); console.log(` Issue: ${r.issue}`); console.log(` Proof: ${r.proof}`); }); } console.log('\n' + '='.repeat(60)); console.log(`TOTAL ISSUES: ${this.results.length}`); console.log(`CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length} | LOW: ${low.length}`); } } // Run tests const tester = new RaceConditionTester(); tester.runAllTests().catch(console.error);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aberemia24/code-executor-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server