護欄——為每個會變更狀態的動作提供同一套政策
Control Center 中的代理可以刪除檔案、推送分支、發佈 pull request, 也可以呼叫外部 API。這些各自屬於不同 種類 的風險,而長期以來, 其中只有一種——shell 命令——有政策約束。護欄的存在,就是為了對代理 能採取的每一個會變更狀態的動作,統一回答同一個問題,無論它是哪個 執行環境或哪個工具所提出:這是被允許的、需要核准的,還是被拒絕的?
一個引擎,涵蓋每一處攔截點
標題為「一個引擎,涵蓋每一處攔截點」的章節政策引擎有且只有一個。凡是動作可能離開系統的位置,都會向它查詢: 內建執行環境的工具登錄表、伺服器端的 MCP 派送器閘門,以及儲存庫操作 所造成的變更。因此,一條規則寫下之後,會同時套用於內建執行環境與 外部代理 CLI——不存在第二條更隱蔽的繞行路徑。較早的、僅適用於 bash 的 命令政策仍然有效:規則可以改為比對命令前綴,而不是動作類別,這兩種 形式共存於同一個儲存區。
不過,這套引擎不能取代作業系統沙箱,沙箱也不能取代它。它們回答的是
不同的問題:護欄決定一次 Control Center 看得見的呼叫究竟能不能發生,
而沙箱則限定由此產生的處理序可以觸及何處。當主機提供後端時,
cc_server 會套用沙箱——但
Windows 不提供任何後端,而 Linux 需要 bwrap 和 socat。
在這類主機上,護欄是最外層的強制執行層,而不是坐落在一層下限之上的
政策;當你決定要把尺度放得多寬時,這一點值得了解。
ActionClass:刻意封閉的分類法
標題為「ActionClass:刻意封閉的分類法」的章節每一個會變更狀態的工具,都會宣告自己屬於哪些 ActionClasses, 取值來自一組封閉的十三種效果類別。其中五種預設會先詢問;其餘的預設 允許,要改變就得靠一條規則。
| 類別 | 內建預設 | 涵蓋內容 |
|---|---|---|
fileDelete |
先詢問 | 刪除檔案(形如刪除的寫入,rm) |
gitPush |
先詢問 | 推送到 git 遠端 |
prCreate |
先詢問 | 建立 pull request |
prPublish |
先詢問 | 發佈審查、合併 PR |
vendorSyncWrite |
先詢問 | 寫入外部工單服務商(Linear、Jira、GitHub 同步) |
fileWriteOutsideWorktree |
允許 | 在隔離工作樹之外寫入檔案 |
gitCommit |
允許 | 建立 git 提交 |
networkEgress |
允許 | 網路出站(網頁抓取、任意 HTTP) |
secretAccess |
允許 | 讀取密鑰或憑證 |
packageInstall |
允許 | 安裝套件(npm、pip、pub、brew) |
processSpawn |
允許 | 產生處理序(shell 以及任何會呼叫 shell 的操作) |
workspaceMutation |
允許 | 變更工作區結構(儲存庫、空間、代理) |
enclosureControl |
允許 | 操控隔離艙(rig):啟動 VM、向它傳送輸入 |
這八個預設允許的類別,是在假設其下存在沙箱下限的前提下選定的。在主機
提供後端的地方,這層下限確實存在;但在 Windows 上,或是在缺少 bwrap
和 socat 的 Linux 機器上,它並不存在——所以如果你希望在那裡為
fileWriteOutsideWorktree 或 packageInstall 加上閘門,就必須自己寫
一條規則。
這個集合是刻意封閉的。新的會變更狀態的工具若不宣告任何類別,就無法 通過棘輪測試,因此分類法只能透過明確、經過論證的決定來擴張—— 「分類法蔓延是這個功能的死因」。十三個類別足以表達人們真正會寫的政策 (未經詢問絕不推送、絕不在工作樹之外刪除、讀取一律允許),又少到 足以讓設定介面保持易於理解。
規則與範圍解析
標題為「規則與範圍解析」的章節一條規則把 (scope, ActionClass | command prefix) → allow | prompt | deny
對應成一個決定。範圍由最具體到最寬泛依序嵌套:
空間 > 代理 > 工作區 > 模式預設 > 內建預設
解析採具體優先:第一個有符合規則的範圍作出決定,解析就此停止。 最嚴格僅是平手時的裁決依據——用於同一範圍內兩條同樣具體的規則之間, 以及合併單一動作所宣告的多個類別時。在同一個範圍內,命令規則以最長的 命令前綴進行比對。在任何地方,允許都壓不過拒絕。
有兩項決定帶有硬性保證:
- 先詢問的失效即拒絕。 在沒有核准者連線的情況下,
prompt會解析為 拒絕。沒有得到回答的問題永遠不算「是」。 - 最嚴格組合。 一個動作若橫跨多個類別,這些類別會以最嚴格的方式 合併;當多個類別都要求先詢問時,一次確認會將它們全部列出。
這裡刻意不設每回合的鎖定機制。每一次工具呼叫都會從頭重新解析政策, 因此同一個動作反覆出現的詢問,是由操作者自己的「記住這個決定」加以 節制,而不是靠任何以回合為範圍的機制。
引數層級規則
標題為「引數層級規則」的章節規則可以約束一個動作的引數,而不只是它的動詞。「可以推送」和
「可以推送到 feature/*」是兩種不同的主張,只有後者才算一種管控——
只授權動詞的能力閘門,正是大多數已公開代理事故背後的共同形貌。
約束可以指名:
- 路徑——glob 模式(
repos/**、!**/.env) - refs——git ref,可用
!取反(['**', '!main']讀作「除 main 之外的任何分支」) - 主機——網路目的地,支援子網域萬用字元(
*.internal) - 指令——命令前綴,依詞邊界比對
- 上限——
maxCount/maxCents,因此「最多刪除 50 個檔案」也能 表達出來
把文法保持封閉且無迴圈,會帶出三項特性,而這三項正是重點:每一條規則 都能化為一句你可以複述的句子、每一次拒絕都能指出符合的約束,而整套 政策可以被列舉——你可以在代理執行之前就看見它能做什麼。
三條要緊的規則:
- 在同一範圍內,受約束的規則比未受約束的規則更具體,因此
「拒絕推送到
main」會勝過「允許推送」,沒有人需要去思考順序問題。 - 限制型規則(deny/prompt)在請求觸及它所指名的任何一個值時 就會生效;寬容型規則(allow)則只有在請求完全落在其中時才 生效。若把兩者讀成同一回事,代理就能把一個無害的路徑與被禁止的路徑 湊在一起,藉此洗白後者。
- 若某條規則指名了請求完全沒有提及的面向,這條規則既不會被套用,也 不會被略過:它會升級為一次核准。默默略過會讓每一條受保護分支 規則,對不回報自身 ref 的工具形同虛設;默默拒絕則會擋下擷取器無法 描述的每一次推送。在沒有核准者連線時,既有的失效即拒絕規則會把 這個升級變成拒絕。
命令規則能攔到什麼、不能攔到什麼
標題為「命令規則能攔到什麼、不能攔到什麼」的章節commandPrefix 規則依詞邊界比對:git push 涵蓋
git push origin main,但不涵蓋 git pushx。它不會正規化 shell。
git push(兩個空格)、cd sub && git push 和
GIT_DIR=… git push 都是不同的字串,沒有任何一個會符合關於 git push
的規則。
這是一項真實的限制,而且被明白說出來,而不是粉飾過去:命令規則只是
操作者用來形塑代理日常行為的便利工具,不是用來對抗具敵意代理的
邊界。真正的邊界是效果類別(由工具宣告,且無法改寫拼法)、政策儲存區
底下的模式指令網,以及兩者之下的作業系統沙箱。一條解析為 allow 的
命令規則,永遠不會放寬其中任何一道——模式指令網會在後續執行,而且
它的 deny 具有最終效力。
常駐核准
標題為「常駐核准」的章節用「記住這個」來回應一次詢問,會寫入一條真正的政策規則:有範圍
(空間/代理/工作區)、受引數約束(核准推送到 feature/login
授予的是 feature/**,而不是 main),並且會在明確的 TTL 到期時
自我撤銷。答應接下來幾個小時,並不等於默默改寫你的永久政策;它所
寫入的規則會落在同一個儲存區,來源標記為 remembered 並附帶到期
時間,因此可以提早撤銷。
強制執行層級
標題為「強制執行層級」的章節每一條規則都帶有一個層級:
- advisory——允許,但記錄規則曾經符合。這是導入的路徑:先在 advisory 模式下推行一條嚴格規則,讀一個星期的稽核軌跡,看清楚它 原本會擋下什麼,然後再將它升級。少了這個層級,嚴格的政策永遠 不會被開啟。
- soft——拒絕,但持有覆寫權限的人可以覆寫,並需記錄理由。覆寫 本身也是一次受稽核的事件。
- hard(預設)——拒絕。沒有任何東西能覆寫它。
受管理層級
標題為「受管理層級」的章節安裝的操作者可以釘住某些政策,任何工作區管理員都無法放寬。受管理規則
存放在伺服器端,並與每個工作區自身的規則鏈按最嚴格的方式合併——
它們並不是位於規則鏈開頭的一個範圍,因為一條位於鏈首的受管理 allow
會讓安裝層級覆寫工作區的拒絕,而這與箝制機制的用意正好相反。因此,
受管理規則只可能收緊,不可能放寬。
把 CC_SERVER_MANAGED_POLICY 指向一個 JSON 檔案,其優先順序會完全
高於已儲存的規則;正因如此,操作者才能釘住一種任何管理介面都無法翻轉
的態勢——這也是「我能阻止我的開發人員停用安全控制項嗎?」的答案。
自主程度旋鈕是一套設定檔,而不是一套平行系統
標題為「自主程度旋鈕是一套設定檔,而不是一套平行系統」的章節依空間設定的自主程度旋鈕,是建立在這一套相同政策儲存區之上的具名
設定檔,而不是第二套機制。它的三種設定在儲存與傳送時分別為
proposeOnly、actWithApproval 和 actFreely(連字號的形式僅用於
介面文案,autonomy.setForSpace 會拒絕其他任何值)。未設定時的行為
等同於 actWithApproval。
每一種設定對已解析出的決定會做的事:
proposeOnly直接拒絕所有受閘門管控的工具。代理仍然可以推理與 回覆;它只是無法行動,而它的訊息會說明原因。actWithApproval(以及未設定)就是上文所述的失效即拒絕核准 閘門。actFreely會對一切未解析為硬性deny的操作預先核准。
最後一項值得直白說明,而不是輕描淡寫:在 actFreely 之下,prompt
決定不會升級給核准者——它會直接獲得允許,而且不會詢問任何人。
只有明確的 deny 規則能在這個旋鈕下存活。因此 actFreely 是刻意
授予的自主權,而不是圖方便的設定;而失效即拒絕規則所保護的,正是真正
要緊的那種情況(actWithApproval,也就是預設值)。
代理之間的委派,在 delegate_task 這一處攔截點由全部四道防線把守:
深度上限(預設 3)、循環偵測、自主程度上限(受委派者行動時的自主
程度永遠不能高於其委派方——權限無法藉由把工作交給自主程度更高的代理
來洗白),以及預算封套(委派會從委派方的剩餘預算中扣減,且無法
再產生更多預算)。任何一項被觸發都會遭到明確拒絕,並把該防線的理由
原樣傳回給代理。
配接器誠實度矩陣
標題為「配接器誠實度矩陣」的章節並非每個執行環境都允許 Control Center 攔截每一個動作。外部代理 CLI 會在處理序內原生執行某些工具,那裡無法設置任何閘門。與其假裝並非 如此,每個配接器都帶有一份誠實度矩陣:針對每個配接器,說明 Control Center 是否篩選工具面、是否攔截工具呼叫、是否檢視完成契約、 能否看見執行器自帶的工具,以及處理序內的工具是否由沙箱設定檔涵蓋。
在信任某項設定之前,有兩筆項目值得一讀。內建執行環境聲明,其處理序內
的檔案工具不受沙盒約束——工具面和這套政策引擎是它僅有的檔案系統
邊界。Claude Code 則聲明它自身的工具呼叫無法被攔截,因為它是以
--dangerously-skip-permissions 啟動的;Control Center 看得見它的
mcp__* 呼叫,以及經由鉤子的 Bash 命令列,而它自帶的讀取、寫入與
編輯工具則在無人察覺的情況下執行。
設定畫面會顯示這份矩陣,因為對一套護欄系統而言,唯一不可原諒的失敗, 就是宣稱擁有並不存在的涵蓋範圍。
在政策咬人之前先探測它
標題為「在政策咬人之前先探測它」的章節護欄位於 設定 → 工作區 → 代理權限
(/workspaces/<id>/settings/workspace/permissions)。這個畫面包含
用來撰寫規則的政策矩陣、政策範本、假設情境探測器、授權稽核軌跡,以及
配接器誠實度矩陣。
探測器接受一個動作類別(或一條指令)與一個範圍,並顯示是哪一條規則 作出決定、以及為什麼。由於解析是純粹且確定性的——同樣的動作、同樣的 範圍、同樣的決定——探測器給出的答案,就是代理將會得到的答案。
相關概念
標題為「相關概念」的章節- 沙箱與資安:真正約束代理執行的是什麼,以及這套引擎底下的作業系統下限能延伸到多遠
- 多人協作——身分、成員資格與線上狀態:空間、主體,以及自主程度旋鈕所在的位置
- 設定護欄:在實務中撰寫與測試規則