contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
199 lines (198 loc) • 7.38 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.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
};
}
}
}