Skip to main content
Glama
mcp-protocol-security.test.ts12.9 kB
/** * MCP Protocol Security Tests * Tests for CVE-2024-SMCP-005 fixes */ import { MCPSecurityValidator } from '../../src/server/mcp-security'; import { WebSocketManager } from '../../src/server/websocket-manager'; import { Socket } from 'socket.io'; import { logger } from '../../src/utils/logger'; // Mock dependencies jest.mock('../../src/utils/logger'); jest.mock('../../src/database/redis'); describe('MCP Protocol Security', () => { let validator: MCPSecurityValidator; beforeEach(() => { validator = new MCPSecurityValidator(); jest.clearAllMocks(); }); describe('Message Validation', () => { it('should accept valid JSON-RPC request', async () => { const message = JSON.stringify({ jsonrpc: '2.0', method: 'initialize', params: { protocolVersion: '1.0.0', clientInfo: { name: 'test-client', version: '1.0.0', }, }, id: 1, }); const result = await validator.validateMessage(message); expect(result.valid).toBe(true); expect(result.sanitized).toBeDefined(); }); it('should reject messages exceeding size limit', async () => { const largeMessage = 'x'.repeat(1048577); // 1MB + 1 byte const result = await validator.validateMessage(largeMessage); expect(result.valid).toBe(false); expect(result.error).toBe('Message too large'); }); it('should reject invalid JSON', async () => { const result = await validator.validateMessage('not json'); expect(result.valid).toBe(false); expect(result.error).toBe('Invalid JSON'); }); it('should reject non-2.0 JSON-RPC version', async () => { const message = JSON.stringify({ jsonrpc: '1.0', method: 'test', id: 1, }); const result = await validator.validateMessage(message); expect(result.valid).toBe(false); }); it('should detect and reject duplicate requests', async () => { const message = JSON.stringify({ jsonrpc: '2.0', method: 'ping', id: 1, }); const result1 = await validator.validateMessage(message); expect(result1.valid).toBe(true); const result2 = await validator.validateMessage(message); expect(result2.valid).toBe(false); expect(result2.error).toBe('Duplicate request'); }); }); describe('Command Validation', () => { it('should allow whitelisted commands', () => { const result = validator.validateCommand('initialize', {}); expect(result.valid).toBe(true); }); it('should reject non-whitelisted commands', () => { const result = validator.validateCommand('execute_arbitrary_code', {}); expect(result.valid).toBe(false); expect(result.error).toContain('Command not allowed'); }); it('should reject dangerous properties in arguments', () => { const result = validator.validateCommand('tools/call', { __proto__: 'dangerous', }); expect(result.valid).toBe(false); expect(result.error).toBe('Dangerous property detected'); }); it('should reject deeply nested objects', () => { const nested: any = {}; let current = nested; for (let i = 0; i < 15; i++) { current.child = {}; current = current.child; } const result = validator.validateCommand('tools/call', nested); expect(result.valid).toBe(false); expect(result.error).toBe('Invalid argument structure'); }); }); describe('Tool Execution Validation', () => { it('should allow whitelisted tools', () => { const result = validator.validateToolExecution('echo', { text: 'hello' }); expect(result.valid).toBe(true); expect(result.sanitizedArgs).toBeDefined(); }); it('should reject non-whitelisted tools', () => { const result = validator.validateToolExecution('system_exec', { cmd: 'ls' }); expect(result.valid).toBe(false); expect(result.error).toContain('Tool not allowed'); }); it('should validate calculate tool expressions', () => { const validResult = validator.validateToolExecution('calculate', { expression: '2 + 2', }); expect(validResult.valid).toBe(true); const invalidResult = validator.validateToolExecution('calculate', { expression: 'eval("malicious")', }); expect(invalidResult.valid).toBe(false); expect(invalidResult.error).toBe('Invalid calculation expression'); }); it('should limit query length', () => { const result = validator.validateToolExecution('query', { query: 'x'.repeat(1001), }); expect(result.valid).toBe(false); expect(result.error).toBe('Query too long'); }); it('should sanitize tool arguments', () => { const result = validator.validateToolExecution('echo', { text: '<script>alert("xss")</script>', safe: 'value', __proto__: 'ignored', }); expect(result.valid).toBe(true); expect(result.sanitizedArgs.text).not.toContain('<script>'); expect(result.sanitizedArgs.__proto__).toBeUndefined(); }); }); describe('Resource Access Validation', () => { it('should allow valid resource URIs', () => { const validUris = [ 'server-info', 'config/settings', 'data/users/profile.json', 'templates/email/welcome.txt', ]; validUris.forEach(uri => { const result = validator.validateResourceAccess(uri); expect(result.valid).toBe(true); }); }); it('should reject path traversal attempts', () => { const maliciousUris = [ '../../../etc/passwd', 'data/../../secret', '..\\..\\windows\\system32', ]; maliciousUris.forEach(uri => { const result = validator.validateResourceAccess(uri); expect(result.valid).toBe(false); expect(result.error).toBe('Path traversal detected'); }); }); it('should reject URIs exceeding length limit', () => { const longUri = 'x'.repeat(501); const result = validator.validateResourceAccess(longUri); expect(result.valid).toBe(false); expect(result.error).toBe('URI too long'); }); it('should reject non-whitelisted patterns', () => { const result = validator.validateResourceAccess('/etc/passwd'); expect(result.valid).toBe(false); expect(result.error).toContain('Resource access denied'); }); }); describe('Output Sanitization', () => { it('should sanitize sensitive data in strings', () => { const input = 'Password=secret123, token="abc123", API_KEY=xyz789'; const output = validator.sanitizeOutput(input); expect(output).not.toContain('secret123'); expect(output).not.toContain('abc123'); expect(output).not.toContain('xyz789'); expect(output).toContain('password=***'); expect(output).toContain('token=***'); expect(output).toContain('api_key=***'); }); it('should sanitize sensitive keys in objects', () => { const input = { username: 'john', password: 'secret', data: { apiToken: 'token123', secretKey: 'key456', }, }; const output = validator.sanitizeOutput(input); expect(output.username).toBe('john'); expect(output.password).toBe('***'); expect(output.data.apiToken).toBe('***'); expect(output.data.secretKey).toBe('***'); }); it('should handle arrays', () => { const input = [ { name: 'item1', token: 'secret1' }, { name: 'item2', token: 'secret2' }, ]; const output = validator.sanitizeOutput(input); expect(output[0].name).toBe('item1'); expect(output[0].token).toBe('***'); expect(output[1].name).toBe('item2'); expect(output[1].token).toBe('***'); }); }); describe('Request ID Validation', () => { it('should accept valid request IDs', () => { const validIds = [ 'abc123', 123, 'request-id-123', null, ]; validIds.forEach(id => { expect(validator.validateRequestId(id)).toBe(true); }); }); it('should reject invalid request IDs', () => { const invalidIds = [ 'x'.repeat(101), // Too long 'id with spaces', 'id<script>', {}, [], undefined, Number.POSITIVE_INFINITY, ]; invalidIds.forEach(id => { expect(validator.validateRequestId(id)).toBe(false); }); }); }); describe('Security Attack Prevention', () => { const attackVectors = [ { name: 'Command injection', message: { jsonrpc: '2.0', method: 'tools/call', params: { name: 'echo', arguments: { text: '; rm -rf /', }, }, id: 1, }, }, { name: 'Path traversal', message: { jsonrpc: '2.0', method: 'resources/read', params: { uri: '../../../etc/passwd', }, id: 2, }, }, { name: 'XSS injection', message: { jsonrpc: '2.0', method: 'tools/call', params: { name: 'echo', arguments: { text: '<script>alert("xss")</script>', }, }, id: 3, }, }, { name: 'Prototype pollution', message: { jsonrpc: '2.0', method: 'tools/call', params: { name: 'echo', arguments: { __proto__: { isAdmin: true }, }, }, id: 4, }, }, ]; attackVectors.forEach(({ name, message }) => { it(`should prevent ${name} attack`, async () => { const messageStr = JSON.stringify(message); const validation = await validator.validateMessage(messageStr); if (validation.valid && validation.sanitized) { // Check that dangerous content is sanitized const sanitizedStr = JSON.stringify(validation.sanitized); expect(sanitizedStr).not.toContain('rm -rf'); expect(sanitizedStr).not.toContain('/etc/passwd'); expect(sanitizedStr).not.toContain('<script>'); expect(sanitizedStr).not.toContain('__proto__'); } }); }); }); describe('Rate Limiting and DoS Prevention', () => { it('should cache request hashes for replay prevention', async () => { const message = JSON.stringify({ jsonrpc: '2.0', method: 'ping', id: 1, }); // First request should succeed const result1 = await validator.validateMessage(message); expect(result1.valid).toBe(true); // Immediate replay should be rejected const result2 = await validator.validateMessage(message); expect(result2.valid).toBe(false); expect(result2.error).toBe('Duplicate request'); }); it('should clean up expired cache entries', async () => { // This test would require mocking Date.now() to test TTL expiration expect(true).toBe(true); // Placeholder }); it('should limit cache size', async () => { // Generate many unique messages to test cache size limit for (let i = 0; i < 1100; i++) { const message = JSON.stringify({ jsonrpc: '2.0', method: 'ping', id: i, }); await validator.validateMessage(message); } // Cache should not exceed MAX_CACHE_SIZE (1000) expect(true).toBe(true); // Placeholder - implementation detail }); }); }); /** * Integration tests for MCP Protocol Security */ describe('MCP Protocol Security Integration', () => { describe('WebSocket Manager Integration', () => { it('should integrate with WebSocket message handling', () => { // Integration test with WebSocketManager expect(true).toBe(true); // Placeholder }); it('should properly handle connection lifecycle', () => { // Test connection initialization, message handling, and cleanup expect(true).toBe(true); // Placeholder }); it('should enforce rate limiting per connection', () => { // Test rate limiting functionality expect(true).toBe(true); // Placeholder }); }); describe('Permission System Integration', () => { it('should enforce tool permissions', () => { // Test that tools are only accessible with proper permissions expect(true).toBe(true); // Placeholder }); it('should enforce resource permissions', () => { // Test that resources are only accessible with proper permissions expect(true).toBe(true); // Placeholder }); }); });

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/perfecxion-ai/secure-mcp'

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