跳到內容

沙箱後端

後端 取值 平台 可用性
封閉式 VM microvm macOS、Linux、Windows(microVM 虛擬機監控程式) 受 microVM 執行階段的探測閘控;適用於互動式終端機——shell 是在機台內執行,而不是在主機上
原生 native macOS(Seatbelt)、Linux 與 WSL2(bubblewrap) 在啟動時探測;Linux 會拒絕一次需要以檔名萬用字元下達寫入拒絕的執行,而 bubblewrap 無法強制執行這一點
無隔離 none 全部 解析時的預設值、終端機的預設值,以及沒有可用後端或設定了 --sandbox off 時的回退

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

microvm 是唯一帶有核心邊界的後端——工作負載確實不在主機上執行。它在任何一個方向上都不會是無聲的回退:在無法啟動 VM 的主機上明確要求它會大聲失敗(「我要的是一台 VM,拿到的卻是自己的筆電」是這套隔離艙唯一承受不起的降級),而只有未指定的預設值才會降級到 native,並且會有一行日誌說明此事。工作階段實際使用的後端會回報給用戶端並標上徽章,因此終端機絕不可能在主機上執行的同時聲稱自己已被封閉。VM 本身的生命週期、映像與對外連線由機台參考介紹。

Windows 沒有原生沙箱。 NativeSandboxAdapter.probe 在每一個不是 macOS 或 Linux/WSL2 的主機上都回傳 available: false。因此 Windows 上的代理派送使用 none。microvm 後端是另一回事:它受 microVM 執行階段的探測閘控,適用於互動式終端機與機台,而不是在主機上包裝 bash。

探測才是決定是否強制執行的那道關。它在伺服器啟動時執行一次。

主機 available 說明/提示
macOS true sandbox-exec(Seatbelt)——命名空間隔離,沒有核心邊界
Linux 或 WSL2 且裝有 bwrap 和 socat 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 檔案。標準派送原則要求這些以檔名萬用字元下達的拒絕,因此原生代理派送目前在 Linux/WSL2 上即便探測到 bubblewrap 也會失敗關閉。它不會以無聲削弱的原則執行。要恢復 Linux 原生派送,需要一個具備建立時檔名規則的後端。關閉沙箱是一種明確的無限制退出選擇,而不是等效的變通做法。

沒有沙箱。行程直接在主機上執行,擁有主機的檔案系統、網路與行程存取權。ACP 與執行環境的 bash 工具仍會取得環境清理與命令原則;經由 NoSandboxAdapter 的 Claude Code 則繼承主機環境。這就是沒有可用後端或設定了 --sandbox off 的主機上,代理派送所回退到的狀態,也是用戶端未指名任何後端時終端機的預設值。

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

當要求 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 保存著已註冊的原始儲存庫簽出,會在每次派送時從工作區的儲存庫解析而來。代理只在按對話劃分的寫入時複製工作樹中工作,因此原始簽出從沙箱內部絕不能可寫——在 macOS 上,Seatbelt 對 $HOME 的一律寫入允許否則會讓它們經由 bash 仍然可寫。它們在每個模式下都會加入機密拒絕寫入集合,chat 也不例外。

每一條拒絕讀取與拒絕寫入路徑,都會同時以它的字面寫法與經符號連結解析後的寫法輸出。核心是拿解析後的路徑來比對拒絕規則,因此針對符號連結路徑寫下的規則永遠不會觸發——而這在 macOS 上是常態而非特例,因為 /tmp 會解析為 /private/tmp,/var/folders/… 會解析為 /private/var/folders/…。包含 * 或 ? 的項目會被當作萬用字元模式,保持原樣;無法解析的路徑則按原樣保留。

欄位 類型 說明
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 正在執行,可以執行命令
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? 原始日誌行,便於除錯