目錄
設計問題
Coding agent 的每個檔案工具,第一個參數幾乎都是路徑。這條路徑是 LLM 生成的字串——可能拼錯、可能幻覺、也可能被 prompt injection 推著去讀 .env、寫 ~/.ssh/authorized_keys。所以每個 agent 都得回答同一組問題:
- 模型能不能碰到 workspace 以外的東西?
- 路徑校驗做在哪一層——工具程式碼、permission 系統、還是作業系統?
- 就算校驗被繞過,最壞情況的爆炸半徑是什麼?
這三題答案的組合,決定了一個 agent 是「方便」還是「可信」。這篇把五個成熟專案的答案攤開來比,再對照我在 rivumi 上的選擇。
五家怎麼做
Codex:OS 級沙箱才是最後防線
OpenAI Codex 的態度最明確:path 校驗可以做很多層,但真正的保證來自核心的 sandboxing 模組。macOS 上走 Apple Seatbelt——codex/codex-rs/sandboxing/src/seatbelt.rs#create_seatbelt_command_args 把工作目錄轉成 sandbox-exec 的 policy 參數;底層 policy 寫在 codex/codex-rs/sandboxing/src/seatbelt_base_policy.sbpl,第一行實質規則就是 (deny default),註解直接說明靈感來自 Chrome 的 renderer sandbox。
Linux 上走 Landlock + seccomp:codex/codex-rs/linux-sandbox/src/landlock.rs#install_filesystem_landlock_rules_on_current_thread 把整個 / 設為唯讀、只把 writable_roots 加上讀寫權限,再掛 seccomp filter 擋網路 syscall。最值得學的是 fail-closed 那一行——restrict_self() 之後檢查 RulesetStatus::NotEnforced,如果核心根本不支援 Landlock、policy 沒有真正生效,就直接回 SandboxErr::LandlockRestrict 拒絕執行。「沙箱沒生效」不等於「可以不沙箱」。
Claude Code:permission 層的精細路徑解析
Claude Code 的防線主要在工具與 permission 層。claude-code-source/src/tools/BashTool/pathValidation.ts#validatePath 對 Bash 指令裡出現的每個路徑做展開 tilde、resolve、比對 allowed working directories。有個細節很誠實:原始碼註解明說刻意不做 symlink resolve 再檢查——因為 macOS 的 /tmp 是 /private/tmp 的 symlink,先 resolve 反而會讓危險路徑漏掉。同一個檔案裡的 checkPathConstraints 和 validateOutputRedirections 連輸出重導向都攔。
檔案工具側,FileEditTool/FileEditTool.ts#validateInput 先驗絕對路徑與 settings 檔案的特殊限制,再交給 checkWritePermissionForTool 走 permission 判定;讀取走 FileReadTool/FileReadTool.ts 的 checkReadPermissionForTool。另外它有一個很有意思的設計:EnterWorktreeTool/EnterWorktreeTool.ts#createWorktreeForSession 可以整個 session 切進一個獨立 git worktree——用版本控制做工作區隔離,而不是硬擋路徑。
OpenCode:project 邊界 + allow/ask/deny
OpenCode 把邊界定義在 project instance 上。opencode/packages/opencode/src/project/instance-context.ts#containsPath 回答「這個路徑算不算專案內」:在 ctx.directory 或 ctx.worktree 內就算,而且特別處理了非 git 專案 worktree 設為 / 會匹配所有絕對路徑的地雷。超出邊界的操作會觸發 tool/external-directory.ts 的 external directory 許可流程,最終由 permission/index.ts 的 allow / ask / deny 規則評估決定放行、詢問或拒絕。
Pi:把邊界留給 ExecutionEnv
badlogic/pi-mono 的做法最極簡:工具層只負責把相對路徑正規化成絕對路徑——pi-mono/packages/agent/src/harness/tools/path-utils.ts#resolveToolPath 全部委派給 pi-mono/packages/agent/src/harness/types.ts 定義的 ExecutionEnv.absolutePath() 介面。也就是說 Pi 本體不內建沙箱,邊界由宿主環境(嵌入 Pi 的應用)自己決定。這是刻意的取捨:core 保持小而可測,隔離責任外移。
OMP:繼承 Pi,加上 worktree 基線
oh-my-pi 作為 Pi 的 fork,沿用同一套 ExecutionEnv 路徑模型,但在 autoresearch 工作流補上了 worktree 基線:oh-my-pi/packages/coding-agent/src/autoresearch/index.ts 會記住 baseline commit,discard 時 reset worktree 回基準點,並在不在專用分支上時警告使用者 revert 安全性不完整。
rivumi 的選擇與差異
rivumi M1 的隔離策略是disposable Git workspace,兩個模組撐起來:
第一層:workspace 本身是拋棄式的固定 commit clone。 src/rivumi/runtime.py#LocalGitWorkspace.prepare 要求 base_sha 必須是完整 40 字元 SHA,先 rev-parse --verify 確認 commit 存在,再用 clone --no-hardlinks --no-checkout 複製(no-hardlinks 確保實體檔案分離),detach HEAD 後還要重新驗證 rev-parse HEAD 等於 base_sha 才肯交付。run_dir 也被禁止放在來源 repo 內部。來源 worktree 從頭到尾不被碰,最後產出的是 unstaged patch,由人類審查。
第二層:所有模型給的路徑過 src/rivumi/policy.py#SafePathPolicy.resolve。 它拒絕絕對路徑、反斜線、NUL byte、.. traversal、任何段落的 .git,最後 resolve 完還要 relative_to(workspace_root) 確認沒有從 symlink 逃出去。glob 是 segment-aware 的——* 不跨目錄,只有完整的 ** segment 才會跨,避免 src/*.py 意外涵蓋 src/deep/x.py。
跟五家比起來,差異很清楚,而且必須誠實講:
- OS sandbox 已有 verification baseline,但不是整個 runtime 的通用沙箱。
--sandbox-checks會把run_check與 final verification 交給src/rivumi/runtime.py#resolve_command_sandbox:macOS 用sandbox-exec,Linuxauto先找 bubblewrap,否則走專案內的 Landlock wrapper;明確要求的 backend 不可用時,command 以 126 fail closed。它包的是已宣告的 verification argv,不代表外部 CLI、provider transport 或所有 host process 都被同一套 policy 包住。 - 遠端另有受限 container slice。
cloudflare/的 Worker/Sandbox control plane 只收 bounded text source map,不收 Git URL、archive、shell string 或 caller credential。這證明隔離邊界能搬進 container,但仍只是受限部署切片,不是 production traffic 與 hostile-code hardening 的完成證明。 - path policy 比 Pi 嚴、比 Codex 淺。 Pi 把邊界外包,rivumi 和 OpenCode 一樣在應用層劃界,但 rivumi 多了
.git全面禁入和 symlink escape 的 resolve 檢查——代價是這些檢查全是 Python 字串處理,理論上任何 parser bug 都是逃脫口。OS 沙箱沒有這個問題,因為核心不看字串。
學術依據
SWE-agent 團隊提出的 ACI(agent–computer interface)概念指出:介面設計直接影響 agent 的成功率與安全性,工具邊界不是實作細節而是設計變數(Yang et al., 2024)。Path policy 正是最基礎的 ACI 決策——五家都在「模型表達意圖」與「意圖被執行」之間插了一道程式碼關卡,差別只在關卡的強度。OpenAI 官方也把 sandbox 與 approval 明確列為 Codex 的兩個獨立安全控制(Codex security docs),呼應 defense in depth:應用層校驗降低事故機率,OS 沙箱限制事故傷害。
改善路線
按優先順序:
- 把 sandbox coverage 說清楚並持續擴大。 目前有界的是 verification command 和 Cloudflare remote slice;外部 runtime 仍各自擁有執行語意。下一步是逐 runtime 列出 filesystem、process、network 能力,沒有可強制邊界時明確 fail closed 或標為 trusted-local。
- 統一網路 egress policy。 bubblewrap 的 network namespace、macOS profile 與 Landlock wrapper 的能力並不完全相同;在 production 宣稱成立前,需要一致的 deny-by-default 規則和跨平台測試。
- 把秘密掃描補到完整 artifact 邊界。
run_checkstdout/stderr 已掃描並遮罩,外部 runtime patch 也會先掃;仍需盤點 native patch、session 與其他 artifact 是否都經同一政策,並測試未涵蓋的 credential 格式。 - 保留現有的強項。 Pinned full-SHA、no-hardlinks、HEAD 重驗證、segment glob——這些是五家裡也不常見的嚴謹度,OS 沙箱進來之後仍值得留著當第二道牆。
一句話總結:disposable workspace 已保護來源 repo,--sandbox-checks 與 Cloudflare Sandbox 也補上兩塊可執行邊界;但「所有 runtime 都不會弄壞機器」仍需要跨平台、跨 backend 的強制與 production 驗證。
參考資料
Loading...