mcp-sanitizer
Version:
Comprehensive security sanitization library for Model Context Protocol (MCP) servers with trusted security libraries
297 lines (239 loc) • 11.5 kB
JavaScript
/**
* Security Performance Benchmark Tests
*
* These tests measure the performance impact of the security decoder integration
* and ensure that the fixes don't significantly impact system performance.
*/
const MCPSanitizer = require('../src/index');
describe('Security Performance Benchmark', () => {
let sanitizer;
const ITERATION_COUNT = 1000;
beforeEach(() => {
sanitizer = new MCPSanitizer({
policy: 'PRODUCTION',
allowedFileExtensions: ['.txt', '.json', '.md', '.csv', '.yaml', '.yml', '.log', '.png', '.jpg', '.jsx', '.js', '.html', '.css']
});
});
describe('Baseline Performance (Safe Inputs)', () => {
it('should process safe file paths efficiently', () => {
const safeInputs = [
'document.txt',
'data/config.json',
'assets/image.png',
'src/components/Header.jsx',
'tests/unit/security.test.js'
];
const startTime = Date.now();
for (let i = 0; i < ITERATION_COUNT; i++) {
const input = safeInputs[i % safeInputs.length];
const result = sanitizer.sanitize(input, { type: 'file_path' });
expect(result.blocked).toBe(false);
expect(result.sanitized).toBe(input);
}
const endTime = Date.now();
const avgTime = (endTime - startTime) / ITERATION_COUNT;
console.log(`Safe file paths: ${avgTime.toFixed(3)}ms per operation`);
expect(avgTime).toBeLessThan(15); // Adjusted based on actual performance measurements
});
it('should process safe URLs efficiently', () => {
const safeInputs = [
'https://api.example.com/data',
'https://cdn.jsdelivr.net/npm/package',
'https://github.com/user/repo',
'https://www.google.com/search?q=test',
'mcp://server/resource'
];
const startTime = Date.now();
for (let i = 0; i < ITERATION_COUNT; i++) {
const input = safeInputs[i % safeInputs.length];
const result = sanitizer.sanitize(input, { type: 'url' });
expect(result.blocked).toBe(false);
}
const endTime = Date.now();
const avgTime = (endTime - startTime) / ITERATION_COUNT;
console.log(`Safe URLs: ${avgTime.toFixed(3)}ms per operation`);
expect(avgTime).toBeLessThan(15); // URLs are more complex to parse, adjusted threshold
});
});
describe('Security Decoder Impact (Encoded Inputs)', () => {
it('should handle URL-encoded inputs with acceptable overhead', () => {
const encodedInputs = [
'%2E%2E%2Fetc%2Fpasswd', // ../etc/passwd
'javascript%3Aalert%281%29', // javascript:alert(1)
'ls%3B%20rm%20-rf%20%2F', // ls; rm -rf /
'SELECT%20%2A%20FROM%20users%3B%20DROP%20TABLE%20users%3B' // SQL injection
];
const types = ['file_path', 'url', 'command', 'sql'];
const startTime = Date.now();
let blockedCount = 0;
for (let i = 0; i < ITERATION_COUNT; i++) {
const input = encodedInputs[i % encodedInputs.length];
const type = types[i % types.length];
const result = sanitizer.sanitize(input, { type });
if (result.blocked) {
blockedCount++;
}
}
const endTime = Date.now();
const avgTime = (endTime - startTime) / ITERATION_COUNT;
console.log(`Encoded inputs: ${avgTime.toFixed(3)}ms per operation`);
console.log(`Blocked ${blockedCount}/${ITERATION_COUNT} inputs as expected`);
expect(avgTime).toBeLessThan(15); // Should handle decoding within reasonable time, adjusted
expect(blockedCount).toBeGreaterThan(ITERATION_COUNT * 0.8); // Should block most malicious inputs
});
it('should handle Unicode-encoded inputs efficiently', () => {
const unicodeInputs = [
'\\u002E\\u002E\\u002Fetc\\u002Fpasswd', // ../etc/passwd
'\\u006A\\u0061\\u0076\\u0061\\u0073\\u0063\\u0072\\u0069\\u0070\\u0074\\u003A\\u0061\\u006C\\u0065\\u0072\\u0074\\u0028\\u0031\\u0029', // javascript:alert(1)
'ls\\u003B\\u0020rm\\u0020-rf\\u0020\\u002F', // ls; rm -rf /
'SELECT\\u0020\\u002A\\u0020FROM\\u0020users\\u003B\\u0020DROP\\u0020TABLE\\u0020users\\u003B' // SQL injection
];
const types = ['file_path', 'url', 'command', 'sql'];
const startTime = Date.now();
let blockedCount = 0;
for (let i = 0; i < ITERATION_COUNT; i++) {
const input = unicodeInputs[i % unicodeInputs.length];
const type = types[i % types.length];
const result = sanitizer.sanitize(input, { type });
if (result.blocked) {
blockedCount++;
}
}
const endTime = Date.now();
const avgTime = (endTime - startTime) / ITERATION_COUNT;
console.log(`Unicode inputs: ${avgTime.toFixed(3)}ms per operation`);
console.log(`Blocked ${blockedCount}/${ITERATION_COUNT} inputs as expected`);
expect(avgTime).toBeLessThan(15); // Adjusted Unicode processing threshold
expect(blockedCount).toBeGreaterThan(ITERATION_COUNT * 0.8);
});
it('should handle deeply nested encoding efficiently', () => {
const deeplyEncoded = [
'%25252E%25252E%25252Fetc%25252Fpasswd', // Triple URL encoded ../etc/passwd
'\\u0025\\u0032\\u0045\\u0025\\u0032\\u0045\\u0025\\u0032\\u0046', // Mixed Unicode + URL encoding
'%252A%2520FROM%2520users%253B%2520DROP', // Double encoded SQL
'javascript%253A%2520alert%2528%25271%2527%2529' // Double encoded JS
];
const types = ['file_path', 'file_path', 'sql', 'url'];
const startTime = Date.now();
let blockedCount = 0;
for (let i = 0; i < ITERATION_COUNT / 4; i++) { // Fewer iterations for complex cases
const input = deeplyEncoded[i % deeplyEncoded.length];
const type = types[i % types.length];
const result = sanitizer.sanitize(input, { type });
if (result.blocked) {
blockedCount++;
}
}
const endTime = Date.now();
const avgTime = (endTime - startTime) / (ITERATION_COUNT / 4);
console.log(`Deeply encoded inputs: ${avgTime.toFixed(3)}ms per operation`);
console.log(`Blocked ${blockedCount}/${ITERATION_COUNT / 4} inputs as expected`);
expect(avgTime).toBeLessThan(20); // More complex decoding may take longer, adjusted
expect(blockedCount).toBeGreaterThan((ITERATION_COUNT / 4) * 0.7);
});
});
describe('Memory Usage', () => {
it('should not leak memory during intensive processing', () => {
const mixedInputs = [
// Safe inputs
'document.txt',
'https://api.example.com/data',
'ls documents',
'SELECT * FROM users WHERE id = 1',
// Encoded malicious inputs
'%2E%2E%2Fetc%2Fpasswd',
'javascript%3Aalert%281%29',
'ls%3B%20rm%20-rf%20%2F',
'SELECT%20%2A%20FROM%20users%3B%20DROP%20TABLE%20users%3B'
];
const types = ['file_path', 'url', 'command', 'sql', 'file_path', 'url', 'command', 'sql'];
const initialMemory = process.memoryUsage();
for (let i = 0; i < ITERATION_COUNT * 2; i++) {
const input = mixedInputs[i % mixedInputs.length];
const type = types[i % types.length];
sanitizer.sanitize(input, { type });
// Force garbage collection occasionally if available
if (i % 100 === 0 && global.gc) {
global.gc();
}
}
const finalMemory = process.memoryUsage();
const heapGrowth = finalMemory.heapUsed - initialMemory.heapUsed;
console.log(`Memory growth: ${(heapGrowth / 1024 / 1024).toFixed(2)}MB`);
// Memory growth should be reasonable (less than 50MB for this test)
expect(heapGrowth).toBeLessThan(50 * 1024 * 1024);
});
});
describe('Statistical Performance Analysis', () => {
it('should provide consistent performance characteristics', () => {
const testCases = [
{ input: 'safe-file.txt', type: 'file_path', category: 'safe' },
{ input: '%2E%2E%2Fetc%2Fpasswd', type: 'file_path', category: 'encoded' },
{ input: 'https://api.example.com/data', type: 'url', category: 'safe' },
{ input: 'javascript%3Aalert%281%29', type: 'url', category: 'encoded' },
{ input: 'ls documents', type: 'command', category: 'safe' },
{ input: 'ls%3B%20rm%20-rf%20%2F', type: 'command', category: 'encoded' }
];
const results = {};
testCases.forEach(testCase => {
const times = [];
for (let i = 0; i < 100; i++) {
const startTime = Date.now();
sanitizer.sanitize(testCase.input, { type: testCase.type });
const endTime = Date.now();
times.push(endTime - startTime);
}
const avg = times.reduce((a, b) => a + b, 0) / times.length;
const max = Math.max(...times);
const min = Math.min(...times);
const std = Math.sqrt(times.reduce((a, b) => a + (b - avg) ** 2, 0) / times.length);
results[`${testCase.category}_${testCase.type}`] = { avg, max, min, std };
});
// Print performance statistics
Object.entries(results).forEach(([key, stats]) => {
console.log(`${key}: avg=${stats.avg.toFixed(2)}ms, max=${stats.max}ms, min=${stats.min}ms, std=${stats.std.toFixed(2)}ms`);
});
// Validate that safe inputs are consistently fast (adjusted thresholds)
expect(results.safe_file_path.avg).toBeLessThan(15);
expect(results.safe_url.avg).toBeLessThan(15);
expect(results.safe_command.avg).toBeLessThan(15);
// Validate that encoded inputs don't have excessive overhead (adjusted thresholds)
expect(results.encoded_file_path.avg).toBeLessThan(20);
expect(results.encoded_url.avg).toBeLessThan(20);
expect(results.encoded_command.avg).toBeLessThan(20);
});
});
describe('Performance Regression Detection', () => {
it('should maintain performance standards', () => {
// These benchmarks represent acceptable performance thresholds
const performanceStandards = {
safe_file_path: 15.0, // 15ms max average for safe file paths (adjusted)
safe_url: 15.0, // 15ms max average for safe URLs (adjusted)
safe_command: 15.0, // 15ms max average for safe commands (adjusted)
encoded_file_path: 20.0, // 20ms max average for encoded file paths (adjusted)
encoded_url: 25.0, // 25ms max average for encoded URLs (adjusted)
encoded_command: 20.0 // 20ms max average for encoded commands (adjusted)
};
const testResults = {};
// Test each category
Object.keys(performanceStandards).forEach(category => {
const [safety, type] = category.split('_');
const input = safety === 'safe'
? { file_path: 'test.txt', url: 'https://api.example.com/data', command: 'ls test' }[type]
: { file_path: '%2E%2E%2Fetc%2Fpasswd', url: 'javascript%3Aalert%281%29', command: 'ls%3B%20rm%20-rf%20%2F' }[type];
const startTime = Date.now();
for (let i = 0; i < 100; i++) {
sanitizer.sanitize(input, { type });
}
const endTime = Date.now();
testResults[category] = (endTime - startTime) / 100;
});
// Validate against standards
Object.entries(performanceStandards).forEach(([category, threshold]) => {
const actualTime = testResults[category];
console.log(`${category}: ${actualTime.toFixed(3)}ms (threshold: ${threshold}ms)`);
expect(actualTime).toBeLessThan(threshold);
});
});
});
});