UNPKG

safe-commander-mcp

Version:

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

85 lines (84 loc) 3.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeCommand = executeCommand; const node_child_process_1 = require("node:child_process"); const node_util_1 = require("node:util"); const command_validator_1 = require("./command-validator"); const rate_limiter_1 = require("../security/rate-limiter"); const config_loader_1 = require("../config/config-loader"); const logger_1 = require("../utils/logger"); const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec); /** * Execute a command with comprehensive security and monitoring * * @param request - The command request containing the command to execute * @returns Promise resolving to command execution result */ async function executeCommand(request) { const { command } = request; const startTime = Date.now(); let commandId = null; try { // Input validation if (!command || typeof command !== 'string') { throw new Error('Command parameter is required and must be a non-empty string'); } // Comprehensive security and validation checks (0, command_validator_1.validateCommand)(command); (0, rate_limiter_1.checkRateLimit)(command, config_loader_1.CONFIG.MAX_CONCURRENT_COMMANDS); // Track the running command commandId = `${command}_${startTime}`; (0, rate_limiter_1.trackCommandStart)(commandId, command); (0, logger_1.log)('info', `Executing command: ${command}`, { commandId, workingDirectory: config_loader_1.CONFIG.ALLOWED_PATH }); // Execute command with resource limits const { stdout, stderr } = await execAsync(command, { cwd: config_loader_1.CONFIG.ALLOWED_PATH, timeout: config_loader_1.CONFIG.COMMAND_TIMEOUT, maxBuffer: config_loader_1.CONFIG.MAX_OUTPUT_SIZE, }); const executionTime = Date.now() - startTime; (0, logger_1.log)('info', `Command completed successfully`, { command, executionTime: `${executionTime}ms`, outputSize: `${(stdout + stderr).length} bytes` }); // Check output size and provide feedback const totalOutput = stdout + stderr; let outputMessage = `Command: ${command}\nExecution time: ${executionTime}ms\n\nOutput:\n${stdout}`; if (stderr) { outputMessage += `\nErrors:\n${stderr}`; } if (totalOutput.length > config_loader_1.CONFIG.MAX_OUTPUT_SIZE) { (0, logger_1.log)('warn', `Command output truncated due to size limit`, { command, originalSize: totalOutput.length }); outputMessage += `\n\n[OUTPUT TRUNCATED - Original size: ${totalOutput.length} bytes, Limit: ${config_loader_1.CONFIG.MAX_OUTPUT_SIZE} bytes]`; } return { success: true, output: outputMessage, stderr, executionTime, command }; } catch (error) { const executionTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred during command execution'; (0, logger_1.log)('error', `Command failed`, { command, executionTime: `${executionTime}ms`, error: errorMessage }); return { success: false, output: `Error executing command '${command}': ${errorMessage}`, executionTime, command }; } finally { // Always clean up the running command tracker if (commandId) { (0, rate_limiter_1.trackCommandEnd)(commandId); } } }