UNPKG

@eladtest/mcp

Version:

MCP server for shellfirm - provides interactive command validation with captcha

163 lines (162 loc) 8.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandInterceptor = void 0; const child_process_1 = require("child_process"); const util_1 = require("util"); const shellfirm_wasm_js_1 = require("./shellfirm-wasm.js"); const browser_challenge_js_1 = require("./browser-challenge.js"); const logger_js_1 = require("./logger.js"); const execAsync = (0, util_1.promisify)(child_process_1.exec); /** * Command interceptor that forces ALL commands through MCP validation */ class CommandInterceptor { /** * Intercept and validate command before execution */ static async interceptCommand(command, workingDirectory, challengeType = 'confirm', allowedSeverities, environment, propagateProcessEnv = true) { void (0, logger_js_1.log)('debug', 'interceptor', { message: 'Intercepting command', command }); try { // Use WASM-based validation with options for proper severity filtering const validationOptions = { allowed_severities: allowedSeverities || [], deny_pattern_ids: [], // Could be extended to support denied patterns }; const validationResult = await (0, shellfirm_wasm_js_1.validateSplitCommandWithOptions)(command, validationOptions); void (0, logger_js_1.log)('debug', 'interceptor', { message: 'WASM validation result', should_challenge: validationResult.should_challenge, should_deny: validationResult.should_deny, matches: validationResult.matches.map(match => match.id) }); if (!validationResult.should_challenge) { // Safe command - execute directly void (0, logger_js_1.log)('info', 'interceptor', { message: 'Command is safe, executing' }); return await this.executeCommand(command, workingDirectory, environment, propagateProcessEnv); } // Command denied completely if (validationResult.should_deny) { void (0, logger_js_1.log)('warning', 'interceptor', { message: 'Command denied by security policy' }); const descriptions = validationResult.matches.map(check => check.description).join(', '); return { allowed: false, message: `Shellfirm MCP: Command denied by security policy. Reasons: ${descriptions}`, error: 'Security policy violation' }; } // Risky command - require browser-based challenge verification const descriptions = validationResult.matches.map(check => check.description).join(', '); void (0, logger_js_1.log)('notice', 'interceptor', { message: 'Risky command detected', patterns: descriptions }); // Check if this is a Block challenge - if so, block immediately if (challengeType === 'block') { void (0, logger_js_1.log)('warning', 'interceptor', { message: 'Command blocked by security policy (Block challenge type)' }); return { allowed: false, message: `Shellfirm MCP: Command blocked by security policy. This command cannot be executed. Reasons: ${descriptions}`, error: 'Command blocked by security policy' }; } void (0, logger_js_1.log)('notice', 'interceptor', { message: 'Opening browser challenge for user verification' }); // Prepare challenge data const challengeData = { command, patterns: validationResult.matches.map(check => check.description), severity: this.getHighestSeverity(validationResult.matches), matches: validationResult.matches.map(check => ({ id: check.id, severity: check.severity, description: check.description })) }; try { // Show browser challenge const challengeResult = await browser_challenge_js_1.BrowserChallenge.showChallenge(challengeType, challengeData, 60000 // 60 second timeout ); if (challengeResult.approved) { void (0, logger_js_1.log)('info', 'interceptor', { message: 'User approved command through browser challenge' }); // User approved - execute the command return await this.executeCommand(command, workingDirectory, environment, propagateProcessEnv); } else { void (0, logger_js_1.log)('warning', 'interceptor', { message: 'User denied command or challenge failed' }); return { allowed: false, message: `Shellfirm MCP: Command denied by user. ${challengeResult.error || 'User chose not to approve the command.'}`, error: 'User denial or challenge failure' }; } } catch (challengeError) { void (0, logger_js_1.log)('error', 'interceptor', { message: 'Browser challenge system error', error: (0, logger_js_1.toErrorObject)(challengeError) }); return { allowed: false, message: `Shellfirm MCP: Challenge system error. Command blocked for security. Error: ${challengeError instanceof Error ? challengeError.message : 'Unknown error'}`, error: 'Challenge system failure' }; } } catch (error) { void (0, logger_js_1.log)('error', 'interceptor', { message: 'Error in command interception', error: (0, logger_js_1.toErrorObject)(error) }); return { allowed: false, message: `Command blocked due to error: ${error instanceof Error ? error.message : 'Unknown error'}`, error: 'Interception error' }; } } /** * Get the highest severity level from a list of matches */ static getHighestSeverity(matches) { const severityOrder = ['low', 'medium', 'high', 'critical']; let highestSeverity = 'medium'; for (const match of matches) { const severity = match.severity || 'medium'; if (severityOrder.indexOf(severity) > severityOrder.indexOf(highestSeverity)) { highestSeverity = severity; } } return highestSeverity; } /** * Execute command after validation */ static async executeCommand(command, workingDirectory, environment, propagateProcessEnv = true) { try { const options = {}; if (workingDirectory) { options.cwd = workingDirectory; } // Configure environment propagation behavior if (propagateProcessEnv) { // Merge clean process.env with provided environment (if any) const cleanProcessEnv = Object.fromEntries(Object.entries(process.env).filter(([_, value]) => value !== undefined)); options.env = { ...cleanProcessEnv, ...(environment || {}) }; } else { // Do not propagate current process env; use only provided env (or empty) options.env = { ...(environment || {}) }; } // Clean the command by removing any trailing whitespace/newlines const cleanCommand = command.trim(); const { stdout, stderr } = await execAsync(cleanCommand, options); return { allowed: true, output: stdout.toString(), error: stderr ? stderr.toString() : undefined, message: 'Command executed successfully' }; } catch (execError) { const errorMessage = execError instanceof Error ? execError.message : 'Unknown execution error'; void (0, logger_js_1.log)('error', 'interceptor', { message: 'Command execution failed', error: errorMessage }); return { allowed: true, // Command was allowed but failed execution output: '', error: errorMessage, message: 'Command was allowed but execution failed' }; } } } exports.CommandInterceptor = CommandInterceptor;