UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

199 lines (198 loc) 7.38 kB
import { BaseTool } from './BaseTool.js'; import { exec } from 'child_process'; import { promisify } from 'util'; import path from 'path'; const execAsync = promisify(exec); export class CommandExecutionTool extends BaseTool { constructor(baseDir) { super(); this.maxTimeout = 30000; // 30 seconds default timeout this.allowedCommands = [ // File operations 'ls', 'dir', 'find', 'grep', 'cat', 'head', 'tail', 'wc', 'du', // Git operations 'git', // Package managers 'npm', 'yarn', 'pnpm', 'pip', 'cargo', 'go', // Build tools 'make', 'cmake', 'mvn', 'gradle', // Text processing 'sed', 'awk', 'sort', 'uniq', 'cut', // System info 'pwd', 'whoami', 'date', 'uname', 'sleep', 'echo', // Node.js 'node', 'tsx', 'ts-node' ]; this.baseDir = baseDir || process.cwd(); } getName() { return 'execute_command'; } getDescription() { return 'Execute shell commands safely with timeout and directory restrictions. Supports common development commands like git, npm, file operations, and text processing.'; } getParameters() { return [ { name: 'command', type: 'string', description: 'The shell command to execute', required: true }, { name: 'working_directory', type: 'string', description: 'Working directory for command execution (relative to base directory)', required: false, default: '.' }, { name: 'timeout', type: 'number', description: 'Command timeout in milliseconds (max 60000ms)', required: false, default: 30000 }, { name: 'capture_stderr', type: 'boolean', description: 'Whether to capture stderr output', required: false, default: true } ]; } async execute(parameters) { // Validate parameters const validationError = this.validateParameters(parameters); if (validationError) { return validationError; } const { command, working_directory = '.', timeout = this.maxTimeout, capture_stderr = true } = parameters; // Security checks const securityCheck = this.performSecurityChecks(command); if (!securityCheck.safe) { return { success: false, error: `Command rejected for security reasons: ${securityCheck.reason}` }; } // Validate timeout if (timeout > 60000) { return { success: false, error: 'Timeout cannot exceed 60 seconds (60000ms)' }; } try { // Resolve working directory const workingDir = path.resolve(this.baseDir, working_directory); // Ensure working directory is within base directory if (!workingDir.startsWith(this.baseDir)) { return { success: false, error: 'Working directory must be within the base directory' }; } // Execute command const result = await this.executeCommand(command, workingDir, timeout, capture_stderr); return { success: true, data: { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, command: command, workingDirectory: workingDir, executionTime: result.executionTime }, message: `Command executed successfully (exit code: ${result.exitCode})` }; } catch (error) { const err = error; return { success: false, error: `Command execution failed: ${err.message}`, data: { stdout: err.stdout || '', stderr: err.stderr || '', exitCode: err.code || -1, command: command } }; } } performSecurityChecks(command) { // Check for dangerous commands const dangerousPatterns = [ /rm\s+-rf\s*\//, // rm -rf / />\s*\/dev\/sd/, // writing to disk devices /mkfs/, // filesystem creation /dd\s+if=/, // disk duplication /sudo/, // privilege escalation /su\s/, // user switching /passwd/, // password changes /chmod\s+777/, // dangerous permissions /curl.*\|\s*sh/, // pipe to shell /wget.*\|\s*sh/, // pipe to shell /eval/, // code evaluation /exec/, // code execution ]; for (const pattern of dangerousPatterns) { if (pattern.test(command)) { return { safe: false, reason: `Dangerous pattern detected: ${pattern.source}` }; } } // Check if command starts with an allowed command const commandParts = command.trim().split(/\s+/); const baseCommand = commandParts[0]; // Allow commands that start with allowed prefixes const isAllowed = this.allowedCommands.some(allowed => baseCommand === allowed || baseCommand.startsWith(allowed + '.')); if (!isAllowed) { return { safe: false, reason: `Command '${baseCommand}' is not in the allowed list. Allowed commands: ${this.allowedCommands.join(', ')}` }; } return { safe: true }; } async executeCommand(command, workingDir, timeout, captureStderr) { const startTime = Date.now(); try { const options = { cwd: workingDir, timeout: timeout, maxBuffer: 1024 * 1024 * 10, // 10MB buffer encoding: 'utf8' }; const result = await execAsync(command, options); const executionTime = Date.now() - startTime; return { stdout: result.stdout || '', stderr: captureStderr ? (result.stderr || '') : '', exitCode: 0, executionTime }; } catch (error) { const executionTime = Date.now() - startTime; // Handle timeout if (error.killed && error.signal === 'SIGTERM') { throw { message: `Command timed out after ${timeout}ms`, stdout: error.stdout || '', stderr: error.stderr || '', code: -1 }; } // Handle other execution errors throw { message: error.message, stdout: error.stdout || '', stderr: captureStderr ? (error.stderr || '') : '', code: error.code || -1 }; } } }