60 lines
1.8 KiB
Python
60 lines
1.8 KiB
Python
"""Shell command execution tool with safety checks."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
|
|
from . import tool
|
|
|
|
# Commands that are always blocked
|
|
BLOCKED_PATTERNS = [
|
|
"rm -rf /",
|
|
"format c:",
|
|
":(){:|:&};:",
|
|
"dd if=/dev/zero",
|
|
"mkfs.",
|
|
"> /dev/sda",
|
|
]
|
|
|
|
|
|
@tool("run_command", "Execute a shell command and return output", category="shell")
|
|
def run_command(command: str, timeout: int = 30, ctx: dict | None = None) -> str:
|
|
# Check require_approval setting
|
|
if ctx and ctx.get("config") and ctx["config"].shell.require_approval:
|
|
return (
|
|
"Shell commands require approval. Use the `delegate_task` tool instead — "
|
|
"it routes through the execution brain which has its own safety controls."
|
|
)
|
|
|
|
# Safety check
|
|
cmd_lower = command.lower().strip()
|
|
for pattern in BLOCKED_PATTERNS:
|
|
if pattern in cmd_lower:
|
|
return f"Blocked: command matches dangerous pattern '{pattern}'"
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
command,
|
|
shell=True,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=min(timeout, 120),
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
)
|
|
output = ""
|
|
if result.stdout:
|
|
output += result.stdout
|
|
if result.stderr:
|
|
output += f"\n[stderr]\n{result.stderr}"
|
|
if result.returncode != 0:
|
|
output += f"\n[exit code: {result.returncode}]"
|
|
|
|
if len(output) > 10000:
|
|
output = output[:10000] + "\n... (truncated)"
|
|
return output.strip() or "(no output)"
|
|
except subprocess.TimeoutExpired:
|
|
return f"Command timed out after {timeout}s"
|
|
except Exception as e:
|
|
return f"Error running command: {e}"
|