UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

207 lines (206 loc) 8.21 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.blacklistedCommands = [ // System modification 'sudo', 'su', 'doas', // User/password management 'passwd', 'chpasswd', 'usermod', 'useradd', 'userdel', // Network/security risks 'nc', 'netcat', 'telnet', 'ssh', 'scp', 'rsync', // System services 'systemctl', 'service', 'launchctl', // Dangerous file operations 'dd', 'shred', 'wipe', // Filesystem operations 'mkfs', 'fdisk', 'parted', 'mount', 'umount', // Process control (except basic ones) 'kill', 'killall', 'pkill', // Compiler/interpreter risks 'gcc', 'clang', 'python', 'perl', 'ruby', 'php', // Package installation (should use specific tools) 'apt', 'yum', 'dnf', 'pacman', 'brew', // Archive extraction (potential zip bombs) 'tar', 'unzip', 'gunzip', 'bunzip2' ]; this.baseDir = baseDir || process.cwd(); } getName() { return 'execute_command'; } getDescription() { return 'Execute shell commands safely with timeout and directory restrictions. Uses blacklist approach - blocks dangerous commands while allowing most legitimate tools and utilities.'; } 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, 300000ms for Docker commands)', 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 (allow longer timeouts for Docker-related operations) const isDockerRelated = command.includes('docker') || command.includes('musicgen') || command.includes('/tmp/musicgen'); const maxAllowedTimeout = isDockerRelated ? 300000 : 60000; // 5 minutes for Docker-related, 1 minute for others if (timeout > maxAllowedTimeout) { return { success: false, error: `Timeout cannot exceed ${maxAllowedTimeout / 1000} seconds (${maxAllowedTimeout}ms)` }; } 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 a blacklisted command const commandParts = command.trim().split(/\s+/); const baseCommand = commandParts[0]; // Block commands that start with blacklisted prefixes const isBlacklisted = this.blacklistedCommands.some(blocked => baseCommand === blocked || baseCommand.startsWith(blocked + '.')); if (isBlacklisted) { return { safe: false, reason: `Command '${baseCommand}' is blacklisted for security reasons. Blocked commands include: ${this.blacklistedCommands.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 }; } } }