contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
207 lines (206 loc) • 8.21 kB
JavaScript
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
};
}
}
}