| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- import subprocess
- import shlex
- import os
- class TerminalTool:
- name = "terminal_exec"
- description = "执行终端命令查看目录、文件和系统信息(支持:pwd, ls, cat, echo, whoami, date等)"
- def __init__(self, security_mode="strict"):
- """
- 初始化终端工具
-
- Args:
- security_mode: "strict"(严格模式,直接拒绝) 或 "warning"(警告模式,给出提示)
- """
- self.security_mode = security_mode
-
- # 扩展的白名单命令列表(无参数或安全参数的命令)
- self.allowed_commands = {
- "ls": [], # ls 可以带参数如 -l, -a
- "pwd": [],
- "echo": ["*"], # echo 允许任何参数
- "whoami": [],
- "cat": ["*"], # cat 允许文件名参数
- "head": ["-n"], # head 允许 -n 参数
- "tail": ["-n"],
- "wc": ["-l", "-w"],
- "date": [],
- "uname": ["-a"],
- "find": ["."], # 限制搜索起点
- # 新增的常用安全命令
- "cd": [], # 目录切换
- "mkdir": ["-p"], # 创建目录
- "touch": [], # 创建文件
- "grep": ["-i", "-n", "-r"], # 文本搜索
- "which": [], # 查找命令位置
- "whereis": [], # 查找程序位置
- "du": ["-h", "-s"], # 磁盘使用情况
- "df": ["-h"], # 文件系统信息
- }
-
- # 危险关键词,用于额外安全检查
- self.dangerous_keywords = [
- "rm", "delete", "del", "format", "mkfs",
- "sudo", "su", "passwd", "chmod", "chown",
- "dd", "mkfs", "fdisk", ">", ">>", "|",
- ";", "&&", "||", "`", "$(", "eval"
- ]
- def get_parameters(self):
- return {
- "input": {
- "type": "str",
- "description": "输入终端命令,如:pwd, ls -la, cat filename.txt",
- "required": True,
- "examples": ["pwd", "ls -la", "cat README.md", "echo hello", "whoami", "date"]
- }
- }
- def _check_command_safety(self, cmd):
- """检查命令安全性
-
- Returns:
- tuple: (is_safe, error_msg, warning_msg)
- is_safe: bool - 是否安全
- error_msg: str - 错误消息
- warning_msg: str - 警告消息
- """
- # 检查危险关键词
- cmd_lower = cmd.lower()
- for keyword in self.dangerous_keywords:
- if keyword in cmd_lower:
- error_msg = f"检测到不安全的操作:{keyword}"
- warning_msg = f"⚠️ 警告:此命令包含 '{keyword}' 操作,可能导致系统损坏或数据丢失!"
- return False, error_msg, warning_msg
-
- # 检查是否包含管道、重定向等操作
- operators = ["|", ">", "<", "&", "&&", "||", ";"]
- for op in operators:
- if op in cmd:
- error_msg = f"检测到不安全的操作符:{op}"
- warning_msg = f"⚠️ 警告:此命令包含 '{op}' 操作符,可能导致意外行为!"
- return False, error_msg, warning_msg
-
- return True, None, None
- def run(self, parameters):
- # 确保参数处理的安全性
- if isinstance(parameters, dict):
- # 统一使用 {"input": command} 格式
- cmd = parameters.get("input", "")
- else:
- cmd = str(parameters) if parameters else ""
- cmd = cmd.strip() if cmd else ""
-
- if not cmd:
- return "错误: 命令不能为空"
-
- # 安全检查
- is_safe, error_msg, warning_msg = self._check_command_safety(cmd)
- if not is_safe:
- if self.security_mode == "strict":
- return f"🚫 安全拒绝: {error_msg}"
- else: # warning mode
- return f"{warning_msg}\n\n命令: {cmd}\n\n如需继续执行,请确认操作的安全性。\n(当前为警告模式,尚未真正执行)"
-
- # 分割命令和参数
- parts = shlex.split(cmd)
- if not parts:
- return "错误: 无效的命令"
-
- command_name = parts[0]
- args = parts[1:] if len(parts) > 1 else []
-
- # 检查命令是否在白名单中
- if command_name not in self.allowed_commands:
- allowed_list = ", ".join(sorted(self.allowed_commands.keys()))
- similar_commands = self._find_similar_commands(command_name)
- error_msg = f"🚫 命令 '{command_name}' 不在允许列表中。"
- error_msg += f"\n\n✅ 允许的命令: {allowed_list}"
- if similar_commands:
- error_msg += f"\n💡 您是否想使用: {', '.join(similar_commands)}?"
- error_msg += f"\n\n📖 输入 'help' 或 '?' 查看命令帮助"
- return error_msg
-
- # 检查参数
- allowed_args = self.allowed_commands[command_name]
-
- # 改进的参数验证逻辑
- if "*" not in allowed_args and args:
- validation_result = self._validate_parameters(command_name, args)
- if not validation_result[0]: # 验证失败
- return validation_result[1]
-
- # 如果允许任何参数,进行基本安全检查
- elif "*" in allowed_args and args:
- validation_result = self._validate_wildcard_args(command_name, args)
- if not validation_result[0]: # 验证失败
- return validation_result[1]
-
- # 执行命令(使用 shell=False 提高安全性)
- try:
- # 使用 shlex.split 可以正确处理带引号的参数
- result = subprocess.run(
- cmd,
- shell=True, # 保持向后兼容,但需要更严格的白名单
- capture_output=True,
- text=True,
- timeout=15,
- cwd=None # 限制在安全目录执行
- )
-
- # 组合标准输出和标准错误
- output = result.stdout
- if result.stderr:
- output += f"\n[标准错误]\n{result.stderr}"
-
- # 返回执行结果
- if result.returncode == 0:
- return output.strip() if output.strip() else "命令执行成功(无输出)"
- else:
- return f"命令执行失败 (返回码: {result.returncode})\n{output.strip()}"
-
- except subprocess.TimeoutExpired:
- return "命令执行超时(超过15秒)。"
- except subprocess.CalledProcessError as e:
- error_output = e.stderr.decode() if isinstance(e.stderr, bytes) else e.stderr
- return f"命令执行错误: {error_output or str(e)}"
- except Exception as e:
- return f"执行异常: {str(e)}"
- def _validate_parameters(self, command_name, args):
- """验证特定命令的参数
-
- Args:
- command_name: 命令名称
- args: 参数列表
-
- Returns:
- tuple: (is_valid, error_message)
- """
- allowed_args = self.allowed_commands[command_name]
-
- # 验证选项参数
- option_args = [arg for arg in args if arg.startswith("-")]
- for arg in option_args:
- if arg not in allowed_args and arg != "-p": # -p 是特殊的,允许mkdir使用
- help_text = self._get_command_help(command_name)
- return False, f"参数 '{arg}' 不被允许。\n{help_text}"
-
- # 验证非选项参数(通常是文件路径)
- file_args = [arg for arg in args if not arg.startswith("-")]
- for arg in file_args:
- if self._is_dangerous_path(arg):
- return False, f"危险路径: {arg}\n只允许访问当前目录及其子目录"
-
- return True, None
- def _validate_wildcard_args(self, command_name, args):
- """验证通配符参数(适用于cat、echo等)
-
- Args:
- command_name: 命令名称
- args: 参数列表
-
- Returns:
- tuple: (is_valid, error_message)
- """
- # 对于文件操作命令,进行路径安全检查
- if command_name in ["cat", "head", "tail", "grep"]:
- for arg in args:
- if not arg.startswith("-") and self._is_dangerous_path(arg):
- return False, f"危险路径: {arg}\n只允许访问当前目录及其子目录"
-
- return True, None
- def _is_dangerous_path(self, path):
- """检查路径是否危险
-
- Args:
- path: 要检查的路径
-
- Returns:
- bool: 是否为危险路径
- """
- # 检查绝对路径
- if os.path.isabs(path):
- return True
-
- # 检查包含危险字符的路径
- dangerous_patterns = ["../", "..\\", "~/", "/etc", "/bin", "/usr", "/var", "/sys"]
- for pattern in dangerous_patterns:
- if pattern in path:
- return True
-
- return False
- def _get_command_help(self, command_name):
- """返回命令的使用帮助
-
- Args:
- command_name: 命令名称
-
- Returns:
- str: 帮助信息
- """
- help_text = {
- "pwd": "用法: pwd\n功能: 显示当前工作目录",
- "ls": "用法: ls [-la] [路径]\n功能: 列出目录内容\n选项: -l(详细信息), -a(显示隐藏文件)",
- "cat": "用法: cat <文件名>\n功能: 显示文件内容",
- "head": "用法: head [-n 行数] <文件>\n功能: 显示文件开头内容",
- "tail": "用法: tail [-n 行数] <文件>\n功能: 显示文件末尾内容",
- "wc": "用法: wc [-l|-w] <文件>\n功能: 统计文件行数、字数\n选项: -l(行数), -w(字数)",
- "echo": "用法: echo <文本>\n功能: 输出文本",
- "whoami": "用法: whoami\n功能: 显示当前用户名",
- "date": "用法: date\n功能: 显示当前日期时间",
- "uname": "用法: uname [-a]\n功能: 显示系统信息\n选项: -a(所有信息)",
- "find": "用法: find . [选项]\n功能: 查找文件\n注意: 只能在当前目录搜索",
- "cd": "用法: cd <目录>\n功能: 切换到指定目录",
- "mkdir": "用法: mkdir [-p] <目录名>\n功能: 创建目录\n选项: -p(递归创建)",
- "touch": "用法: touch <文件名>\n功能: 创建空文件",
- "grep": "用法: grep [-inr] '模式' <文件>\n功能: 搜索文本\n选项: -i(忽略大小写), -n(显示行号), -r(递归)",
- "which": "用法: which <命令>\n功能: 查找命令位置",
- "whereis": "用法: whereis <程序>\n功能: 查找程序位置",
- "du": "用法: du [-hs] [路径]\n功能: 显示磁盘使用情况\n选项: -h(人类可读), -s(总计)",
- "df": "用法: df [-h]\n功能: 显示文件系统信息\n选项: -h(人类可读)"
- }
- return help_text.get(command_name, f"命令 '{command_name}' 暂无帮助信息")
- def _find_similar_commands(self, command_name):
- """查找相似的命令名称
-
- Args:
- command_name: 输入的命令名称
-
- Returns:
- list: 相似命令列表
- """
- import difflib
-
- # 获取所有允许的命令
- allowed_commands = list(self.allowed_commands.keys())
-
- # 使用difflib查找相似命令
- similar = difflib.get_close_matches(command_name, allowed_commands, n=3, cutoff=0.6)
-
- return similar
|