sandbox-effect-cap.html 11 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <!doctype html><html lang="zh"><head><meta charset="utf-8">
  2. <meta name="viewport" content="width=device-width,initial-scale=1">
  3. <title>硬沙箱:把&#x27;写文件&#x27;这一 effect 限制在能力边界内</title><style>
  4. :root{--fg:#1a1a1a;--bg:#fff;--mut:#666;--line:#e3e3e3;--accent:#3b5bdb;
  5. --codebg:#f6f8fa;--warn:#b00020;--okbg:#eef4ff;--formalbg:#f3f0ff;--conceptbg:#fbfbf9;}
  6. *{box-sizing:border-box}
  7. body{font:15px/1.65 -apple-system,"PingFang SC",Segoe UI,Roboto,sans-serif;
  8. color:var(--fg);background:var(--bg);margin:0;padding:0}
  9. a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
  10. .wrap{max-width:1180px;margin:0 auto;padding:28px 22px 80px}
  11. header.top{border-bottom:1px solid var(--line);padding-bottom:14px;margin-bottom:22px}
  12. header.top h1{margin:0 0 4px;font-size:20px}
  13. .sub{color:var(--mut);font-size:13px}
  14. .crumbs{font-size:13px;margin-bottom:18px}.crumbs a{color:var(--mut)}
  15. .badge{display:inline-block;font-size:12px;background:var(--okbg);color:var(--accent);
  16. border-radius:4px;padding:1px 8px;margin-right:6px}
  17. h2.ctitle{font-size:19px;margin:6px 0 4px}
  18. .keypoint{background:#fff8e6;border-left:3px solid #e6a700;padding:10px 14px;
  19. border-radius:4px;margin:14px 0;font-size:14px}
  20. .cols{display:grid;grid-template-columns:1fr 1.5fr 1fr;gap:16px;margin:22px 0}
  21. .col{border:1px solid var(--line);border-radius:8px;padding:14px 16px;min-width:0}
  22. .col h3{margin:0 0 10px;font-size:13px;letter-spacing:.04em;text-transform:uppercase;color:var(--mut)}
  23. .col.concept{background:var(--conceptbg)}
  24. .col.formal{background:var(--formalbg)}
  25. .col p{margin:0 0 10px;white-space:pre-wrap;font-size:14px}
  26. .anchor{margin:0 0 16px}
  27. .anchor .meta{font-size:12px;color:var(--mut);margin-bottom:4px}
  28. .anchor .meta code{background:var(--codebg);padding:1px 5px;border-radius:3px}
  29. pre{background:var(--codebg);border:1px solid var(--line);border-radius:6px;
  30. overflow:auto;margin:0 0 6px;padding:10px 0;font-size:12px;line-height:1.5}
  31. pre code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;display:block}
  32. .ln{display:flex}.ln .n{color:#aaa;text-align:right;width:46px;padding:0 10px;
  33. user-select:none;flex:none}.ln .c{white-space:pre;padding-right:14px}
  34. .note{font-size:12.5px;color:var(--mut);margin:0 0 14px}
  35. .warn{color:var(--warn);background:#fff0f0;border:1px solid #ffd0d0;border-radius:6px;
  36. padding:8px 12px;font-size:13px}
  37. .kv{font-size:13px}.kv code{background:var(--codebg);padding:1px 5px;border-radius:3px}
  38. .formal .term{font-family:ui-monospace,Menlo,monospace;background:#fff;border:1px solid var(--line);
  39. border-radius:5px;padding:8px 10px;font-size:13px;margin-bottom:8px;word-break:break-word}
  40. .effect{font-family:ui-monospace,Menlo,monospace;color:#7048e8;font-size:13px;margin-bottom:8px}
  41. .trace{border:1px solid var(--line);border-radius:8px;padding:14px 16px;margin:22px 0;background:#fafafa}
  42. .trace h3{margin:0 0 10px;font-size:14px}
  43. .stat{display:inline-block;margin:0 18px 6px 0;font-size:13px}
  44. .stat b{font-size:17px;color:var(--accent)}
  45. .toolchip{display:inline-block;background:#eef;border:1px solid #dde;border-radius:12px;
  46. padding:1px 9px;margin:0 6px 6px 0;font-size:12px}
  47. .takeaway{background:var(--okbg);border-radius:8px;padding:14px 18px;margin:22px 0;
  48. white-space:pre-wrap;font-size:14px}
  49. .rel a{margin-right:14px;font-size:13px}
  50. table.idx{border-collapse:collapse;width:100%;margin-top:8px}
  51. table.idx td{border-bottom:1px solid var(--line);padding:9px 8px;vertical-align:top}
  52. table.idx .wk{color:var(--mut);font-size:13px;width:64px;white-space:nowrap}
  53. .modhdr{margin:26px 0 4px;font-size:15px;color:var(--accent)}
  54. footer{margin-top:50px;color:var(--mut);font-size:12px;border-top:1px solid var(--line);padding-top:14px}
  55. @media(max-width:900px){.cols{grid-template-columns:1fr}}
  56. </style></head><body><div class="wrap">
  57. <div class="crumbs"><a href="index.html">← 对照卡索引</a></div>
  58. <header class="top">
  59. <span class="badge">模块 C</span><span class="badge">第 8 周</span>
  60. <span class="badge">sandbox-effect-cap</span>
  61. <h2 class="ctitle">硬沙箱:把&#x27;写文件&#x27;这一 effect 限制在能力边界内</h2></header>
  62. <div class="keypoint">🎯 prompt 约束挡不住模型(实测它把 5 次 WriteFile 全写到 F 之外、还跑偏续跑别人的研究); 必须工具层硬拦。只拦写不拦读;Bash 靠 CWD + dangerousCommandBlock(shell 无法工具层硬沙箱)。
  63. </div>
  64. <div class="cols">
  65. <div class="col concept"><h3>📖 教科书概念</h3><p>智能体一旦能写文件、跑命令,就具备了改变环境的副作用(side effect)。仅靠
  66. prompt 约束(&quot;请只在 F 目录内操作&quot;)是不可靠的——模型会越界。工程上必须在
  67. **工具层**做硬性能力限制(capability confinement):无论模型怎么想,越界的写
  68. 操作在执行边界被直接拒绝。
  69. 这对应操作系统里的沙箱/权限模型:能力(capability)显式授予且不可逾越,
  70. realpath 解析符号链接以防 `..` 或 symlink 逃逸。
  71. </p></div>
  72. <div class="col code"><h3>💻 平台源码落点</h3><div class="anchor"><div class="meta"><code>lambdagent/src/lambdagent/builtin_tools/_sandbox.py</code> · <b>check_write_allowed</b> · L38-56</div><pre><code><div class="ln"><span class="n">38</span><span class="c">def check_write_allowed(target_path: str) -&gt; Tuple[bool, str]:</span></div><div class="ln"><span class="n">39</span><span class="c"> &quot;&quot;&quot;目标路径是否允许写。未设 root → 总允许;设了 → 必须落在 root 内。</span></div><div class="ln"><span class="n">40</span><span class="c"></span></div><div class="ln"><span class="n">41</span><span class="c"> 返回 (ok, error_msg)。realpath 解析符号链接,防 symlink 逃逸。</span></div><div class="ln"><span class="n">42</span><span class="c"> &quot;&quot;&quot;</span></div><div class="ln"><span class="n">43</span><span class="c"> root = get_sandbox_root()</span></div><div class="ln"><span class="n">44</span><span class="c"> if not root:</span></div><div class="ln"><span class="n">45</span><span class="c"> return True, &quot;&quot;</span></div><div class="ln"><span class="n">46</span><span class="c"> try:</span></div><div class="ln"><span class="n">47</span><span class="c"> rp = os.path.realpath(target_path)</span></div><div class="ln"><span class="n">48</span><span class="c"> except Exception:</span></div><div class="ln"><span class="n">49</span><span class="c"> rp = os.path.abspath(target_path)</span></div><div class="ln"><span class="n">50</span><span class="c"> if rp == root or rp.startswith(root + os.sep):</span></div><div class="ln"><span class="n">51</span><span class="c"> return True, &quot;&quot;</span></div><div class="ln"><span class="n">52</span><span class="c"> return False, (</span></div><div class="ln"><span class="n">53</span><span class="c"> f&quot;[SANDBOX_DENIED] 拒绝写到工作文件夹之外:{target_path}\n&quot;</span></div><div class="ln"><span class="n">54</span><span class="c"> f&quot;本次会话只允许在工作文件夹内读写:{root}\n&quot;</span></div><div class="ln"><span class="n">55</span><span class="c"> f&quot;请把目标路径改到该文件夹内(用其下的绝对路径)后重试。&quot;</span></div><div class="ln"><span class="n">56</span><span class="c"> )</span></div></code></pre><div class="note">模块级 root + 锁。未设 root → 总允许;设了 → realpath 后必须 == root 或落在 root/ 之下,否则返回 [SANDBOX_DENIED]。realpath 防 symlink/.. 逃逸。
  73. </div></div><div class="anchor"><div class="meta"><code>lambdagent/src/lambdagent/builtin_tools/file_tools.py</code> · <b>_resolve</b> · L21-35</div><pre><code><div class="ln"><span class="n">21</span><span class="c">def _resolve(path: str) -&gt; str:</span></div><div class="ln"><span class="n">22</span><span class="c"> &quot;&quot;&quot;把(可能相对的)路径解析为绝对路径,基准 = Bash 会话当前目录(_session_cwd),</span></div><div class="ln"><span class="n">23</span><span class="c"> 与 Bash/沙箱一致。原实现用 os.path.abspath()/os.getcwd() 即**服务进程 CWD**</span></div><div class="ln"><span class="n">24</span><span class="c"> (=启动目录 lambdagentpaas),导致工作区对话里 ListFiles/相对路径落到错目录——</span></div><div class="ln"><span class="n">25</span><span class="c"> agent 用绝对路径读到了对的论文,却 ListFiles 列出 lambdagentpaas 源码、自我迷惑、</span></div><div class="ln"><span class="n">26</span><span class="c"> 放弃落盘(commit 见此修复)。绝对路径原样规整。&quot;&quot;&quot;</span></div><div class="ln"><span class="n">27</span><span class="c"> try:</span></div><div class="ln"><span class="n">28</span><span class="c"> from lambdagent.builtin_tools.shell_tools import _get_cwd</span></div><div class="ln"><span class="n">29</span><span class="c"> base = _get_cwd()</span></div><div class="ln"><span class="n">30</span><span class="c"> except Exception:</span></div><div class="ln"><span class="n">31</span><span class="c"> base = os.getcwd()</span></div><div class="ln"><span class="n">32</span><span class="c"> if not path:</span></div><div class="ln"><span class="n">33</span><span class="c"> return base</span></div><div class="ln"><span class="n">34</span><span class="c"> if os.path.isabs(path):</span></div><div class="ln"><span class="n">35</span><span class="c"> return os.path.abspath(path)</span></div></code></pre><div class="note">相对路径必须按会话 CWD(shell_tools._get_cwd)解析,而非服务进程 CWD。 配套坑:加任何新文件工具都要走 _resolve,否则读侧目录会和写侧对不上。
  74. </div></div></div>
  75. <div class="col formal"><h3>∑ 形式化对象</h3><div class="term">Guard(WriteFile, check_write_allowed) ; root = realpath(F)</div><div class="effect">effect: State[fs] ↾ Cap(root) (effect 被限制到能力 root 内)</div><p>沙箱在形式上是一个 effect 限制算子:把 WriteFile 的 State[fs] effect 投影 (restrict, ↾)到能力集 Cap(root) 之内。越界写不是&quot;运行期再补救&quot;,而是被 Guard 直接拒绝并把 [SANDBOX_DENIED] 反馈给模型纠正——这正是&quot;形式化提前拒绝&quot; 在安全维度的兑现,和范式③的能力授权(Cap)是同一套语言。
  76. </p></div>
  77. </div>
  78. <div class="trace"><h3>🎞 Live Demo</h3><p class="note">回归测试 test_sandbox_blocks_writes_outside_root 验证越界写被拒。 教学演示:在工作区模式下令 agent 尝试写 F 之外,观察 trace 里的 [SANDBOX_DENIED] 与模型随后的自我纠正。
  79. </p></div>
  80. <div class="takeaway"><b>学习目标 / Takeaway</b>
  81. 学生应能把三件事连起来:
  82. 1. 副作用(effect)是危险的来源,所以需要边界;
  83. 2. 边界必须在工具层强制(prompt 不够),代码落点是 check_write_allowed + _resolve;
  84. 3. 形式上这是 effect 到能力集的限制(↾ Cap),与范式③的外包能力授权是同一抽象。
  85. 这张卡把&quot;安全&quot;从一句口号变成一个可指认的 effect 限制算子。
  86. </div>
  87. <p class="rel"><b>相关对照卡:</b> <a href="tool-claude-code-native.html">工具调用范式③:claude-code 原生车道(把工具调用外包给运行时)</a> <a href="tool-native-fc.html">工具调用范式②:原生 function-calling(结构化 tool_calls)</a></p>
  88. <footer>对照卡源文件: <code>teaching/cards/04-sandbox-effect-cap.yml</code> ·
  89. 源码 span 由 gen.py 在生成时实时读自仓库</footer>
  90. </div></body></html>