Files
Anton 052591281d Initial clean version - Agent Maxim
- Complete agent system with planning and execution
- Bridge API with FastAPI server
- WhatsApp-style mobile dashboard
- Enhanced web search with query improvement
- Security guards and policy checks
- Comprehensive documentation
2026-04-09 18:07:43 +03:00

89 lines
2.8 KiB
Python

"""Safe shell execution tool with allowlist enforcement."""
from __future__ import annotations
import subprocess
from core.state import PlanStep, StepResult, StepStatus
# Commands explicitly allowed
ALLOWED_COMMANDS: frozenset[str] = frozenset({"pwd", "ls", "echo", "cat", "python", "git"})
# Commands explicitly blocked (belt-and-suspenders)
BLOCKED_COMMANDS: frozenset[str] = frozenset({
"bash", "sh", "rm", "sudo", "curl", "wget",
"chmod", "chown", "docker", "systemctl",
})
# For commands with restricted sub-commands
RESTRICTED_SUBCOMMANDS: dict[str, set[str]] = {
"python": {"--version"},
"git": {"status"},
}
def run_shell(step: PlanStep) -> StepResult:
"""Execute a whitelisted shell command via subprocess (shell=False)."""
raw_command: str = step.payload.get("command", "").strip()
parts = raw_command.split()
if not parts:
return StepResult(step_id=step.step_id, status=StepStatus.FAILED, error="Empty command")
binary = parts[0]
if binary in BLOCKED_COMMANDS:
return StepResult(
step_id=step.step_id,
status=StepStatus.FAILED,
error=f"Command '{binary}' is blocked by security policy",
)
if binary not in ALLOWED_COMMANDS:
return StepResult(
step_id=step.step_id,
status=StepStatus.FAILED,
error=f"Command '{binary}' is not in allowlist. Allowed: {sorted(ALLOWED_COMMANDS)}",
)
# Validate restricted sub-commands
if binary in RESTRICTED_SUBCOMMANDS:
allowed_sub = RESTRICTED_SUBCOMMANDS[binary]
sub = " ".join(parts[1:])
if sub not in allowed_sub:
return StepResult(
step_id=step.step_id,
status=StepStatus.FAILED,
error=f"Only '{binary} {sorted(allowed_sub)}' is permitted, got: '{binary} {sub}'",
)
try:
proc = subprocess.run(
parts,
shell=False,
capture_output=True,
text=True,
timeout=10,
)
output = proc.stdout + proc.stderr
if proc.returncode != 0:
return StepResult(
step_id=step.step_id,
status=StepStatus.FAILED,
output_text=output,
error=f"Non-zero exit code: {proc.returncode}",
)
return StepResult(step_id=step.step_id, status=StepStatus.DONE, output_text=output)
except subprocess.TimeoutExpired:
return StepResult(
step_id=step.step_id,
status=StepStatus.FAILED,
error="Command timed out after 10s",
)
except FileNotFoundError:
return StepResult(
step_id=step.step_id,
status=StepStatus.FAILED,
error=f"Binary '{binary}' not found on PATH",
)