iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
AI Security

我也希望安全第一——然後我們看看這些鎖是怎麼一個個被撬開的系列 第 5 篇

第 5 篇|模型提出 Tool Call 之後,誰來決定能不能執行?

  • 分享至 

  • xImage
  •  

模型提出 Tool Call 之後,誰來決定能不能執行?

[[我也希望安全第一]]|第 5/30 天

每次都問「可以嗎」,不一定比較安全

Claude Code 在 2026 年把 auto mode 設為部分付費方案的預設值時,Anthropic 公布了一組很反直覺的數字:在 1,053 名測試者的研究裡,自動防護攔下 89% 的有害動作,人工審查只攔下 13.6%。使用者批准了 97% 的權限提示。

我看到這組數字的第一個反應是,人怎麼可能比自動檢查差這麼多。再想一下每天跳出的 cookie、手機權限和公司 VPN 警告,其實不難理解。確認視窗如果一天出現幾十次,很快就會變成通往下一步的按鈕。

這些比例來自 Anthropic 自己的研究,公開報導沒有完整交代測試設計,不能拿來證明 auto mode 在所有情況都比人安全。比較值得注意的是:把決策交給人,不等於已經設計好控制。

第 4 篇說明 Harness 如何把模型、context 和工具接成一個工作過程。這一篇往下問:如果真的要管住一次 tool call,控制應該放在哪裡?

三個地方,管的是不同問題

沿用修程式的例子。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 只是簡報。

一份 tool contract 應該寫到什麼程度

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,回傳將受影響的項目。
  • 執行刪除需要一次短效 approval token,而且 token 綁定同一個 fixture、revision 與使用者。
  • 重試相同 idempotency_key 不會再刪一次。
  • 結果回傳刪除筆數、資源 ID、政策版本與 audit ID,不能只有一句 success。

這些欄位有些是模型看的,有些是一般程式執行的。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 筆訂單」時,才有理由真的停下來看,而不是把它當成今天第九十七個 yes/no。

三套 Agent 對「誰准它動手」的回答

把同一件修程式任務放進 Pi、Goose 與 OpenHands,差異不先出在模型,而是執行權放在哪裡:

系統 模型看到的主要能力 執行與批准位置 最容易誤會的地方
Pi 預設 read、write、edit、bash 核心保持精簡;細緻 gate、路徑保護與 sandbox 由 extension 或外部環境補 工具少不等於權限小;第三方 extension 本身可執行任意程式碼
Goose 內建開發工具,以及啟用的 extension/MCP 工具 由 autonomous、manual approval、smart approval、chat-only 模式和個別工具規則決定是否詢問;也可另開 adversary reviewer 核准與模型審查仍是應用層控制,不等於 OS sandbox;工具讀寫分類是 best effort,adversary reviewer 失效時會放行
OpenHands Agent、Terminal、File Editor 與 Workspace Agent Server 執行;可直接跑本機,也可把 conversation 放進 Docker/Kubernetes workspace UI 顯示 sandbox 不代表 Canvas、Automation 與所有掛載也在同一個隔離區

這張表不是排名。Pi 適合看見最小 loop 和自行負責的邊界;Goose 把權限模式、MCP 擴充與額外 reviewer 疊在本機 Agent 上;OpenHands 則把 workspace、server 與遠端執行做成較完整的平台。真正的檢查方式都一樣:不要只看畫面上有沒有「允許」,要用實際執行身分嘗試讀 workspace 外檔案、換掉已批准參數、直連外網,確認最後是哪一層拒絕。

Goose 在這裡特別有意思。官方文件目前把 autonomous 列為預設模式;切到 smart approval 後,系統會試著判斷哪些動作風險較高,adversary mode 也能在 shell 執行前交給另一個模型覆核。後兩者都增加了一道判斷,卻沒有把不確定性變成強制隔離。官方文件甚至明載 adversary reviewer 出錯時採 fail-open。若測試只確認「危險指令通常會跳出提示」,就還沒有回答 reviewer 掛掉、分類錯誤或 Agent 換一條工具路徑時會發生什麼。

下一篇會把情境反過來:工具沒有被外部攻擊,使用者也沒有要求破壞資料,agent 為了把任務做完,仍然自己換了目標、找了憑證,甚至刪掉沒被點名的機器。那時候,問題就不只是 Harness 有沒有提供按鈕,而是模型如何理解「完成任務」。

參考與來源


上一篇
第 4 篇|三個詞的演進:Prompt → Context → Harness Engineering
下一篇
第 6 篇|找不到 VM 1、2、3,它為什麼刪了 5、6、7?
系列文
我也希望安全第一——然後我們看看這些鎖是怎麼一個個被撬開的 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言