/**
* Security Test Suite for FAF MCP
* Championship-level security validation
*/
import { describe, test, expect } from '@jest/globals';
import * as path from 'path';
// Mock file operations for testing
const validatePath = (filePath: string): boolean => {
// Check for empty path
if (!filePath || filePath.trim() === '') return false;
// Check for directory traversal
if (filePath.includes('..')) return false;
// Check for absolute paths outside cwd
const resolved = path.resolve(filePath);
const cwd = process.cwd();
if (!resolved.startsWith(cwd)) return false;
// Check for system directories
const blocked = ['/etc', '/sys', '/proc', '/dev', '/root'];
if (blocked.some(dir => resolved.startsWith(dir))) return false;
return true;
};
const sanitizeInput = (input: string): string => {
// Remove control characters
let clean = input.replace(/[\x00-\x1F\x7F]/g, '');
// Remove potential command injection characters
clean = clean.replace(/[;&|`$(){}[\]]/g, '');
return clean;
};
describe('Path Traversal Protection', () => {
test('should block directory traversal attempts', () => {
expect(validatePath('../../../etc/passwd')).toBe(false);
expect(validatePath('../../sensitive.txt')).toBe(false);
expect(validatePath('./../data')).toBe(false);
});
test('should block absolute paths outside working directory', () => {
expect(validatePath('/etc/passwd')).toBe(false);
expect(validatePath('/root/.ssh/id_rsa')).toBe(false);
expect(validatePath('/sys/kernel')).toBe(false);
});
test('should allow valid relative paths', () => {
expect(validatePath('test.txt')).toBe(true);
expect(validatePath('./data/file.json')).toBe(true);
expect(validatePath('src/index.ts')).toBe(true);
});
test('should handle edge cases', () => {
expect(validatePath('')).toBe(false);
expect(validatePath('.')).toBe(true);
expect(validatePath('./')).toBe(true);
});
});
describe('Input Sanitization', () => {
test('should remove control characters', () => {
expect(sanitizeInput('test\x00file')).toBe('testfile');
expect(sanitizeInput('data\x1Fvalue')).toBe('datavalue');
});
test('should remove command injection characters', () => {
expect(sanitizeInput('rm -rf /; echo test')).toBe('rm -rf / echo test');
expect(sanitizeInput('test && malicious')).toBe('test malicious');
expect(sanitizeInput('$(whoami)')).toBe('whoami');
});
test('should preserve normal text', () => {
expect(sanitizeInput('normal-file_name.txt')).toBe('normal-file_name.txt');
expect(sanitizeInput('Score: 88/100')).toBe('Score: 88/100');
});
});
describe('Resource Limits', () => {
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const MAX_PATH_LENGTH = 255;
const OPERATION_TIMEOUT = 5000;
test('should enforce file size limits', () => {
const checkFileSize = (size: number) => size <= MAX_FILE_SIZE;
expect(checkFileSize(1024)).toBe(true); // 1KB
expect(checkFileSize(5 * 1024 * 1024)).toBe(true); // 5MB
expect(checkFileSize(11 * 1024 * 1024)).toBe(false); // 11MB
});
test('should enforce path length limits', () => {
const checkPathLength = (p: string) => p.length <= MAX_PATH_LENGTH;
expect(checkPathLength('normal/path.txt')).toBe(true);
expect(checkPathLength('a'.repeat(256))).toBe(false);
});
test('should enforce operation timeouts', () => {
const checkTimeout = (duration: number) => duration <= OPERATION_TIMEOUT;
expect(checkTimeout(100)).toBe(true);
expect(checkTimeout(5000)).toBe(true);
expect(checkTimeout(6000)).toBe(false);
});
});
describe('Error Message Sanitization', () => {
const sanitizeError = (error: Error): string => {
let message = error.message;
// Remove absolute paths
message = message.replace(/\/[^\s]+/g, '[path]');
// Remove system information
message = message.replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[ip]');
// Remove stack traces
if (message.includes('at ')) {
message = message.split('\n')[0];
}
return message;
};
test('should remove absolute paths from errors', () => {
const error = new Error('File not found: /Users/username/secret.txt');
expect(sanitizeError(error)).toBe('File not found: [path]');
});
test('should remove IP addresses', () => {
const error = new Error('Connection failed to 192.168.1.1');
expect(sanitizeError(error)).toBe('Connection failed to [ip]');
});
test('should remove stack traces', () => {
const error = new Error('Error occurred\n at function (file.js:10:5)');
expect(sanitizeError(error)).toBe('Error occurred');
});
});
describe('MCP Protocol Security', () => {
test('should validate JSON-RPC messages', () => {
const isValidMessage = (msg: any): boolean => {
if (!msg.jsonrpc || msg.jsonrpc !== '2.0') return false;
if (!msg.method && !msg.id) return false;
if (msg.params && typeof msg.params !== 'object') return false;
return true;
};
expect(isValidMessage({ jsonrpc: '2.0', method: 'test', id: 1 })).toBe(true);
expect(isValidMessage({ jsonrpc: '1.0', method: 'test' })).toBe(false);
expect(isValidMessage({ method: 'test' })).toBe(false);
expect(isValidMessage({ jsonrpc: '2.0' })).toBe(false);
});
test('should limit message size', () => {
const MAX_MESSAGE_SIZE = 1024 * 1024; // 1MB
const checkMessageSize = (msg: string) => msg.length <= MAX_MESSAGE_SIZE;
expect(checkMessageSize('{"test": "data"}')).toBe(true);
expect(checkMessageSize('x'.repeat(MAX_MESSAGE_SIZE + 1))).toBe(false);
});
});
// Export test utilities for use in implementation
export { validatePath, sanitizeInput };