mcp-wsl-exec

by spences10
Verified
  • src
import { spawn } from 'child_process'; import { dangerous_commands, wsl_config } from './constants.js'; import { CommandValidationError, CommandTimeoutError } from './errors.js'; import { CommandResponse } from './types.js'; export class CommandExecutor { private sanitize_command(command: string): string { // Enhanced command sanitization const sanitized = command .replace(/[;&|`$]/g, '') // Remove shell metacharacters .replace(/\\/g, '/') // Normalize path separators .replace(/\.\./g, '') // Remove parent directory references .replace(/~/g, '') // Remove home directory references .trim(); // Remove leading/trailing whitespace // Check for empty command after sanitization if (!sanitized) { throw new CommandValidationError( 'Invalid command: Empty after sanitization', ); } return sanitized; } private validate_working_dir(working_dir?: string): string | undefined { if (!working_dir) return undefined; // Sanitize and validate working directory const sanitized = working_dir .replace(/[;&|`$]/g, '') .replace(/\\/g, '/') .trim(); if (!sanitized) { throw new CommandValidationError('Invalid working directory'); } return sanitized; } private validate_timeout(timeout?: number): number | undefined { if (!timeout) return undefined; if (isNaN(timeout) || timeout < 0) { throw new CommandValidationError('Invalid timeout value'); } return timeout; } public is_dangerous_command(command: string): boolean { return dangerous_commands.some( (dangerous) => command.toLowerCase().includes(dangerous.toLowerCase()) || command.match(new RegExp(`\\b${dangerous}\\b`, 'i')), ); } public async execute_command( command: string, working_dir?: string, timeout?: number, ): Promise<CommandResponse> { return new Promise((resolve, reject) => { const sanitized_command = this.sanitize_command(command); const validated_dir = this.validate_working_dir(working_dir); const validated_timeout = this.validate_timeout(timeout); const cd_command = validated_dir ? `cd "${validated_dir}" && ` : ''; const full_command = `${cd_command}${sanitized_command}`; const wsl_process = spawn(wsl_config.executable, [ '--exec', wsl_config.shell, '-c', full_command, ]); let stdout = ''; let stderr = ''; wsl_process.stdout.on('data', (data) => { stdout += data.toString(); }); wsl_process.stderr.on('data', (data) => { stderr += data.toString(); }); let timeout_id: NodeJS.Timeout | undefined; if (validated_timeout) { timeout_id = setTimeout(() => { wsl_process.kill(); reject(new CommandTimeoutError(validated_timeout)); }, validated_timeout); } wsl_process.on('close', (code) => { if (timeout_id) { clearTimeout(timeout_id); } resolve({ stdout, stderr, exit_code: code, command: sanitized_command, working_dir: validated_dir, }); }); wsl_process.on('error', (error) => { if (timeout_id) { clearTimeout(timeout_id); } reject(error); }); }); } }