智能体一旦能写文件、跑命令,就具备了改变环境的副作用(side effect)。仅靠 prompt 约束("请只在 F 目录内操作")是不可靠的——模型会越界。工程上必须在 **工具层**做硬性能力限制(capability confinement):无论模型怎么想,越界的写 操作在执行边界被直接拒绝。 这对应操作系统里的沙箱/权限模型:能力(capability)显式授予且不可逾越, realpath 解析符号链接以防 `..` 或 symlink 逃逸。
38def check_write_allowed(target_path: str) -> Tuple[bool, str]:39 """目标路径是否允许写。未设 root → 总允许;设了 → 必须落在 root 内。4041 返回 (ok, error_msg)。realpath 解析符号链接,防 symlink 逃逸。42 """43 root = get_sandbox_root()44 if not root:45 return True, ""46 try:47 rp = os.path.realpath(target_path)48 except Exception:49 rp = os.path.abspath(target_path)50 if rp == root or rp.startswith(root + os.sep):51 return True, ""52 return False, (53 f"[SANDBOX_DENIED] 拒绝写到工作文件夹之外:{target_path}\n"54 f"本次会话只允许在工作文件夹内读写:{root}\n"55 f"请把目标路径改到该文件夹内(用其下的绝对路径)后重试。"56 )21def _resolve(path: str) -> str:22 """把(可能相对的)路径解析为绝对路径,基准 = Bash 会话当前目录(_session_cwd),23 与 Bash/沙箱一致。原实现用 os.path.abspath()/os.getcwd() 即**服务进程 CWD**24 (=启动目录 lambdagentpaas),导致工作区对话里 ListFiles/相对路径落到错目录——25 agent 用绝对路径读到了对的论文,却 ListFiles 列出 lambdagentpaas 源码、自我迷惑、26 放弃落盘(commit 见此修复)。绝对路径原样规整。"""27 try:28 from lambdagent.builtin_tools.shell_tools import _get_cwd29 base = _get_cwd()30 except Exception:31 base = os.getcwd()32 if not path:33 return base34 if os.path.isabs(path):35 return os.path.abspath(path)沙箱在形式上是一个 effect 限制算子:把 WriteFile 的 State[fs] effect 投影 (restrict, ↾)到能力集 Cap(root) 之内。越界写不是"运行期再补救",而是被 Guard 直接拒绝并把 [SANDBOX_DENIED] 反馈给模型纠正——这正是"形式化提前拒绝" 在安全维度的兑现,和范式③的能力授权(Cap)是同一套语言。
回归测试 test_sandbox_blocks_writes_outside_root 验证越界写被拒。 教学演示:在工作区模式下令 agent 尝试写 F 之外,观察 trace 里的 [SANDBOX_DENIED] 与模型随后的自我纠正。
相关对照卡: 工具调用范式③:claude-code 原生车道(把工具调用外包给运行时) 工具调用范式②:原生 function-calling(结构化 tool_calls)