mcp-sanitizer
Version:
Comprehensive security sanitization library for Model Context Protocol (MCP) servers with trusted security libraries
207 lines (165 loc) • 7.29 kB
JavaScript
/**
* 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);
});
});
});
});