File size: 6,075 Bytes
64bc9a3 fe11cca fce80d1 fe11cca 64bc9a3 20765cf fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca dfd5fc7 fce80d1 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca fce80d1 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca fce80d1 20765cf fce80d1 20765cf fce80d1 20765cf fce80d1 20765cf fce80d1 fe11cca 64bc9a3 fe11cca 64bc9a3 fe11cca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | // Sandboxed shell tool β multi-OS (macOS, Linux, Windows).
// Detects platform and uses the appropriate shell.
// Only allowlisted commands run by default.
// Set MONA_ALLOW_CMDS to extend. Use MONA_SHELL_UNSAFE=1 to allow anything.
import { exec, spawn } from 'node:child_process';
import { promisify } from 'node:util';
import os from 'node:os';
import fs from 'node:fs';
import path from 'node:path';
const pexec = promisify(exec);
// ββ Platform detection ββββββββββββββββββββββββββββββββββββββββββββ
const PLATFORM = os.platform(); // 'darwin' | 'linux' | 'win32'
const SHELL_CONFIG = {
darwin: {
shell: '/bin/zsh',
path: '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin',
},
linux: {
shell: '/bin/sh',
path: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
},
win32: {
shell: 'powershell.exe',
path: '', // Windows uses system PATH
},
};
const cfg = SHELL_CONFIG[PLATFORM] || SHELL_CONFIG.linux;
// ββ OS-aware default allowlist ββββββββββββββββββββββββββββββββββββ
const DEFAULTS = {
darwin: 'df,uptime,uname,whoami,date,hostname,vm_stat,top,cat,head,tail,wc,ls,pwd,echo,env,which,sw_vers,sysctl',
linux: 'df,uptime,uname,whoami,date,hostname,free,ps,top,cat,head,tail,wc,ls,pwd,echo,env,which',
win32: 'whoami,date,hostname,dir,type,echo,ver,systeminfo,tasklist',
};
const ALLOW = new Set(
(process.env.MONA_ALLOW_CMDS || DEFAULTS[PLATFORM] || DEFAULTS.linux)
.split(',').map(s => s.trim()).filter(Boolean)
);
const UNSAFE = process.env.MONA_SHELL_UNSAFE === '1';
/** Shell security posture β advertised to the cloud in `hello` so the
* control plane can enforce agent_permissions without probing. */
export const security = {
allowlist: [...ALLOW].sort(),
unsafe: UNSAFE,
platform: PLATFORM,
};
// ββ Per-OS command mapping (translate common unix windows) ββββββ
const CMD_MAP_WIN32 = {
ls: 'dir',
cat: 'type',
pwd: 'echo %cd%',
uname: 'ver',
df: 'wmic logicaldisk get size,freespace,caption',
free: 'systeminfo | find "Available Physical Memory"',
uptime: 'systeminfo | find "System Boot Time"',
whoami: 'whoami',
clear: 'cls',
cp: 'copy',
mv: 'move',
rm: 'del',
};
// ββ Blocked patterns (always denied) ββββββββββββββββββββββββββββββ
const BLOCKED_PATTERNS = [
// Unix
/rm\s+(-[a-z]*[rf][a-z]*\s+)+\/s*$/i,
/rm\s+(-[a-z]*[rf][a-z]*\s+)+\*\s*$/i,
/mkfs\b/i,
/dd\s+if=/i,
/:\(\)\s*\{.*\}/,
/>\s*\/dev\/sd[a-z]/i,
/chmod\s+777\s+\//i,
/sudo\b/i,
/shutdown\b/i,
// Windows
/format\s+[a-z]:/i,
/del\s+\/f\s+\/s\s+[a-z]:\\/i,
/rmdir\s+\/s\s+[a-z]:\\/i,
/diskpart\b/i,
];
const EXEC_OPTS = {
timeout: 15_000,
maxBuffer: 1 << 20, // 1 MB
shell: cfg.shell,
env: {
...process.env,
PATH: cfg.path || process.env.PATH,
},
};
// ββ Tool definition βββββββββββββββββββββββββββββββββββββββββββββββ
export const shell = {
name: 'shell',
description: `Execute a shell command (${PLATFORM}; allowlisted by default; max 15s timeout; background:true for GUI/long-running processes)`,
args: { cmd: 'string β the command to run', background: 'bool β optional, detach and return immediately (for GUI apps, servers, tkinter windows)' },
platform: PLATFORM,
async run(args) {
let cmd = String(args.cmd || '').trim();
if (!cmd) return { error: 'Empty command' };
if (cmd.length > 2000) return { error: 'Command too long (max 2000 chars)' };
// Block dangerous patterns always
for (const pat of BLOCKED_PATTERNS) {
if (pat.test(cmd)) {
return { error: 'Command blocked for security', cmd };
}
}
// Allowlist check (unless unsafe mode)
if (!UNSAFE) {
const base = cmd.split(/[;\s|&]/)[0].trim().split('/').pop().split('\\').pop();
if (!ALLOW.has(base)) {
return {
error: `Command '${base}' not in allowlist`,
allowed: [...ALLOW].sort(),
platform: PLATFORM,
hint: 'Set MONA_ALLOW_CMDS to extend',
};
}
}
// Map unix commands to Windows equivalents when on win32
if (PLATFORM === 'win32' && CMD_MAP_WIN32[cmd.split(/\s+/)[0].toLowerCase()]) {
const [orig, ...rest] = cmd.split(/\s+/);
cmd = CMD_MAP_WIN32[orig.toLowerCase()] + (rest.length ? ' ' + rest.join(' ') : '');
}
try {
// Background mode: GUI apps / long-running processes (e.g. tkinter
// windows) must not block the task or die with the 15s timeout.
if (args.background) {
const logFile = path.join(os.homedir(), '.mona-agent', `bg-${Date.now()}.log`);
fs.mkdirSync(path.dirname(logFile), { recursive: true });
const out = fs.openSync(logFile, 'a');
const child = spawn(cfg.shell, ['-c', cmd], {
detached: true,
stdio: ['ignore', out, out],
env: { ...process.env, PATH: cfg.path || process.env.PATH },
});
child.unref();
fs.closeSync(out);
return {
exitCode: null,
pid: child.pid,
background: true,
log: logFile,
note: 'Process started in background and detached from the agent. Output: ' + logFile,
platform: PLATFORM,
};
}
const { stdout, stderr } = await pexec(cmd, EXEC_OPTS);
return {
exitCode: 0,
stdout: stdout.slice(0, 8000),
stderr: stderr.slice(0, 2000),
platform: PLATFORM,
};
} catch (err) {
return {
exitCode: err.code ?? 1,
stdout: (err.stdout || '').slice(0, 8000),
stderr: (err.stderr || '').slice(0, 2000),
error: err.killed ? 'Command timed out (15s)' : err.message,
platform: PLATFORM,
};
}
},
};
|