跳至內容

沙箱後端

後端 值 平台 可用性
隔離式 VM microvm macOS、Linux、Windows(microVM 虛擬機器監控程式) 以探測為閘,取決於 microVM 執行階段;套用於互動式終端機 —— shell 在測試機內執行,而非在主機上
原生 native macOS(Seatbelt)、Linux 與 WSL2(bubblewrap) 於開機時探測;對於要求以檔名 glob 拒絕寫入的執行,Linux 會拒絕,因為 bubblewrap 無法強制執行
無 none 全部 解析時的預設值、終端機的預設值,以及在沒有可用後端或設定了 --sandbox off 時的退回選項

對於 null 或無法辨識的已儲存值,SandboxBackend.fromName 會傳回 none。已儲存的 "docker" 會對應至 native。

microvm 是唯一具備核心邊界的後端 —— 工作負載確實不在主機上執行。它從不作為任何一方默默退回的選項:在無法啟動 VM 的主機上明確要求它,會大聲失敗(「我要求的是一部 VM,得到的卻是我的筆電」是這個隔離環境唯一承擔不起的降級),而只有未指明的預設值才會降級至 native,並以一行日誌說明。工作階段的實際後端會回報給用戶端並顯示徽章,因此終端機永遠無法在主機上執行時聲稱自己已受隔離。VM 本身的生命週期、映像與對外流量,見測試機參考。

Windows 沒有原生沙箱。 對於任何不是 macOS 或 Linux/WSL2 的主機,NativeSandboxAdapter.probe 都會傳回 available: false。因此 Windows 上的代理派發採用 none。microvm 後端則是另一回事:它以 microVM 執行階段的探測結果為閘,套用於互動式終端機與測試機,而非包裝主機上的 bash。

探測結果是決定是否強制執行的閘門。它只在伺服器開機時執行一次。

主機 available 備註/提示
macOS true sandbox-exec(Seatbelt)—— 命名空間隔離,沒有核心邊界
具備 bwrap 與 socat 的 Linux 或 WSL2 true bubblewrap —— 命名空間隔離,沒有核心邊界
缺少任一工具的 Linux 或 WSL2 false requiresInstall: true,並附上指出缺少哪些套件的安裝提示
任何其他平台 false 此平台不提供原生沙箱

伺服器會以一行啟動日誌記錄結果:agent sandbox ON 加上備註、no OS-native agent sandbox on this host 加上提示,或 agent sandbox DISABLED by --sandbox off。GET /healthz 不含任何沙箱欄位。

--sandbox / CC_SERVER_SANDBOX 接受 on(預設)或 off。它是退出選項,而非啟用選項:on 的意思是「當主機提供後端時就包裝」,永遠不是「要求必須有後端」。

使用 Apple 的 Seatbelt 沙箱機制(與 macOS App Sandbox 所用的系統相同)。

產生的設定檔預設寬鬆((allow default)),再劃出拒絕項,因為完全預設拒絕的設定檔會令互動式 shell 無法使用 —— 要列舉的 dyld、XPC 與 Mach 呼叫實在太多。實際後果是代理仍可讀取主機的大部分內容;你已註冊的工作副本是靠明確的寫入與重新命名拒絕來保護,而不是靠隱形。

  • 檔案系統讀取:機密路徑會被拒絕(~/.ssh、~/.aws、~/.gnupg、~/.config/gh、~/Library/Keychains,以及拒絕讀取集合中的其餘路徑)
  • 檔案系統寫入:重設為拒絕,然後加上明確的子路徑允許;強制拒絕的路徑(shell rc 檔案、.git/hooks、Claude 設定、受保護的工作副本)即使在可寫入的根目錄內也會被封鎖。每一條被拒路徑及其上層路徑都會拒絕 file-write-unlink,因此重新命名無法繞過寫入拒絕
  • 行程:對永遠危險的二進位檔拒絕 process-exec,並封鎖從可寫入目錄執行任何東西
  • 網絡:限制於行內代理伺服器連接埠加上 DNS —— 回送並非一律允許,Unix 通訊端也不允許。代理伺服器會強制執行按每個工作階段的網域允許清單(見網絡對外流量)
  • 設定:設定檔按每次啟動,從 SandboxSpec 產生

需求:

  • sandbox-exec(/usr/bin/sandbox-exec),每個 macOS 安裝都內含 —— 無需額外工具

使用 bubblewrap(bwrap)建立掛載與 PID 命名空間。

  • 檔案系統:建立只看得見被綁定路徑的掛載命名空間
  • 網絡:在未共用的網絡命名空間中執行;流量經由 socat 的 Unix 通訊端橋接抵達主機代理伺服器
  • 行程:PID 命名空間隔離

需求:

  • 已安裝 bubblewrap 與 socat(Debian/Ubuntu 上為 sudo apt-get install bubblewrap socat)
  • 核心命名空間支援(現代 Linux 的標準配備)

Bubblewrap 可以遮蔽既有路徑,但無法禁止可寫入綁定內新增的 .env、*.key 或 *.pem 檔案。標準派發政策要求這些檔名 glob 拒絕,因此原生代理派發目前在 Linux/WSL2 上即使探測找到 bubblewrap 也會失效即拒絕。它不會以悄然放寬的政策執行。要有具備建立時檔名規則的後端,才能恢復 Linux 的原生派發。停用沙箱是明確、不受限制的退出做法,而不是等效的變通方式。

不進行任何沙箱包裝。行程直接在主機上執行,擁有主機的檔案系統、網絡與行程存取權。ACP 與執行框架的 bash 工具仍會取得環境淨化與指令政策;經由 NoSandboxAdapter 的 Claude Code 則繼承主機環境。這是在沒有可用後端或設定了 --sandbox off 的主機上,代理派發所退回的狀態,也是用戶端未指名任何後端時終端機的預設。

終端機按自己的條件受沙箱包裝,而不是由派發的探測決定。TerminalSessionService 的 defaultBackend 是 none,而 cc_server 不會覆寫它,因此除非用戶端明確要求 native,終端機工作階段會執行主機的 shell —— 桌面版的終端機面板並不會這樣要求。

當要求 native 時,shell 會經由 SandboxManager、在固定的終端機設定檔之下受到包裝:$HOME 與該工作階段的工作目錄可寫入,網絡開放,而 ~/.ssh、~/.aws、~/.gnupg、~/.config/gh 與 ~/Library/Keychains 則被拒絕讀取。唯讀程式碼託管權杖與推送閘道不適用 —— 那是派發層面的事。若無法進行包裝,該工作階段會失敗,而不是在主機 shell 的 PTY 上顯示原生徽章。

沙箱啟動後,對外網絡存取是由行內代理伺服器強制執行,而不是靠核心防火牆規則。在沒有可用後端的主機上,這一切都不適用,衍生的行程會以與伺服器使用者完全相同的方式存取網絡。

  • 沙箱執行階段會在 cc_server 行程內、於 127.0.0.1 上執行一個 HTTP/HTTPS 代理伺服器與一個 SOCKS5 代理伺服器,每個受限制的工作階段各一對(http_proxy.dart、socks_proxy.dart)。並行執行不會共用同一對。
  • 受沙箱包裝的行程會取得代理伺服器環境變數(HTTP_PROXY/HTTPS_PROXY → HTTP 代理伺服器,ALL_PROXY → SOCKS 代理伺服器;GIT_SSH_COMMAND 會將 git-over-SSH 導向 SOCKS 代理伺服器)。在 Linux 上,網絡命名空間是未共用的,因此代理伺服器是唯一的出路;在 macOS 上,沙箱共用主機的回送,並直接與它們通訊。
  • 每個連線的目標主機會與該工作階段的網域允許清單比對;其他一律拒絕。萬用字元有兩種形式,各含恰好一個 *:前置的 *.example.com 會匹配任何子網域(包括 a.b.example.com),但不匹配頂級網域本身;中間標籤的 bedrock.*.amazonaws.com 則恰好匹配一個標籤(bedrock.us-east-1.amazonaws.com,而非 bedrock.evil.attacker.amazonaws.com)。
  • 允許清單會按每個工作階段,從精選的基準清單(kBaselineAllowedDomains:LLM 供應商、Git 代管、套件註冊表)解析,再加上 GitHub 網域(kGithubDomains),以及 SandboxSpec.egressAllowlist。派發路徑從不傳入額外的 egressAllowlist 項目,因此實際上每次執行得到的,都是基準清單加上 GitHub。
  • 一份固定的拒絕清單(kBaselineDeniedDomains:雲端中繼資料端點、遙測接收端)優先於所有允許項。
  • networkEnabled: false 仍會啟動按每個工作階段的代理伺服器,但允許清單是空的,因此每一個對外連線都會被拒絕。派發會在執行開始時依存取網絡規則設定這個值:拒絕會關閉網絡,允許與先詢問會讓它保持開啟。

傳遞給 SandboxPort.launch 的規格:

欄位 型別 說明
sessionId String 穩定的工作階段識別碼(通常是空間 id)
workspaceId String 所屬工作區 id
agentId String? 目前綁定至該沙箱的代理(如有)
bindMounts List<SandboxBindMount> 主機 → 客體的掛載;沙箱只看得到此處列出的主機路徑
networkEnabled bool(預設 true) 派發時依網絡對外連線規則設定(只有解析為拒絕時才關閉)。關閉時,按每個工作階段的代理伺服器仍會啟動,但允許清單是空的,因此每一個對外連線都會被拒絕
egressAllowlist List<String>(預設 []) 額外允許的對外流量網域;支援萬用字元(*.example.com,以及一個標籤的中間形式)。派發從不填入此欄位,因此受包裝的執行只會得到精選基準清單,加上 GitHub 網域
guestWorkdir String? 沙箱內的預設工作目錄;個別的 exec 呼叫可以覆寫它
mode Mode(預設 chat) 劃定檔案系統寫入規則:chat 讓綁定掛載保持可寫入;review、plan 與 orchestrate 則為唯讀
protectedPaths List<String>(預設 []) 在任何模式下都絕不可在沙箱內寫入的主機路徑
runnerStateDirs List<String>(預設 []) 執行器用來存放自身狀態的主機目錄,在每個模式下都可寫入(目前是由 Control Center 管理的 CLAUDE_CONFIG_DIR)
execGrantRoots List<String>(預設 []) 由操作員核准的目錄樹,會在 macOS 上重新開啟「可寫入目錄的 exec 封鎖」。Linux 會忽略它們 —— bwrap 沒有 $HOME 的 exec 封鎖可供重新開啟

protectedPaths 存放原始的已註冊程式庫工作副本,於每次派發時從工作區的程式庫解析。代理只在按每個對話的寫入時複製 worktree 中工作,因此原始副本絕不可從沙箱內寫入 —— 否則在 macOS 上,Seatbelt 對 $HOME 的一律寫入允許會令它們可經由 bash 寫入。它們在每個模式下(包括 chat)都會加入機密拒絕寫入集合。

每個拒絕讀取與拒絕寫入的路徑,都會以其字面拼法與符號連結解析後的拼法各輸出一份。核心會以解析後的路徑來比對拒絕規則,因此針對符號連結路徑撰寫的規則永遠不會觸發 —— 而在 macOS 上,這是常態而非罕見情況,因為 /tmp 解析為 /private/tmp,/var/folders/… 解析為 /private/var/folders/…。含有 * 或 ? 的項目會視為 glob 而不加處理;無法解析的路徑則保持原樣。

欄位 型別 說明
hostPath String 主機上的絕對路徑
guestPath String 沙箱內的路徑(使用原生沙箱時這等於 hostPath;保留以維持配接器對稱)
readOnly bool(預設 false) 為 true 時以唯讀方式掛載

由 SandboxPort.launch 傳回:

欄位 型別 說明
sessionId String 工作階段識別碼
backend SandboxBackend 正在執行的是哪個後端
state SandboxState 生命週期狀態
error Object? 當 state 為 error 時設定
details Map<String, Object?> 配接器私有的記帳資料(工作目錄、PID、設定檔路徑等)
狀態 說明
created 規格已建立,但 VM 尚未啟動
warm 正在執行且已可接受 exec 指令
active 目前正在執行指令
suspended 已暫停/已檢查點至磁碟以節省 RAM
destroyed 已拆除;控制代碼不再有效
error 啟動失敗 —— 見 SandboxHandle.error
事件類型 說明
stdout 沙箱的標準輸出
stderr 沙箱的標準錯誤
exit 沙箱行程已結束(帶有 exitCode)
starting 沙箱正在啟動
ready 沙箱已可接受 exec 呼叫
killed 行程被主機終止(OOM、逾時、手動停止)
violation 沙箱政策遭到違反(帶有一個 SandboxViolation)
欄位 型別 說明
action String 被拒的動作詞(例如 file-read*、network-outbound)
target String 被拒操作所針對的路徑或主機
raw String? 原始日誌行,可用於除錯