最近在整理一個 Agent 與內部開發平台的查詢整合時,我又碰到一個很容易讓人安心過頭的設計:把新增、修改、刪除相關工具從清單中拿掉,只留下搜尋、讀取與列舉,然後把這個 Agent 稱為「唯讀」。
這樣做當然有價值。少給工具,能降低模型誤選動作的機率,也讓能力範圍比較容易理解。但從技術主管的角度,我不會因此就認定安全邊界已經成立。因為 Agent 能不能寫入,不只取決於它看見哪些 MCP 工具,還取決於它拿到什麼身份、憑證,以及執行環境裡是否存在另一條路。
工具清單是操作介面,不是完整權限模型
假設一個 Agent 只被註冊了查詢 Issue、讀取程式碼與查看 Pull Request 的工具。從模型眼中看,它確實沒有「建立 Issue」或「合併 Pull Request」的按鈕。這是一層有效的能力收斂,我也支持這樣做。
問題是,工具清單只描述平台希望 Agent 怎麼操作,未必描述 Agent 最終能做什麼。
如果同一個執行環境還有終端機,Agent 可能直接呼叫 API;如果檔案系統裡能讀到高權限 token,它可能繞過受限的 MCP Server;如果同一身份能連到另一個未受管制的整合入口,「唯讀」就只是其中一條路的介面設定,而不是端到端的事實。
這和把後台的刪除按鈕從畫面拿掉很像。畫面確實比較不容易誤按,但只要 API 仍接受刪除請求、登入身份仍有刪除權限,我們就不能說系統已經禁止刪除。UI 隱藏不是授權;Agent 的工具過濾也不是。
真正的唯讀,要從憑證開始
我現在會把唯讀 Agent 當成一個需要多層證明的性質,而不是設定檔裡的一個標籤。
第一層是服務端身份。Agent 使用的帳號或 token,本身就應該只有讀取 scope。即使它意外取得未列出的 API 路徑,服務端也必須拒絕寫入。這是最重要的一層,因為權限判斷離資料愈近,愈不容易被上層繞過。
第二層是工具面。MCP、外掛或函式註冊仍應只暴露任務需要的查詢能力。服務端拒絕寫入是底線,工具面收斂則是在底線之前減少誤用、prompt injection 與不必要探索的機會。兩者不是二選一,而是縱深防禦。
第三層是替代路徑。要盤點 Agent 是否能用終端機、通用 HTTP Client、瀏覽器自動化、腳本執行或其他 Agent 間接完成同一個動作。很多「唯讀」設計失效,不是原工具突然變質,而是旁邊一直放著一把萬用鑰匙。
第四層是秘密管理。唯讀 token 不該和管理者 token 放在同一個可讀環境,更不該期待模型「知道不要用」。只要憑證可取得,就要假設某次錯誤路由、惡意內容或工具組合可能碰到它。秘密的可見範圍,本身就是能力邊界。
「呼叫失敗」也是需要驗證的功能
不少團隊會測 Agent 能不能成功查到資料,卻沒有測它能不能成功被拒絕。對權限治理來說,後者同樣重要。
我會安排幾種反向測試:使用同一身份直接呼叫寫入 API,確認服務端回傳拒絕;嘗試透過通用網路工具走替代入口,確認憑證沒有額外 scope;檢查執行環境可讀的秘密與設定;再放入帶有誘導指令的內容,觀察 Agent 是否會尋找旁路。
這些測試不需要等到紅隊演練才做。它們應該像一般 regression test 一樣,在身份、工具、MCP Server、部署映像或網路政策變更後重跑。因為唯讀不是部署當下的一次性承諾,而是一個可能隨環境漂移的系統行為。
稽核紀錄也不能只寫「Agent 使用了查詢工具」。我更想知道它以哪個 principal 執行、實際碰了哪些端點、是否出現被拒絕的寫入嘗試,以及是否曾呼叫未預期的通用工具。沒有這些證據,我們只能證明正常路徑看起來唯讀,無法證明異常路徑被擋住。
把唯讀做成可維護的部署規格
實務上,我會要求每個唯讀 Agent 至少有一份簡單的能力清單:允許的資料範圍、服務端 scope、可用工具、禁止動作、網路出口、可讀秘密,以及對應的拒絕測試。部署時固定整合套件版本,升級後重新驗證工具數量與權限行為,避免上游更新悄悄增加能力。
如果 Agent 與多人共用環境,更要讓唯讀身份和其他高權限工作流分開。不要因為容器、主機或聊天入口相同,就共用一組方便但過大的憑證。方便通常只省下設定的幾分鐘,事後卻很難回答某次操作到底使用哪個身份、經過哪條路。
我仍然會從工具過濾開始,因為它簡單、有效,而且能降低日常風險;但我不會停在那裡。真正可相信的唯讀,是即使模型判斷錯誤、提示詞被影響,甚至主動尋找別條路,系統仍只能讓它讀。
對 Agent 平台來說,「沒有寫入按鈕」只是一個介面特徵;「拿不到能寫入的身份,而且每條旁路都經過驗證」才是安全屬性。當我們能用服務端拒絕、隔離憑證與可重跑測試證明這件事,唯讀才不再是一句讓人放心的標籤,而是可以交給工程團隊長期維護的契約。