# 硬沙箱:把'写文件'这一 effect 限制在能力边界内

← 对照卡索引
🎯 prompt 约束挡不住模型(实测它把 5 次 WriteFile 全写到 F 之外、还跑偏续跑别人的研究); 必须工具层硬拦。只拦写不拦读;Bash 靠 CWD + dangerousCommandBlock(shell 无法工具层硬沙箱)。

### 📖 教科书概念

智能体一旦能写文件、跑命令,就具备了改变环境的副作用(side effect)。仅靠 prompt 约束("请只在 F 目录内操作")是不可靠的——模型会越界。工程上必须在 **工具层**做硬性能力限制(capability confinement):无论模型怎么想,越界的写 操作在执行边界被直接拒绝。 这对应操作系统里的沙箱/权限模型:能力(capability)显式授予且不可逾越, realpath 解析符号链接以防 `..` 或 symlink 逃逸。

### 💻 平台源码落点

`lambdagent/src/lambdagent/builtin_tools/_sandbox.py` · check_write_allowed · L38-56

    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    )

模块级 root + 锁。未设 root → 总允许;设了 → realpath 后必须 == root 或落在 root/ 之下,否则返回 [SANDBOX_DENIED]。realpath 防 symlink/.. 逃逸。
`lambdagent/src/lambdagent/builtin_tools/file_tools.py` · _resolve · L21-35

    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)

相对路径必须按会话 CWD(shell_tools._get_cwd)解析,而非服务进程 CWD。 配套坑:加任何新文件工具都要走 _resolve,否则读侧目录会和写侧对不上。

### ∑ 形式化对象

Guard(WriteFile, check_write_allowed) ; root = realpath(F)
effect: State[fs] ↾ Cap(root) (effect 被限制到能力 root 内)

沙箱在形式上是一个 effect 限制算子:把 WriteFile 的 State[fs] effect 投影 (restrict, ↾)到能力集 Cap(root) 之内。越界写不是"运行期再补救",而是被 Guard 直接拒绝并把 [SANDBOX_DENIED] 反馈给模型纠正——这正是"形式化提前拒绝" 在安全维度的兑现,和范式③的能力授权(Cap)是同一套语言。

### 🎞 Live Demo

回归测试 test_sandbox_blocks_writes_outside_root 验证越界写被拒。 教学演示:在工作区模式下令 agent 尝试写 F 之外,观察 trace 里的 [SANDBOX_DENIED] 与模型随后的自我纠正。

学习目标 / Takeaway 学生应能把三件事连起来: 1. 副作用(effect)是危险的来源,所以需要边界; 2. 边界必须在工具层强制(prompt 不够),代码落点是 check_write_allowed + _resolve; 3. 形式上这是 effect 到能力集的限制(↾ Cap),与范式③的外包能力授权是同一抽象。 这张卡把"安全"从一句口号变成一个可指认的 effect 限制算子。

相关对照卡: 工具调用范式③:claude-code 原生车道(把工具调用外包给运行时) 工具调用范式②:原生 function-calling(结构化 tool_calls)