UNPKG

mcp-sanitizer

Version:

Comprehensive security sanitization library for Model Context Protocol (MCP) servers with trusted security libraries

207 lines (165 loc) 7.29 kB
/** * Parser Differential Vulnerability Test (CVE-TBD-001, CVSS 9.1) * * Tests for the critical parser differential vulnerability where security-decoder.js * and validation-utils.js use different parsing logic, allowing bypass attacks. * * Vulnerability: The security decoder normalizes strings, but validators may * access the original string, allowing attackers to craft inputs that appear * safe to the decoder but malicious to the validators. */ const MCPSanitizer = require('../src/sanitizer/mcp-sanitizer'); const { securityDecode } = require('../src/utils/security-decoder'); // validateCommand not needed after refactor describe('Parser Differential Vulnerability Tests (CVE-TBD-001)', () => { let sanitizer; beforeEach(() => { sanitizer = new MCPSanitizer('PRODUCTION'); }); describe('Directional Override Attacks', () => { test('should prevent directional override bypass in commands', () => { // This payload uses Unicode directional overrides to hide malicious content const payload = '\u0063\u0061\u0074\u202e/passwd\u202d/etc'; // Security decoder sees: "cat/etc/passwd" (after normalization) // Command validator should see the same normalized version const result = sanitizer.sanitize(payload, { type: 'command' }); expect(result.blocked).toBe(true); expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/directional|override|encoding/i) ]) ); }); test('should prevent file path directional override bypass', () => { // Payload: appears to be "safe.txt" but actually accesses "/etc/passwd" const payload = 'safe\u202e/passwd\u202d.txt/etc'; const result = sanitizer.sanitize(payload, { type: 'file_path' }); expect(result.blocked).toBe(true); expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/directional|override|encoding|system directory/i) ]) ); }); }); // Unicode Homograph tests moved to security-comprehensive.test.js // These are now covered more thoroughly in the comprehensive security suite describe('Multiple Encoding Bypass', () => { test('should prevent double URL encoding bypass', () => { // Double URL encoded "../" sequence const payload = '%252e%252e%252f'; const result = sanitizer.sanitize(payload, { type: 'file_path' }); expect(result.blocked).toBe(true); expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/directory traversal|encoding/i) ]) ); }); test('should prevent mixed encoding bypass', () => { // Mix of Unicode escape and URL encoding const payload = '\\u002e\\u002e%2f'; const result = sanitizer.sanitize(payload, { type: 'file_path' }); expect(result.blocked).toBe(true); expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/directory traversal|blocked pattern|encoding/i) ]) ); }); }); describe('TOCTOU (Time-of-Check-Time-of-Use) Attacks', () => { test('should ensure consistent processing between decoder and validator', async () => { const payload = 'safe\u202e/passwd\u202d.txt/etc'; // Both should see the same normalized string const decoded = securityDecode(payload); // This should NOT bypass validation by seeing different strings const result = sanitizer.sanitize(payload, { type: 'file_path' }); expect(result.blocked).toBe(true); expect(decoded.decoded).not.toContain('\u202e'); expect(decoded.decoded).not.toContain('\u202d'); }); }); describe('Polyglot Attack Vectors', () => { test('should prevent command injection with polyglot payload', () => { // Combines multiple attack vectors const payload = 'sаfe\u202e; rm -rf /\u202d.txt'; const result = sanitizer.sanitize(payload, { type: 'command' }); expect(result.blocked).toBe(true); expect(result.warnings.length).toBeGreaterThan(0); }); test('should prevent SQL injection with polyglot payload', () => { const payload = 'SELECT\u202e\' OR \'1\'=\'1\u202d FROM users'; const result = sanitizer.sanitize(payload, { type: 'sql' }); expect(result.blocked).toBe(true); expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/sql|injection|directional/i) ]) ); }); }); describe('Zero-Width Character Attacks', () => { test('should prevent zero-width joiner bypass', () => { const payload = 'rm\u200d -rf /'; // Zero-width joiner const result = sanitizer.sanitize(payload, { type: 'command' }); expect(result.blocked).toBe(true); expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/zero.?width|dangerous|blocked/i) ]) ); }); test('should prevent zero-width no-break space bypass', () => { const payload = 'cat\ufeff/etc/passwd'; // ZWNBSP const result = sanitizer.sanitize(payload, { type: 'command' }); expect(result.blocked).toBe(true); expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/zero.?width|dangerous|blocked/i) ]) ); }); }); describe('Fix Validation Tests', () => { test('should demonstrate unified parsing prevents bypass', () => { const payload = '\u0063\u0061\u0074\u202e/passwd\u202d/etc'; // Test that both decoder and sanitizer see the same normalized string const decoded = securityDecode(payload); const result = sanitizer.sanitize(payload, { type: 'command' }); // The directional override should be removed, resulting in "cat/passwd/etc" expect(decoded.decoded).toBe('cat/passwd/etc'); expect(result.blocked).toBe(true); // Verify the attack was detected expect(result.warnings).toEqual( expect.arrayContaining([ expect.stringMatching(/dangerous|blocked|system|encoding/i) ]) ); }); test('should ensure immutability prevents modification attacks', () => { const payload = 'test\u202efile.txt\u202d'; const result = sanitizer.sanitize(payload, { type: 'file_path' }); // The normalized string should be immutable expect(result.metadata.originalInput).toBe(payload); if (result.sanitized) { expect(result.sanitized).not.toContain('\u202e'); expect(result.sanitized).not.toContain('\u202d'); } }); test('should validate comprehensive attack vector detection', () => { const attackVectors = [ '\u0063\u0061\u0074\u202e/passwd\u202d/etc', // Directional override 'cаt /etc/passwd', // Cyrillic homograph '%252e%252e%252f', // Double URL encoding 'rm\u200d -rf /', // Zero-width joiner 'cat\ufeff/etc/passwd' // Zero-width no-break space ]; attackVectors.forEach(payload => { const result = sanitizer.sanitize(payload, { type: 'command' }); expect(result.blocked).toBe(true); expect(result.warnings.length).toBeGreaterThan(0); }); }); }); });