/**
* 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
});
});
});