UNPKG

ssh-bridge-ai

Version:

One Command Magic SSH with Invisible Analytics - Connect to any server instantly with 'sshbridge user@server'. Zero setup, zero friction, pure magic. Industry-standard security with behind-the-scenes business intelligence.

488 lines (406 loc) • 14.8 kB
const SandboxManager = require('./sandbox-manager'); const logger = require('../utils/logger'); const chalk = require('chalk'); /** * Sandbox Executor - Manages the workflow of testing commands in sandbox * before executing them on real servers, similar to Cursor's approach */ class SandboxExecutor { constructor(options = {}) { this.sandboxManager = new SandboxManager(options); this.alwaysSandbox = options.alwaysSandbox !== false; // Default to true this.dryRun = options.dryRun || false; this.sandboxResults = new Map(); // Bind event handlers this.sandboxManager.on('sandbox:execute', this.onSandboxExecute.bind(this)); } /** * Execute a command with sandbox safety and user confirmation */ async executeWithSandbox(command, options = {}) { const commandId = this.generateCommandId(); const startTime = Date.now(); logger.info(`šŸ”’ Testing command in sandbox: ${chalk.cyan(command)}`); try { // Step 1: Create sandbox environment const sandbox = await this.sandboxManager.createSandbox(); // Step 2: Test command in sandbox const sandboxResult = await this.sandboxManager.executeInSandbox(command, sandbox); // Step 3: Validate command safety const safetyCheck = this.sandboxManager.isCommandSafe(command, sandboxResult); // Store sandbox results this.sandboxResults.set(commandId, { command, sandboxResult, safetyCheck, timestamp: startTime, duration: Date.now() - startTime }); // Step 4: Handle safety validation if (!safetyCheck.safe) { logger.error(`🚨 Command blocked: ${safetyCheck.reason}`); if (safetyCheck.sandboxOutput) { logger.error(`Sandbox output: ${safetyCheck.sandboxOutput}`); } await this.sandboxManager.cleanupSandbox(sandbox); return { success: false, blocked: true, reason: safetyCheck.reason, sandboxResult, commandId }; } // Step 5: Check if this is a dry run if (this.dryRun || options.dryRun) { logger.info(`šŸ” Dry run mode - command would execute: ${chalk.green(command)}`); logger.info(`Sandbox result: ${chalk.gray(sandboxResult.stdout)}`); await this.sandboxManager.cleanupSandbox(sandbox); return { success: true, dryRun: true, sandboxResult, commandId, message: 'Command validated in sandbox (dry run mode)' }; } // Step 6: Show preview and ask for confirmation (Cursor-like workflow) if (options.target && options.executeOnTarget) { const preview = this.generateCommandPreview(command, sandboxResult, safetyCheck); // Always require confirmation unless explicitly auto-approved if (!options.autoApprove) { const confirmed = await this.requestUserConfirmation(preview); if (!confirmed) { logger.info(`āŒ Command cancelled by user`); await this.sandboxManager.cleanupSandbox(sandbox); return { success: false, cancelled: true, sandboxResult, commandId, message: 'Command cancelled by user' }; } } // Step 7: Execute on real target after confirmation logger.info(`šŸš€ User confirmed, executing on target: ${chalk.cyan(command)}`); try { const realResult = await this.executeOnTarget(command, options.target); await this.sandboxManager.cleanupSandbox(sandbox); return { success: true, sandboxResult, realResult, commandId, message: 'Command executed successfully on target' }; } catch (error) { logger.error(`āŒ Target execution failed: ${error.message}`); await this.sandboxManager.cleanupSandbox(sandbox); return { success: false, sandboxResult, error: error.message, commandId, message: 'Command passed sandbox but failed on target' }; } } // Step 8: Clean up and return sandbox results await this.sandboxManager.cleanupSandbox(sandbox); logger.info(`āœ… Command validated in sandbox: ${chalk.green(command)}`); return { success: true, sandboxResult, commandId, message: 'Command validated in sandbox' }; } catch (error) { logger.error(`āŒ Sandbox execution failed: ${error.message}`); return { success: false, error: error.message, commandId, message: 'Sandbox execution failed' }; } } /** * Execute command on real target (SSH server, etc.) */ async executeOnTarget(command, target) { // This would integrate with your existing SSH system // For now, we'll return a placeholder logger.info(`šŸŽÆ Executing on target: ${target.type || 'unknown'}`); if (target.ssh) { // Use existing SSH connection return await target.ssh.execCommand(command); } else if (target.execute) { // Use custom execute function return await target.execute(command); } else { throw new Error('No execution method provided for target'); } } /** * Batch execute multiple commands with sandbox safety */ async executeBatch(commands, options = {}) { const results = []; const sandbox = await this.sandboxManager.createSandbox(); try { for (const command of commands) { logger.info(`šŸ”’ Testing command in sandbox: ${chalk.cyan(command)}`); const sandboxResult = await this.sandboxManager.executeInSandbox(command, sandbox); const safetyCheck = this.sandboxManager.isCommandSafe(command, sandboxResult); results.push({ command, sandboxResult, safetyCheck, timestamp: Date.now() }); // Stop if any command is unsafe if (!safetyCheck.safe) { logger.error(`🚨 Batch stopped: ${safetyCheck.reason}`); break; } } // If all commands are safe and not dry run, execute on target if (options.target && !this.dryRun && results.every(r => r.safetyCheck.safe)) { logger.info(`šŸš€ Executing ${results.length} validated commands on target`); for (const result of results) { const realResult = await this.executeOnTarget(result.command, options.target); result.realResult = realResult; } } } finally { await this.sandboxManager.cleanupSandbox(sandbox); } return results; } /** * Get sandbox statistics */ getStats() { const totalCommands = this.sandboxResults.size; const blockedCommands = Array.from(this.sandboxResults.values()) .filter(r => r.safetyCheck && !r.safetyCheck.safe).length; const successfulCommands = totalCommands - blockedCommands; return { totalCommands, blockedCommands, successfulCommands, blockRate: totalCommands > 0 ? (blockedCommands / totalCommands * 100).toFixed(2) : 0, sandboxStatus: this.sandboxManager.getStatus() }; } /** * Get detailed results for a specific command */ getCommandResult(commandId) { return this.sandboxResults.get(commandId); } /** * Clear sandbox results history */ clearHistory() { this.sandboxResults.clear(); logger.info('šŸ—‘ļø Sandbox execution history cleared'); } /** * Generate unique command ID */ generateCommandId() { return `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Event handler for sandbox execution */ onSandboxExecute(data) { logger.debug(`šŸ”’ Sandbox execution: ${data.command.substring(0, 50)}...`); } /** * Enable/disable sandbox mode */ setSandboxMode(enabled) { this.alwaysSandbox = enabled; logger.info(`šŸ”’ Sandbox mode ${enabled ? 'enabled' : 'disabled'}`); } /** * Set dry run mode */ setDryRunMode(enabled) { this.dryRun = enabled; logger.info(`šŸ” Dry run mode ${enabled ? 'enabled' : 'disabled'}`); } /** * Generate a comprehensive command preview (Cursor-like) */ generateCommandPreview(command, sandboxResult, safetyCheck) { const preview = { command: command, sandboxOutput: sandboxResult.stdout || '', sandboxError: sandboxResult.stderr || '', exitCode: sandboxResult.exitCode, duration: sandboxResult.duration || 0, safetyLevel: safetyCheck.safe ? 'SAFE' : 'BLOCKED', warnings: safetyCheck.warnings || [], impact: this.assessCommandImpact(command, sandboxResult), summary: this.generateCommandSummary(command, sandboxResult) }; return preview; } /** * Assess the potential impact of a command */ assessCommandImpact(command, sandboxResult) { const impact = { risk: 'LOW', scope: 'LOCAL', reversibility: 'REVERSIBLE', systemAffected: false }; // Analyze command for potential impact if (command.includes('rm -rf') || command.includes('rm -r')) { impact.risk = 'HIGH'; impact.scope = 'FILESYSTEM'; impact.reversibility = 'IRREVERSIBLE'; } if (command.includes('chmod') || command.includes('chown')) { impact.risk = 'MEDIUM'; impact.scope = 'PERMISSIONS'; } if (command.includes('systemctl') || command.includes('service')) { impact.risk = 'MEDIUM'; impact.scope = 'SYSTEM'; impact.systemAffected = true; } if (command.includes('reboot') || command.includes('shutdown')) { impact.risk = 'CRITICAL'; impact.scope = 'SYSTEM'; impact.systemAffected = true; impact.reversibility = 'IRREVERSIBLE'; } return impact; } /** * Generate a human-readable command summary */ generateCommandSummary(command, sandboxResult) { const parts = command.split(' '); const mainCommand = parts[0]; let summary = `Execute ${mainCommand}`; if (parts.length > 1) { const args = parts.slice(1).join(' '); summary += ` with arguments: ${args}`; } if (sandboxResult.stdout) { summary += `\nExpected output: ${sandboxResult.stdout.substring(0, 100)}${sandboxResult.stdout.length > 100 ? '...' : ''}`; } return summary; } /** * Request user confirmation for command execution (Cursor-like) */ async requestUserConfirmation(preview) { const inquirer = require('inquirer'); console.log('\n' + '='.repeat(80)); console.log('šŸ”’ COMMAND PREVIEW & CONFIRMATION REQUIRED'); console.log('='.repeat(80)); // Display command details console.log(`\nšŸ“ Command: ${chalk.cyan(preview.command)}`); console.log(`šŸŽÆ Target: SSH Server`); console.log(`ā±ļø Sandbox Duration: ${preview.duration}ms`); console.log(`šŸ” Exit Code: ${preview.exitCode}`); // Display impact assessment console.log(`\nāš ļø Impact Assessment:`); console.log(` Risk Level: ${this.getRiskColor(preview.impact.risk)}`); console.log(` Scope: ${chalk.yellow(preview.impact.scope)}`); console.log(` Reversibility: ${chalk.yellow(preview.impact.reversibility)}`); console.log(` System Affected: ${preview.impact.systemAffected ? chalk.red('YES') : chalk.green('NO')}`); // Display sandbox output preview if (preview.sandboxOutput) { console.log(`\nšŸ“¤ Expected Output:`); console.log(chalk.gray(preview.sandboxOutput.substring(0, 200))); if (preview.sandboxOutput.length > 200) { console.log(chalk.gray('... (truncated)')); } } if (preview.sandboxError) { console.log(`\nāš ļø Sandbox Warnings:`); console.log(chalk.yellow(preview.sandboxError.substring(0, 200))); if (preview.sandboxError.length > 200) { console.log(chalk.yellow('... (truncated)')); } } // Display command summary console.log(`\nšŸ“‹ Summary: ${preview.summary}`); console.log('\n' + '='.repeat(80)); // Ask for confirmation const answers = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'šŸš€ Do you want to execute this command on the real server?', default: false }, { type: 'list', name: 'action', message: 'What would you like to do?', choices: [ { name: 'āœ… Execute command', value: 'execute' }, { name: 'šŸ” Modify command', value: 'modify' }, { name: 'āŒ Cancel execution', value: 'cancel' }, { name: 'šŸ“‹ Show full sandbox output', value: 'show_output' } ], when: (answers) => answers.confirm } ]); if (answers.action === 'show_output') { console.log('\nšŸ“‹ Full Sandbox Output:'); console.log('='.repeat(80)); console.log(preview.sandboxOutput || 'No output'); console.log('='.repeat(80)); // Ask again after showing output const finalAnswer = await inquirer.prompt([ { type: 'confirm', name: 'finalConfirm', message: 'šŸš€ Now do you want to execute this command?', default: false } ]); return finalAnswer.finalConfirm; } if (answers.action === 'modify') { const modifiedCommand = await inquirer.prompt([ { type: 'input', name: 'newCommand', message: 'Enter the modified command:', default: preview.command } ]); console.log(`\nšŸ”„ Command modified to: ${chalk.cyan(modifiedCommand.newCommand)}`); console.log('šŸ”„ Re-testing modified command in sandbox...'); // This would trigger a re-test of the modified command // For now, we'll just return false to indicate the original command shouldn't execute return false; } return answers.confirm; } /** * Get colored risk level display */ getRiskColor(risk) { switch (risk) { case 'LOW': return chalk.green('LOW'); case 'MEDIUM': return chalk.yellow('MEDIUM'); case 'HIGH': return chalk.red('HIGH'); case 'CRITICAL': return chalk.red.bold('CRITICAL'); default: return chalk.gray(risk); } } } module.exports = SandboxExecutor;