code.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. #!/usr/bin/env python3
  2. """
  3. s03_permission.py - 权限系统
  4. 在工具执行前插入三道关卡:
  5. 关卡 1:硬性拒绝列表(rm -rf /、sudo 等)
  6. 关卡 2:规则匹配(是否写到工作区外?是否是破坏性命令?)
  7. 关卡 3:用户审批(暂停并等待确认)
  8. +-------+ +--------+ +--------+ +--------+ +------+
  9. | Tool | -> | Gate 1 | -> | Gate 2 | -> | Gate 3 | -> | Exec |
  10. | call | | deny? | | match? | | allow? | | |
  11. +-------+ +--------+ +--------+ +--------+ +------+
  12. | | | |
  13. v v v v
  14. (正常) (拦截) (询问用户) (用户拒绝?)
  15. Agent 循环里只新增一行:
  16. if not check_permission(block):
  17. continue
  18. 基于 s02(多工具)构建。用法:
  19. python s03_permission/code.py
  20. 需要: pip install anthropic python-dotenv + .env 中配置 ANTHROPIC_API_KEY
  21. """
  22. import os, subprocess
  23. from pathlib import Path
  24. try:
  25. import readline
  26. readline.parse_and_bind('set bind-tty-special-chars off')
  27. readline.parse_and_bind('set input-meta on')
  28. readline.parse_and_bind('set output-meta on')
  29. readline.parse_and_bind('set convert-meta off')
  30. except ImportError:
  31. pass
  32. from anthropic import Anthropic
  33. from dotenv import load_dotenv
  34. load_dotenv(override=True)
  35. if os.getenv("ANTHROPIC_BASE_URL"):
  36. os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
  37. WORKDIR = Path.cwd()
  38. client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
  39. MODEL = os.environ["MODEL_ID"]
  40. SYSTEM = f"你是位于 {WORKDIR}. 所有破坏性操作都需要用户审批。"
  41. # ═══════════════════════════════════════════════════════════
  42. # 来自 s02 : 工具实现
  43. # ═══════════════════════════════════════════════════════════
  44. def run_bash(command: str) -> str:
  45. try:
  46. r = subprocess.run(command, shell=True, cwd=WORKDIR,
  47. capture_output=True, text=True, timeout=120)
  48. out = (r.stdout + r.stderr).strip()
  49. return out[:50000] if out else "(无输出)"
  50. except subprocess.TimeoutExpired:
  51. return "错误:执行超时(120 秒)"
  52. def run_read(path: str, limit: int | None = None) -> str:
  53. try:
  54. lines = (WORKDIR / path).resolve().read_text().splitlines()
  55. if limit and limit < len(lines):
  56. lines = lines[:limit] + [f"... ({len(lines) - limit} 行更多内容)"]
  57. return "\n".join(lines)
  58. except Exception as e:
  59. return f"错误:{e}"
  60. def run_write(path: str, content: str) -> str:
  61. try:
  62. file_path = (WORKDIR / path).resolve()
  63. file_path.parent.mkdir(parents=True, exist_ok=True)
  64. file_path.write_text(content)
  65. return f"已写入 {len(content)} 字节到 {path}"
  66. except Exception as e:
  67. return f"错误:{e}"
  68. def run_edit(path: str, old_text: str, new_text: str) -> str:
  69. try:
  70. file_path = (WORKDIR / path).resolve()
  71. text = file_path.read_text()
  72. if old_text not in text:
  73. return f"错误:在文件中未找到目标文本:{path}"
  74. file_path.write_text(text.replace(old_text, new_text, 1))
  75. return f"已编辑 {path}"
  76. except Exception as e:
  77. return f"错误:{e}"
  78. def run_glob(pattern: str) -> str:
  79. import glob as g
  80. try:
  81. results = []
  82. for match in g.glob(pattern, root_dir=WORKDIR):
  83. if (WORKDIR / match).resolve().is_relative_to(WORKDIR):
  84. results.append(match)
  85. return "\n".join(results) if results else "(无匹配)"
  86. except Exception as e:
  87. return f"错误:{e}"
  88. # ═══════════════════════════════════════════════════════════
  89. # 来自 s02 (未改动): 工具定义与分发
  90. # ═══════════════════════════════════════════════════════════
  91. TOOLS = [
  92. {"name": "bash", "description": "运行一条 shell 命令。",
  93. "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
  94. {"name": "read_file", "description": "读取文件内容。",
  95. "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
  96. {"name": "write_file", "description": "向文件写入内容。",
  97. "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
  98. {"name": "edit_file", "description": "在文件中替换一次完全匹配的文本。",
  99. "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
  100. {"name": "glob", "description": "查找匹配 glob 模式的文件。",
  101. "input_schema": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}},
  102. ]
  103. TOOL_HANDLERS = {
  104. "bash": run_bash, "read_file": run_read, "write_file": run_write,
  105. "edit_file": run_edit, "glob": run_glob,
  106. }
  107. # ═══════════════════════════════════════════════════════════
  108. # 新增于 s03: 三关卡权限流水线
  109. # ═══════════════════════════════════════════════════════════
  110. # 关卡 1:硬性拒绝列表 — 始终禁止
  111. DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if=", "> /dev/sda"]
  112. def check_deny_list(command: str) -> str | None:
  113. for pattern in DENY_LIST:
  114. if pattern in command:
  115. return f"已拦截:'{pattern}' 位于拒绝列表中"
  116. return None
  117. # 关卡 2:规则匹配 — 根据上下文检查
  118. PERMISSION_RULES = [
  119. {"tools": ["read_file", "write_file", "edit_file"],
  120. "check": lambda args: not (WORKDIR / args.get("path", "")).resolve().is_relative_to(WORKDIR),
  121. "message": "写入工作区外部路径"},
  122. {"tools": ["bash"],
  123. "check": lambda args: any(kw in args.get("command", "") for kw in ["rm ", "> /etc/", "chmod 777"]),
  124. "message": "可能具有破坏性的命令"},
  125. ]
  126. def check_rules(tool_name: str, args: dict) -> str | None:
  127. for rule in PERMISSION_RULES:
  128. if tool_name in rule["tools"] and rule["check"](args):
  129. return rule["message"]
  130. return None
  131. # 关卡 3:用户审批 — 规则命中后等待确认
  132. def ask_user(tool_name: str, args: dict, reason: str) -> str:
  133. print(f"\n\033[33m⚠ {reason}\033[0m")
  134. print(f" 工具:{tool_name}({args})")
  135. choice = input(" 是否允许?[y/N] ").strip().lower()
  136. return "allow" if choice in ("y", "yes") else "deny"
  137. # 流水线:三道关卡串联
  138. def check_permission(block) -> bool:
  139. if block.name == "bash":
  140. reason = check_deny_list(block.input.get("command", ""))
  141. if reason:
  142. print(f"\n\033[31m⛔ {reason}\033[0m")
  143. return False
  144. reason = check_rules(block.name, block.input)
  145. if reason:
  146. decision = ask_user(block.name, block.input, reason)
  147. if decision == "deny":
  148. return False
  149. return True
  150. # ═══════════════════════════════════════════════════════════
  151. # agent_loop — 与以下相同: s02, 插入 check_permission()
  152. # ═══════════════════════════════════════════════════════════
  153. def agent_loop(messages: list):
  154. while True:
  155. response = client.messages.create(
  156. model=MODEL, system=SYSTEM, messages=messages,
  157. tools=TOOLS, max_tokens=8000,
  158. )
  159. messages.append({"role": "assistant", "content": response.content})
  160. if response.stop_reason != "tool_use":
  161. return
  162. results = []
  163. for block in response.content:
  164. if block.type != "tool_use":
  165. continue
  166. print(f"\033[36m> {block.name}\033[0m")
  167. # s03 变化:执行前先经过权限流水线
  168. if not check_permission(block):
  169. results.append({"type": "tool_result", "tool_use_id": block.id,
  170. "content": "权限被拒绝."})
  171. continue
  172. handler = TOOL_HANDLERS.get(block.name)
  173. output = handler(**block.input) if handler else f"未知工具:{block.name}"
  174. print(str(output)[:200])
  175. results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
  176. messages.append({"role": "user", "content": results})
  177. if __name__ == "__main__":
  178. print("s03: 权限系统")
  179. print("输入问题,回车发送。输入 q 退出。\n")
  180. history = []
  181. while True:
  182. try:
  183. query = input("\033[36ms03 >> \033[0m")
  184. except (EOFError, KeyboardInterrupt):
  185. break
  186. if query.strip().lower() in ("q", "exit", ""):
  187. break
  188. history.append({"role": "user", "content": query})
  189. agent_loop(history)
  190. for block in history[-1]["content"]:
  191. if getattr(block, "type", None) == "text":
  192. print(block.text)
  193. print()