沙箱後端
後端概覽
標題為「後端概覽」的章節| 後端 | 取值 | 平台 | 可用性 |
|---|---|---|---|
| 封閉式 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 的意思是「主機提供後端時就包裝」,而絕不是「要求一定要有後端」。
原生後端
標題為「原生後端」的章節macOS:Seatbelt
標題為「macOS:Seatbelt」的章節使用 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 安裝都內建——不需要額外的工具
Linux:bubblewrap
標題為「Linux:bubblewrap」的章節使用 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仍會啟動按工作階段的代理,但允許清單是空的,因此所有對外連線都會被拒絕。派送會在執行開始時依存取網路規則設定這個值:拒絕會關閉網路,允許與先詢問會讓它保持開啟。
SandboxSpec
標題為「SandboxSpec」的章節傳給 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/…。包含 * 或 ? 的項目會被當作萬用字元模式,保持原樣;無法解析的路徑則按原樣保留。
SandboxBindMount
標題為「SandboxBindMount」的章節| 欄位 | 類型 | 說明 |
|---|---|---|
hostPath |
String |
主機上的絕對路徑 |
guestPath |
String |
沙箱內的路徑(使用原生沙箱時它等於 hostPath;保留它是為了配接器的對稱性) |
readOnly |
bool(預設 false) |
為 true 時以唯讀方式掛載 |
SandboxHandle
標題為「SandboxHandle」的章節由 SandboxPort.launch 回傳:
| 欄位 | 類型 | 說明 |
|---|---|---|
sessionId |
String |
工作階段識別碼 |
backend |
SandboxBackend |
正在執行的是哪個後端 |
state |
SandboxState |
生命週期狀態 |
error |
Object? |
當 state 為 error 時設定 |
details |
Map<String, Object?> |
配接器私有的簿記資訊(工作目錄、PID、設定檔路徑等) |
SandboxState
標題為「SandboxState」的章節| 狀態 | 說明 |
|---|---|
created |
規格已建立,但 VM 尚未啟動 |
warm |
正在執行,可以執行命令 |
active |
目前正在執行命令 |
suspended |
已暫停/已檢查點保存到磁碟以節省 RAM |
destroyed |
已拆除;控制代碼不再有效 |
error |
啟動失敗——請見 SandboxHandle.error |
沙箱事件
標題為「沙箱事件」的章節| 事件類型 | 說明 |
|---|---|
stdout |
來自沙箱的標準輸出 |
stderr |
來自沙箱的標準錯誤 |
exit |
沙箱行程已結束(帶有 exitCode) |
starting |
沙箱正在啟動 |
ready |
沙箱已準備好接受 exec 呼叫 |
killed |
行程被主機終止(OOM、逾時、手動停止) |
violation |
有一條沙箱原則遭到違反(帶有 SandboxViolation) |
SandboxViolation
標題為「SandboxViolation」的章節| 欄位 | 類型 | 說明 |
|---|---|---|
action |
String |
被拒絕的動詞(例如 file-read*、network-outbound) |
target |
String |
被拒絕操作的目標路徑或主機 |
raw |
String? |
原始日誌行,便於除錯 |
- 沙箱與資安
- 沙箱原則
- 護欄
- cc_server CLI:
--sandbox旗標