# modules/terminal_ops.py - 终端操作模块(修复Python命令检测) import os import sys import asyncio import subprocess import shutil import time import signal import re from pathlib import Path from typing import Dict, Optional, Tuple, TYPE_CHECKING from types import SimpleNamespace try: from config import ( TERMINAL_COMMAND_TIMEOUT, FORBIDDEN_COMMANDS, OUTPUT_FORMATS, MAX_RUN_COMMAND_CHARS, TOOLBOX_TERMINAL_IDLE_SECONDS, HOST_SANDBOX_NETWORK_PERMISSION, ) except ImportError: project_root = Path(__file__).resolve().parents[1] if str(project_root) not in sys.path: sys.path.insert(0, str(project_root)) from config import ( TERMINAL_COMMAND_TIMEOUT, FORBIDDEN_COMMANDS, OUTPUT_FORMATS, MAX_RUN_COMMAND_CHARS, TOOLBOX_TERMINAL_IDLE_SECONDS, HOST_SANDBOX_NETWORK_PERMISSION, ) from modules.toolbox_container import ToolboxContainer from modules.host_sandbox_runner import ( HostSandboxError, NETWORK_PERMISSION_RESTRICTED, build_host_sandbox_plan, build_host_sandbox_readonly_plan, host_sandbox_enabled, ) if TYPE_CHECKING: from modules.user_container_manager import ContainerHandle from modules.terminal_manager import TerminalManager class CommandMixin: """TerminalOperator command 能力 mixin。""" def _validate_command(self, command: str) -> Tuple[bool, str]: """验证命令安全性""" # 检查禁止的命令 for forbidden in FORBIDDEN_COMMANDS: if forbidden in command.lower(): return False, f"用户不允许执行包含“{forbidden}”的指令" # 检查危险的命令模式 dangerous_patterns = [ "sudo", "chmod 777", "rm -rf", "> /dev/", "fork bomb" ] for pattern in dangerous_patterns: if pattern in command.lower(): return False, f"用户不允许执行包含“{pattern}”的指令" return True, "" @staticmethod def _clamp_timeout(requested: Optional[int], default: int, max_limit: int) -> int: """对timeout进行默认化与上限夹紧。""" if not requested or requested <= 0: return default return min(int(requested), max_limit) def _resolve_work_path(self, working_dir: Optional[str]) -> Path: if working_dir: work_path = (self.project_path / working_dir).resolve() work_path.relative_to(self.project_path) return work_path return self.project_path def _format_toolbox_output(self, payload: Dict) -> Dict: success = bool(payload.get("success")) output_text = payload.get("output", "") or "" # 去掉常见的交互式shell警告 noisy_lines = ( "bash: cannot set terminal process group", "bash: no job control in this shell", ) filtered = [] for line in output_text.splitlines(): if any(noise in line for noise in noisy_lines): continue filtered.append(line) output_text = "\n".join(filtered) result = { "success": success, "output": output_text, "return_code": 0 if success else -1 } if not success: result["error"] = payload.get("error") or payload.get("message", "命令执行失败") if "message" in payload: result["message"] = payload["message"] if "status" in payload: result["status"] = payload["status"] if "truncated" in payload: result["truncated"] = payload["truncated"] return result