UNPKG

@sirmrmarty/n8n-nodes-tmux-orchestrator

Version:

n8n nodes for orchestrating Claude AI agents through tmux sessions

247 lines 9.23 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.secureGit = exports.secureTmux = exports.secureExec = exports.SecureCommandExecutor = void 0; const child_process_1 = require("child_process"); const path = __importStar(require("path")); const crypto_1 = require("crypto"); class SecureCommandExecutor { static validateCommand(command, args) { const baseCommand = path.basename(command); if (!this.ALLOWED_COMMANDS.has(baseCommand)) { throw new Error(`Command '${baseCommand}' not in allowed command list`); } const fullCommand = [command, ...args].join(' '); for (const pattern of this.DANGEROUS_PATTERNS) { if (pattern.test(fullCommand)) { throw new Error(`Command contains dangerous pattern: ${pattern.source}`); } } if (baseCommand === 'bash' || baseCommand === 'sh') { if (!args.includes('-c') || args.length < 2) { throw new Error('Shell commands must use -c flag with explicit command'); } } if (baseCommand === 'rm') { const dangerousPaths = ['/', '/usr', '/etc', '/var', '/home', '~']; const rmArgs = args.join(' '); if (args.includes('-r') || args.includes('-rf')) { for (const dangerousPath of dangerousPaths) { if (rmArgs.includes(dangerousPath)) { throw new Error(`Recursive deletion of ${dangerousPath} is prohibited`); } } } } } static sanitizeArgs(args) { return args.map(arg => { arg = arg.replace(/\0/g, ''); if (arg.length > 1000) { throw new Error('Argument too long (max 1000 characters)'); } return arg; }); } static async executeSecure(config) { const startTime = Date.now(); try { SecureCommandExecutor.validateCommand(config.command, config.args); const sanitizedArgs = SecureCommandExecutor.sanitizeArgs(config.args); const options = { cwd: config.cwd || process.cwd(), env: { ...process.env, ...(config.env || {}), LD_PRELOAD: undefined, PATH: process.env.PATH }, stdio: ['pipe', 'pipe', 'pipe'], timeout: config.timeout || SecureCommandExecutor.DEFAULT_TIMEOUT, killSignal: 'SIGTERM' }; const result = await SecureCommandExecutor.spawnCommand(config.command, sanitizedArgs, options, config.maxOutputSize); return { success: result.exitCode === 0, stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, duration: Date.now() - startTime, command: `${config.command} ${sanitizedArgs.join(' ')}` }; } catch (error) { return { success: false, stdout: '', stderr: error.message, exitCode: -1, duration: Date.now() - startTime, command: `${config.command} ${config.args.join(' ')}` }; } } static async spawnCommand(command, args, options, maxOutputSize) { return new Promise((resolve, reject) => { const maxSize = maxOutputSize || SecureCommandExecutor.DEFAULT_MAX_OUTPUT; let stdout = ''; let stderr = ''; let stdoutSize = 0; let stderrSize = 0; const child = (0, child_process_1.spawn)(command, args, options); const timeout = setTimeout(() => { child.kill('SIGTERM'); setTimeout(() => child.kill('SIGKILL'), 5000); reject(new Error(`Command timeout after ${options.timeout}ms`)); }, options.timeout); child.stdout?.on('data', (data) => { const chunk = data.toString(); stdoutSize += chunk.length; if (stdoutSize > maxSize) { child.kill('SIGTERM'); reject(new Error(`Command output exceeded ${maxSize} bytes`)); return; } stdout += chunk; }); child.stderr?.on('data', (data) => { const chunk = data.toString(); stderrSize += chunk.length; if (stderrSize > maxSize) { child.kill('SIGTERM'); reject(new Error(`Command error output exceeded ${maxSize} bytes`)); return; } stderr += chunk; }); child.on('close', (code) => { clearTimeout(timeout); resolve({ exitCode: code || 0, stdout: stdout.trim(), stderr: stderr.trim() }); }); child.on('error', (error) => { clearTimeout(timeout); reject(error); }); }); } static async executeTmux(action, args = [], options) { const tmuxArgs = [action, ...args]; const allowedTmuxActions = [ 'list-sessions', 'list-windows', 'list-panes', 'capture-pane', 'send-keys', 'display-message', 'new-session', 'new-window', 'rename-window', 'kill-session', 'kill-window' ]; if (!allowedTmuxActions.includes(action)) { throw new Error(`Tmux action '${action}' not allowed`); } return SecureCommandExecutor.executeSecure({ command: 'tmux', args: tmuxArgs, timeout: 10000, maxOutputSize: 512 * 1024, ...options }); } static async executeGit(action, args = [], options) { const gitArgs = [action, ...args]; const allowedGitActions = [ 'status', 'diff', 'log', 'add', 'commit', 'push', 'pull', 'checkout', 'branch', 'remote', 'config', 'clean' ]; if (!allowedGitActions.includes(action)) { throw new Error(`Git action '${action}' not allowed`); } if (action === 'clean' && args.includes('-fd')) { throw new Error('Forced directory cleanup not allowed'); } return SecureCommandExecutor.executeSecure({ command: 'git', args: gitArgs, timeout: 30000, ...options }); } static generateAuditHash(command, args, cwd) { const data = `${command}:${args.join(':')}:${cwd || process.cwd()}:${Date.now()}`; return (0, crypto_1.createHash)('sha256').update(data).digest('hex').substring(0, 16); } } exports.SecureCommandExecutor = SecureCommandExecutor; SecureCommandExecutor.DEFAULT_TIMEOUT = 30000; SecureCommandExecutor.DEFAULT_MAX_OUTPUT = 1024 * 1024; SecureCommandExecutor.ALLOWED_COMMANDS = new Set([ 'tmux', 'python3', 'python', 'node', 'npm', 'yarn', 'git', 'gh', 'bash', 'sh', 'sleep', 'echo', 'cat', 'grep', 'find', 'ls', 'pwd', 'whoami', 'chmod', 'mkdir', 'touch', 'rm' ]); SecureCommandExecutor.DANGEROUS_PATTERNS = [ /;\s*[|&]/, /\$\([^)]*\)/, /`[^`]*`/, /\${[^}]*}/, />[>]?\s*\/dev/, /\|\s*\w/, /&&|\|\|/, /;\s*\w/, /\\[`$()]/ ]; exports.secureExec = SecureCommandExecutor.executeSecure; exports.secureTmux = SecureCommandExecutor.executeTmux; exports.secureGit = SecureCommandExecutor.executeGit; //# sourceMappingURL=secureExecution.js.map