UNPKG

safe-commander-mcp

Version:

A secure MCP server for executing whitelisted development commands with comprehensive security controls and resource limits

92 lines (91 loc) 3.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateCommand = validateCommand; exports.isCommandValid = isCommandValid; exports.getBaseCommand = getBaseCommand; const sanitizer_1 = require("../security/sanitizer"); const path_validator_1 = require("../security/path-validator"); const config_loader_1 = require("../config/config-loader"); const logger_1 = require("../utils/logger"); /** * Enhanced command validation with length and security checks * Updated with secure error messages that don't leak configuration information * * @param command - The command string to validate * @throws Error if command is invalid or fails security checks */ function validateCommand(command) { if (!command || typeof command !== 'string') { throw new Error('Command must be a non-empty string'); } if (command.length > config_loader_1.CONFIG.MAX_COMMAND_LENGTH) { throw new Error(`Command exceeds maximum length of ${config_loader_1.CONFIG.MAX_COMMAND_LENGTH} characters`); } // Sanitize and validate against dangerous characters const sanitized = (0, sanitizer_1.sanitizeCommand)(command); const firstWord = sanitized.split(' ')[0]; if (!config_loader_1.CONFIG.ALLOWED_COMMANDS.includes(firstWord)) { // SECURITY: Updated error message to remove information leakage // Old (bad): `Command '${firstWord}' is not in the allowed commands list: [${CONFIG.ALLOWED_COMMANDS.join(', ')}]` // New (secure): No configuration disclosure throw new Error(`Command '${firstWord}' is not permitted. Use the 'list_available_commands' tool to see available options.`); } // Validate paths in command arguments for path-sensitive commands const args = sanitized.split(' ').slice(1); // Get arguments after the command const pathSensitiveCommands = ['cat', 'ls', 'node', 'npm', 'find', 'grep', 'head', 'tail']; if (pathSensitiveCommands.includes(firstWord)) { for (const arg of args) { // Skip flags (arguments starting with -) if (arg.startsWith('-')) { continue; } // Check if argument looks like a file path (contains / or is a relative path) if (arg.includes('/') || arg.includes('\\') || arg.startsWith('.')) { try { // Validate the path to prevent directory traversal (0, path_validator_1.validatePath)(arg, config_loader_1.CONFIG.ALLOWED_PATH); (0, logger_1.log)('info', `Path validated successfully`, { command: firstWord, path: arg }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Path validation failed'; // SECURITY: Don't reveal internal path structure in error messages throw new Error(`Invalid path argument in command. Path must be within the allowed working directory.`); } } } } } /** * Validate command without throwing (for testing/checking) * * @param command - The command string to validate * @returns Object with validation result and error message if invalid */ function isCommandValid(command) { try { validateCommand(command); return { valid: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown validation error'; return { valid: false, error: errorMessage }; } } /** * Get the base command (first word) from a command string * * @param command - Full command string * @returns The base command name */ function getBaseCommand(command) { if (!command || typeof command !== 'string') { return ''; } try { const sanitized = (0, sanitizer_1.sanitizeCommand)(command); return sanitized.split(' ')[0]; } catch { return ''; } }