@mseep/mcp-wsl-exec
Version:
A secure Model Context Protocol (MCP) server for executing commands in Windows Subsystem for Linux (WSL) with built-in safety features and validation
93 lines (92 loc) • 3.58 kB
JavaScript
import { spawn } from 'child_process';
import { dangerous_commands, wsl_config } from './constants.js';
import { CommandValidationError, CommandTimeoutError } from './errors.js';
export class CommandExecutor {
sanitize_command(command) {
// 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;
}
validate_working_dir(working_dir) {
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;
}
validate_timeout(timeout) {
if (!timeout)
return undefined;
if (isNaN(timeout) || timeout < 0) {
throw new CommandValidationError('Invalid timeout value');
}
return timeout;
}
is_dangerous_command(command) {
return dangerous_commands.some((dangerous) => command.toLowerCase().includes(dangerous.toLowerCase()) ||
command.match(new RegExp(`\\b${dangerous}\\b`, 'i')));
}
async execute_command(command, working_dir, timeout) {
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;
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);
});
});
}
}