← 所有文章

你的代理程式有兩個不可信輸入

出自指南: Claude Code Comprehensive Guide

傳統程式只有一條信任邊界:使用者交給它的輸入。你在那條邊上做驗證,事情基本就結束了。代理程式悄悄又加了兩條,而圍繞代理程式建起來的工具大多只把其中一條當真。

第一個新輸入是模型寫的程式碼,由你的程序執行。第二個是模型讀到的工具輸出,然後據此行動。兩者都從你控制不了的地方進入你的系統,一個來自語言模型的取樣分布,一個來自第三方的伺服器,而且兩者一到達就被當作可信的。眼下代理程式安全之所以讓人覺得沒有著落,是因為業界對第一個輸入已經有了可信的答案,卻還在假裝第二個輸入是同一個問題。它不是。

核心要點

  • 代理程式有兩個不可信輸入,不是一個。 由你的執行環境執行的模型生成程式碼,以及由你的模型攝入並據此行動的工具/伺服器輸出。它們以不同方式失效,需要不同的防禦,把它們混為一談正是人們感覺自己有沙箱卻依然可被利用的原因。12
  • 不可信程式碼的執行終於有了可信的隔離答案。 Simon Willison 的 micropython-wasm 在一個帶記憶體和 CPU 限制、可控檔案與網路存取的 WebAssembly 沙箱裡執行模型寫的 Python,這些正是一個真正的沙箱需要的特性,建立在一個「在瀏覽器裡經受了近十年考驗」的基底之上。1
  • 工具投毒不是程式碼執行問題,所以程式碼沙箱對它毫無作用。 藏在 MCP 工具描述裡的惡意指令「對使用者不可見但對 AI 模型可見」,即便被投毒的工具從未執行,它們也能劫持代理程式。23
  • 最深層的問題是連線時與執行時之間的信任缺口。 工具描述只在核准時審查一次;工具回應則直接流入模型的上下文,沒有任何對等的檢查,而惡意伺服器可以在你核准之後修改描述。2

兩個輸入,兩種失效方式

把兩個輸入並排放著看,因為不對稱就是全部要點。

當模型寫了程式碼而你去 exec() 它時,失效方式是程式碼對你的機器做了什麼:讀了不該讀的檔案、打開一個 socket、讓 CPU 空轉到永遠、破壞狀態。爆炸半徑是你的程序以及它能觸及的一切。防禦古老且被充分理解:隔離。把程式碼放到一個碰不到要緊東西的地方執行。

當模型讀到工具輸出並據此行動時,失效方式是那些文字改變了模型決定要做的事。經典意義上沒有任何東西在你的主機上執行;相反,作為「資料」到達的文字被當成了「指令」來解釋,而握著你憑證的代理程式把它執行了。爆炸半徑是你的代理程式被授權去做的一切。再多的程序隔離都幫不上忙,因為代理程式本來就應該能讀那個工具並據此行動。漏洞利用走的是合法路徑。

程式碼沙箱是圍著第一個輸入的一堵牆。它是必要的,而且終於變好了。它對第二個輸入也完全沉默。

終於有了沙箱的那個輸入

2026 年 6 月 6 日,Simon Willison 發布了 micropython-wasm,一個在 WebAssembly 沙箱裡執行 Python 的 alpha 套件,此前他多年嘗試的各種方案都不太合適。1 動機正是第一個不可信輸入:他想在自己的 Datasette Agent 裡執行 LLM 生成的程式碼而不危及主機,用他的話說,「一個有 bug 或惡意的外掛可能弄壞一切或洩露私密資料」。1

它值得關注,是因為它實際滿足的那份清單,而那份清單就是可用沙箱的定義:

  • 記憶體和 CPU 限制,這樣模型寫出的 while True: s += "x" 就無法耗盡主機。CPU 用 wasmtime 的「fuel」機制來限制,Willison 對此很坦率,指出其單位「很難推理」。1
  • 可控的檔案與網路存取,預設拒絕而不是預設開放。
  • 一個乾淨的隔離模型:程式碼在一個請求/回應佇列後面的獨立執行緒裡執行,WASM 直譯器阻塞在一個宿主函式上,直到被交給工作,所以不可信程式碼從不與主機共享位址空間。1
  • 一個有過往紀錄的基底。 他選擇 WebAssembly 是因為它「從一開始就被設計來支援我在意的所有特性,並且在瀏覽器裡經受了近十年的考驗」,選擇 MicroPython 則是因為面向瀏覽器的 Pyodide 不支援伺服器端。1

這種坦率是好的安全工作的標誌。Willison 以 alpha 形式發布它,並寫道他「還沒準備好把它推薦給任何不願承擔重大風險的人」。1 這就是證據門的姿態:只聲稱你已驗證的,一分不多。重點不在於這一個套件就是答案;而在於不可信程式碼的答案的形狀現在清楚且可重現了:一條帶資源限制和預設拒絕 I/O 的真正隔離邊界。第一個輸入正在變成一個已解決的工程問題。

陷阱在於相信解決了它你就安全了。

沒有沙箱的那個輸入

第二個輸入不執行程式碼,所以牆什麼也做不了。2025 年 4 月 1 日,Invariant Labs 揭露了工具投毒攻擊:「惡意指令被嵌入 MCP 工具描述之中,對使用者不可見但對 AI 模型可見。」2 這種不對稱就是漏洞本身:「AI 模型看到完整的工具描述,包括隱藏的指令,而使用者在他們的 UI 裡通常只看到簡化版本。」2 一個在 UI 裡寫著「把兩個數相加」的工具,可以在模型實際消費的描述裡攜帶一條指令:讀取 ~/.ssh/id_rsa 並透過一個隱藏參數把它外洩。2 OWASP 現在把這歸類為一種有名字的攻擊類別。3

有兩個屬性讓它以隔離搆不著的方式變得惡劣:

它不需要工具被呼叫就能觸發。 被投毒的指令住在描述裡,而模型在考慮工具集時就會攝入它,所以即便函式從未執行,攻擊也能落地。3 沒有程式碼事件可以拿去沙箱化。

信任一次授予,之後被濫用。 工具描述在連線時審查,也就是你核准一個伺服器的時候。之後回應在執行時直接流入模型的上下文,沒有任何對等的檢查,而且,按 Invariant 的「抽地毯」發現,「惡意伺服器可以在用戶端已經核准之後修改工具描述」。2 你的一次性核准保護不了任何持續的東西。

那個經典示範不是假設。研究者給一條 GitHub issue 投了毒,注入了文字;當一位開發者讓助理審查開放的 issue 時,代理程式攝入了那條 issue,遵循了隱藏指令,用開發者的權杖讀取了私有儲存庫,並把內容洩露進一個公開的 pull request。3 每一個單獨的動作都是被授權的。代理程式做的正是代理程式該做的事。這就是為什麼程式碼沙箱在這裡無關緊要:沒有任何不可信的東西執行了;只是不可信的文字在一個握有真實憑證的程序裡被提升成了指令。這和 Ken Thompson 在《反思對信任的信任》裡留下的教訓是同一個:危險不在於你檢查的程式碼,而在於你未經檢查就給出的信任。

防禦第二個輸入

如果隔離是針對程式碼的防禦,那麼針對工具輸出的防禦就是把資料與指令之間的邊界當作真實存在的,在執行時,每一次,也就是連線時核准模型跳過的那件事。微軟自己的 MCP 指南也落在同一個地方,建議用能偵測並過濾注入指令的「提示防護」,外加 spotlighting,即「在系統訊息中加入分隔符號,明確標出輸入文字的位置」,讓模型能區分內容與命令。4 具體來說:

  • 在執行時重新檢查描述,而不只是在核准時。 固定住你核准的那份工具定義,並偵測伺服器的描述何時在你腳下發生了變化。抽地毯之所以能成功,只是因為沒人看第二眼。2
  • 明確標記資料/指令邊界。 工具回應是資料,不是指令,而實用的做法就是 spotlighting:在上下文裡給工具輸出加上分隔符號,從結構上告訴模型,這是拿來推理的內容,而不是要遵循的命令。4 它與 MCP 工具的動作級授權配合,這樣即便一個被劫持的代理程式也搆不到那些高後果的呼叫。
  • 對被授權的路徑套用最小權限。 GitHub 那次利用之所以成功,是因為一個代理程式同時握有「讀私有儲存庫」和「開公開 PR」,而最小權限正是微軟點名的對抗這類攻擊的基礎衛生措施。4 Joanna Rutkowska 的透過分區實現安全是架構層面的同一種本能:假定一個域已被攻陷,並確保它搆不到其他域。讀 issue 的代理程式不應該是握著外洩路徑的那個代理程式。
  • 盯住出口流量,因為外洩才是收益所在。 這些攻擊大多終結於同一步:資料離開。一個靜默出口視角能在注入溜過前門之後仍然抓住後果。

這些沒有一個是牆。它們是代理程式本該走的那條路上的門,這是更難、更不令人滿足的那種安全,也是第二個輸入真正需要的那種。

我從中取走的

我要留下的心智模型是:「我的代理程式有沙箱嗎?」是個錯誤的問題,因為它悄悄假定只有一條邊界要守。有兩條。我的模型寫的程式碼需要一堵牆,多虧了 Willison 這樣的工作,砌那堵牆正在變成一項有清楚規格的常規工程任務。我的模型讀的工具輸出需要的是完全不同的東西:一個從不讓資料悄悄變成指令的執行環境,以及一個緊到被劫持的代理程式也做不了多少事的授權模型。把它們混為一談,就是你如何一邊覺得受到了保護,「看,程式碼跑在 WASM 裡」,一邊透過那個沒人裝門的輸入大敞四開。

更深的連續性在於,代理程式與其說發明了一個新的漏洞類別,不如說給一個老漏洞添了新的棲身之處。不可信輸入向來是安全的根源;代理程式時代只是給它開了兩扇新門,一扇你能用隔離鎖上,一扇你只能靠紀律盯著。既要砌牆也要裝門。在一個有兩個輸入的系統上,只有一堵牆,是一份感覺像完整的半份防禦。

常見問題

AI 代理程式的兩個不可信輸入是什麼?

由你的執行環境執行的模型生成程式碼,以及由你的模型讀取並據此行動的工具或伺服器輸出。前者能直接損害主機;後者能用代理程式已經持有的憑證和權限劫持代理程式的決策。它們需要不同的防禦:程式碼用隔離,工具輸出用執行時的資料/指令分離和嚴格的授權。12

程式碼沙箱能防住 MCP 工具投毒嗎?

不能。工具投毒不在你的主機上執行程式碼,它把指令嵌入一個工具的描述裡,讓模型把它當作可信的,而且即便工具從未被呼叫也能觸發。23 沙箱隔離的是執行,所以對一個搭乘代理程式合法、被授權動作的攻擊毫無作用。

什麼是工具投毒攻擊?

由 Invariant Labs 在 2025 年 4 月揭露,這是一種「惡意指令被嵌入 MCP 工具描述之中,對使用者不可見但對 AI 模型可見」的攻擊。2 因為模型看到完整描述而使用者只看到簡化的 UI 標籤,隱藏的指令,比如讀取 SSH 金鑰並外洩,就能劫持代理程式。OWASP 現在把它作為一種有名字的攻擊類別來追蹤。23

什麼是連線時與執行時的信任缺口?

工具描述只審查一次,在你核准一個伺服器的時候(連線時)。之後工具回應在執行時流入模型的上下文,沒有任何對等的檢查,而且惡意伺服器可以在核准之後修改描述(「抽地毯」)。2 修復辦法是在執行時驗證,而不只是在核准時。


來源


  1. Simon Willison,「Running Python code in a sandbox with MicroPython and WASM,」 simonwillison.net,2026 年 6 月 6 日。介紹 alpha 版 micropython-wasm 套件和 datasette-agent-micropython 外掛;動機是「一個有 bug 或惡意的外掛可能弄壞一切或洩露私密資料」;沙箱清單(透過 wasmtime 「fuel」 實現記憶體/CPU 限制、可控檔案/網路存取、獨立執行緒的請求/回應隔離);選擇 WebAssembly 的理由是它「從一開始就被設計來支援我在意的所有特性,並且在瀏覽器裡經受了近十年的考驗」;以及他「還沒準備好把它推薦給任何不願承擔重大風險的人」的警示。 ↩↩↩↩↩↩↩↩↩

  2. Luca Beurer-Kellner 與 Marc Fischer,「MCP Security Notification: Tool Poisoning Attacks,」 Invariant Labs,2025 年 4 月 1 日。原始揭露:「當惡意指令被嵌入 MCP 工具描述之中、對使用者不可見但對 AI 模型可見時,就發生了工具投毒攻擊」;可見性缺口(「AI 模型看到完整的工具描述,包括隱藏的指令,而使用者在他們的 UI 裡通常只看到簡化版本」);「惡意伺服器可以在用戶端已經核准之後修改工具描述」的「抽地毯」發現;以及讀取 ~/.cursor/mcp.json 和 ~/.ssh/id_rsa 的被投毒 add 範例。 ↩↩↩↩↩↩↩↩↩↩↩↩↩

  3. 「MCP Tool Poisoning,」 OWASP Foundation。把工具投毒歸類為一種有名字的攻擊類別:藏在工具中繼資料裡、「即便被投毒的工具從未被呼叫」也會影響代理程式的指令,以及那個 GitHub MCP 範例,一條被投毒的 issue 引導代理程式透過開發者的權杖讀取私有儲存庫並把它們洩露進一個公開的 pull request。 ↩↩↩↩↩↩

  4. Sarah Young 與 Den Delimarsky,「Protecting against indirect prompt injection attacks in MCP,」 Microsoft Developer Blog,2025 年 4 月 28 日。建議使用「利用先進的機器學習演算法和自然語言處理來偵測並過濾惡意指令」的「提示防護」、「在系統訊息中加入分隔符號,明確標出輸入文字的位置」的 spotlighting、對 MCP 元件的供應鏈驗證,以及最小權限之類的基礎衛生措施。 ↩↩↩

相關文章

您的代理沙箱只是建議

攻擊者開了一個GitHub議題,並在Cline的下一個版本中植入惡意軟體。代理沙箱在三個層級上失敗。以下是真正有效的防禦方式。

15 分鐘閱讀

MCP 伺服器是新的攻擊面

50 個 MCP 漏洞,60 天內 30 個 CVE,13 個嚴重等級。工具使用協定是無人稽核的攻擊面——以下是分類與修復方案。

6 分鐘閱讀

Ralph 迴圈:我如何在夜間運行自主 AI 代理

我建構了一套自主代理系統,搭配停止鉤子、生成預算與檔案系統記憶體。以下是失敗經驗與真正能交付程式碼的方法。

9 分鐘閱讀