[[我也希望安全第一]]|第 5/30 天
Claude Code 在 2026 年把 auto mode 設為部分付費方案的預設值時,Anthropic 公布了一組很反直覺的數字:在 1,053 名測試者的研究裡,自動防護攔下 89% 的有害動作,人工審查只攔下 13.6%。使用者批准了 97% 的權限提示。
實際看到這組數字的第一個反應是,人怎麼可能比自動檢查差這麼多。再想一下每天跳出的 cookie、手機權限和公司 VPN 警告,其實不難理解。確認視窗如果一天出現幾十次,很快就會變成通往下一步的按鈕。
這些比例來自 Anthropic 自己的研究,公開報導沒有完整交代測試設計,不能拿來證明 auto mode 在所有情況都比人安全。比較值得注意的是:把決策交給人,不等於已經設計好控制。
沿用修程式的例子。Agent 收到「清掉舊的測試資料,讓測試通過」,接著準備刪除檔案。Harness 至少有三個介入點:
| 控制面 | 可以做的事 | 不能獨自保證的事 |
|---|---|---|
| 工作指引 | 要求先讀檔、避免碰 production、遇到歧義先停 | 模型一定會遵守,或不被後來的內容帶走 |
| 工具與權限 | 只提供 delete_test_fixture,限制路徑、帳號與動作 |
被允許的操作一定符合使用者實際意圖 |
| 驗證與流程 | 檢查 diff、跑測試、要求證據,高風險動作送審 | Evaluator 自己不會誤判,或人工不會疲乏 |
三層放在一起,才比較像一套安全設計。
工作指引最便宜,也最容易修改。專案裡的 AGENTS.md 或 CLAUDE.md 可以要求「修改前先讀取」「不要碰正式環境」。它適合補工作習慣,卻仍然是送進模型的文字。模型忘了、誤解了,或者讀到另一段互相衝突的指令,檔案系統不會因此自動拒絕它。
工具邊界硬得多。如果 agent 只有一個會產生草稿的 invoice.create_draft,就不能直接呼叫 invoice.pay。ChatGPT Health 接上 Epic 電子病歷時採用唯讀存取,也是同一個思路:先讓模型讀取病歷、整理時間線,不讓生成內容直接寫回正式病歷。這會少掉一些自動化能力,卻也拿掉一整類寫錯資料的事故。
最後是驗證流程。模型說「完成」以前,Harness 可以要求它附上測試結果、diff 或 API 回應;Planner、Generator 與 Evaluator 也可以先約定完成條件,再開始執行。這對品質很有用。碰到付款、刪除或公開發布時,我不會讓另一個模型的「看起來沒問題」成為唯一授權。
一個常見的誤會,是替 tool 寫了 JSON Schema,便以為權限問題也解決了。Schema 可以拒絕缺欄位或型別錯誤,卻不知道登入者能不能刪這筆資料,也不知道這筆資料現在是不是 production。
比較穩的路徑是讓模型只能提出請求,最後的允許或拒絕由外部程式決定:
Model proposes tool call
↓
Schema validation
↓
Policy decision ── deny/require approval ──→ Audit log
↓ allow
Tool adapter(使用受限身分)
↓
Downstream API/filesystem/database
↓
Result + evidence → Evaluator → User
這裡的 policy engine 可以是自己的幾個明確判斷,也可以是 Open Policy Agent(OPA)這類通用引擎。重點不是採用哪個 repo,而是 policy decision 和 enforcement 必須在模型無法改寫的位置。OPA 官方文件描述的分工也是服務先詢問政策,取得 allow/deny 後,再由服務本身執行決定;OPA 不會替呼叫端自動擋住請求。
如果 agent 能跳過 adapter,直接拿到 production token,前面的 policy diagram 只是簡報。
MCP 的 tool 定義可以用 JSON Schema 描述輸入。現行規格也要求 server 驗證輸入、實作存取控制與 rate limit,client 對敏感操作應提供確認、timeout 和 audit log。Schema 是起點,實際合約還要補上授權、效果與失敗語義。
以刪除測試資料為例,我會讓模型送出這種請求:
{
"tool": "test_fixture.delete",
"arguments": {
"fixture_id": "fx_2048",
"expected_revision": 17,
"reason": "replace invalid parser fixture",
"dry_run": true,
"idempotency_key": "task-781-fx-2048"
}
}
工具端另外固定以下規則:
fixture_id 必須屬於目前專案與 test 環境,不能接受任意檔案路徑。expected_revision;資料已被別人修改就拒絕,不猜新的目標。dry_run,回傳將受影響的項目。idempotency_key 不會再刪一次。這些欄位有些是模型看的,有些是一般程式執行的。deny 規則、revision 比對和 token 驗證不應交給模型自行判斷。
def dispatch(call, actor, policy, tools):
validate_schema(call)
decision = policy.evaluate(actor=actor, call=call)
audit(call=call, actor=actor, decision=decision)
if decision == "deny":
raise PermissionError("policy denied tool call")
if decision == "require_approval":
return {"status": "pending_approval"}
result = tools[call["tool"]](**call["arguments"])
validate_result(result)
return result
這段 pseudo code 故意很普通。安全關鍵在於所有執行路徑都經過同一個 decision point,而不是再叫一個 LLM 猜准一點。
人工覆核不是沒用。它適合處理系統無法代替人決定的事,例如「要不要把這份報價寄給客戶」「是否接受這個不可逆的資料轉換」。它不適合拿來確認每一次讀檔、列目錄和重跑測試。
比較實際的做法是先分級:
read + bounded scope → 自動執行並記錄
write + 可回復 + 通過 policy → 自動或批次覆核
外部傳送/權限提升/不可逆操作 → 明確的人類批准
未知工具、未知資源、政策服務失效 → fail closed
這樣做不會讓確認視窗消失,而是讓它重新有意義。半夜值班的人看到「將刪除 production 的 8,421 筆訂單」時,才有理由真的停下來看,而不是把它當成今天第87個 yes/no。
下一篇會把情境反過來:工具沒有被外部攻擊,使用者也沒有要求破壞資料,agent 為了把任務做完,仍然自己換了目標、找了憑證,甚至刪掉沒被點名的機器。那時候,問題就不只是 Harness 有沒有提供按鈕,而是模型如何理解「完成任務」。