|
|
@@ -0,0 +1,56 @@
|
|
|
+"""工作文件夹硬沙箱(P1)。
|
|
|
+
|
|
|
+工作区对话模式下,把「写」限定在用户选的工作文件夹 F 内:WriteFile / EditFile
|
|
|
+的目标路径若落在 F 之外,直接拒绝并把原因返回给模型,让它改到 F 内。
|
|
|
+
|
|
|
+为什么需要:仅靠 systemPrompt 提示"就地在 F 操作"挡不住模型——实测过工作区助手
|
|
|
+把产物写到了 F 之外的另一个 run 目录。工具层硬拦是唯一可靠手段。
|
|
|
+
|
|
|
+作用范围:只拦【写】(write_file/edit_file)。读不拦(读不破坏,且常需交叉引用
|
|
|
+F 之外的文件)。Bash 是真正的 shell、无法在工具层可靠沙箱,靠 CWD=F + guard
|
|
|
+(dangerousCommandBlock) 兜。
|
|
|
+
|
|
|
+线程模型:与 shell_tools._session_cwd 一致——模块级 + 锁。单用户桌面 run 通常串行。
|
|
|
+未设 root(None)= 不限制(专项智能体写各自 run 目录,不受影响)。
|
|
|
+"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import os
|
|
|
+import threading
|
|
|
+from typing import Optional, Tuple
|
|
|
+
|
|
|
+_root: Optional[str] = None
|
|
|
+_lock = threading.Lock()
|
|
|
+
|
|
|
+
|
|
|
+def set_sandbox_root(path: str | None) -> None:
|
|
|
+ """设置写沙箱根目录;None / 空 = 解除限制。"""
|
|
|
+ global _root
|
|
|
+ with _lock:
|
|
|
+ _root = os.path.realpath(path) if path else None
|
|
|
+
|
|
|
+
|
|
|
+def get_sandbox_root() -> Optional[str]:
|
|
|
+ with _lock:
|
|
|
+ return _root
|
|
|
+
|
|
|
+
|
|
|
+def check_write_allowed(target_path: str) -> Tuple[bool, str]:
|
|
|
+ """目标路径是否允许写。未设 root → 总允许;设了 → 必须落在 root 内。
|
|
|
+
|
|
|
+ 返回 (ok, error_msg)。realpath 解析符号链接,防 symlink 逃逸。
|
|
|
+ """
|
|
|
+ root = get_sandbox_root()
|
|
|
+ if not root:
|
|
|
+ return True, ""
|
|
|
+ try:
|
|
|
+ rp = os.path.realpath(target_path)
|
|
|
+ except Exception:
|
|
|
+ rp = os.path.abspath(target_path)
|
|
|
+ if rp == root or rp.startswith(root + os.sep):
|
|
|
+ return True, ""
|
|
|
+ return False, (
|
|
|
+ f"[SANDBOX_DENIED] 拒绝写到工作文件夹之外:{target_path}\n"
|
|
|
+ f"本次会话只允许在工作文件夹内读写:{root}\n"
|
|
|
+ f"请把目标路径改到该文件夹内(用其下的绝对路径)后重试。"
|
|
|
+ )
|