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
JavaScript
;
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);
}
}
}