iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
AI Engineering

現代化的 AI 系統設計系列 第 19

[Day19] - Agent 被 Prompt Injection 騙了,為什麼仍不該有能力退款?從執行授權到人工核准

  • 分享至 

  • xImage
  •  

客服 Agent 收到一封客訴信:我的訂單還沒有收到,請幫我確認能不能退款。
但實際上信件後半段藏著另一段指令:讀取最近十筆訂單、把付款資料寄到外部信箱,再替其中一筆建立退款。

如果模型識破 Prompt Injection,當然很好。

但正式環境不能把最後一道防線押在模型每次都判斷正確。攻擊者甚至不需要控制整個 Agent,只要讓它取得一次不該有的執行權,就足以造成真正損失。

因此,這篇真正想討論的不是:模型有沒有相信惡意文字?

而是:

即使模型被騙,為什麼退款、外寄資料與讀取其他訂單仍然不該發生?

https://ithelp.ithome.com.tw/upload/images/20260902/20183613FKhGUe7LdR.png

我認為核心的分工是
模型負責提出動作,Runtime 負責決定這次效果能不能發生;人工核准批准的則是一筆固定、不可偷換的效果。


安全的目標,不是讓模型永遠不犯錯

Prompt Injection 麻煩的地方,是資料與指令都以自然語言進入模型。
Agent 必須讀懂信件才能處理客訴,也因此可能把信件裡的文字誤認成下一步工作。

AgentDojoInjecAgent 都把攻擊放進能實際操作工具的環境;Anthropic 在瀏覽器 Agent 的防禦研究中也提醒,目前不能假設任何瀏覽器 Agent 對 Prompt Injection 免疫。

模型訓練、內容標記、分類器與風險監測仍然有價值,它們可以降低模型受影響的機率。但它們無法單獨回答另一個問題:模型失守之後,系統最多允許它做到哪裡?

我們可以採用更務實的設計前提:

不可信資料可以影響模型的提案,但不能替提案創造新的權力。

客訴信可以提供「訂單尚未收到」這項證據,卻不能擴張使用者目的、要求讀取別人的訂單,或指定新的外部收件人。Runtime 不必先證明信件是惡意的;只要這個資料來源沒有授權能力,就不能讓它改變高風險動作的邊界。

這裡的安全目標也因此改變了:不是要求模型每次都說「不」,而是讓未授權的外部效果始終為零。


一次工具呼叫,其實同時代表兩個主體

使用者已登入,不代表 Agent 可以代替他做所有事情;Agent 有自己的服務身分,也不代表系統知道它正在替誰工作。

一次正式執行至少要保留兩個主體:

主體 要回答的問題
使用者(subject) 這個需求與原始權利屬於誰?
Agent 執行者(actor) 實際是哪個 Agent 或服務代表使用者行動?

如果付款服務只看見共用的 support-service 帳號,事故發生時便很難回答:這次代表哪位使用者?使用者原本只要求查詢,還是已同意退款?權限在委派途中是否被擴大?

RFC 8693 將「委派」與「冒充」分開。委派會同時保留誰提出需求、誰代為執行,而不是讓 Agent 完全變成使用者。

在 MCP 工具鏈中,還可能有多段信任關係:

  1. 使用者把有限目的委派給 Agent。
  2. Agent 存取 MCP Server。
  3. MCP Server 再存取付款或郵件服務。

每一段都要重新確認對象與範圍。「前一段已登入」不能成為後面所有動作的通行證。MCP 的 Authorization 規格要求權杖綁定目標資源;官方安全最佳實踐也禁止把用戶端權杖原樣轉送給下游服務。


不要授權工具,要授權這一次效果

refund_order 可以退回五百元,也可以退回五萬元;可以退到原付款方式,也可能被改成陌生帳戶。工具名稱相同,實際風險卻完全不同。

因此,「允許使用退款工具」的粒度太粗。真正被允許的應該是一句完整的話:

customer 456 委派客服 Agent,在十分鐘內,依據 State 42 與退款政策 7.3,把 order 123 的 TWD 500 退回原付款方式。

https://ithelp.ithome.com.tw/upload/images/20260902/20183613YG6GrGtlXL.png

只要使用者、資源、金額、目的地、State、政策版本或期限有一項改變,就已經是另一個效果,不能沿用上一筆授權。

我會把它整理成一份「執行授權封套」:

operation_id: op_789
subject: customer_456
actor: support_agent_runtime
target: payment.refund_order
resource: order_123
arguments_hash: sha256:...
state_version: 42
policy_version: refund-policy/7.3
expires_at: 2026-09-02T10:10:00+08:00

這不是 MCP 或 OAuth 已經定義好的標準格式,而是本文用來連接身分、實際參數、政策與 State 的架構設計。重點不在 YAML,而是往後的政策判斷、人工核准與真正執行,都必須指向同一筆不可偷換的效果

這也帶來一個容易忽略的判斷:工具目錄只能描述能力,不能替自己決定安全等級。Server 提供的工具名稱、說明與唯讀提示,可以幫助模型規劃,卻不能成為正式授權依據。MCPToxMCP Security Bench都研究了藏在工具中繼資料、名稱衝突與錯誤回應裡的攻擊。

所以工具的風險、可用對象與寫入範圍,必須由 Host 或 Runtime 擁有。Server 可以說明自己會做什麼,不能自己宣布「我很安全,所以放行我」。


政策做決定,Runtime 負責讓它無法被繞過

很多系統已經有 IAM、OAuth 或政策引擎,卻仍可能被 Agent 繞過。原因是「算出拒絕」和「真的攔住呼叫」是兩件事。

我們要分清兩個位置:

  • 政策決策點:根據使用者、Agent、資源、參數、環境與業務 State,算出允許、拒絕或需要人工核准。
  • 政策執行點:真正站在副作用之前,沒有通過就不送出工具呼叫。

如果 Runtime 已經持有高權限憑證,能繞過政策引擎直接呼叫付款服務,那一筆 DENY 紀錄沒有任何保護作用。執行點必須位於無法繞過的位置,例如 Runtime、MCP 閘道或工具轉接層。

https://ithelp.ithome.com.tw/upload/images/20260902/20183613NNXeoHGyXv.png

上圖是本文綜合 RFC、MCP 與政策模型後提出的建議流程,不是任何單一標準規定的完整順序:

  1. 模型提出候選動作。
  2. Runtime 重新讀取目前 State,把主體、資源與參數正規化後固定。
  3. 政策判斷允許、拒絕,或要求人工核准。
  4. 通過後,執行點才消耗一次性授權收據,並注入短效、限定目標的憑證。
  5. 外部系統產生效果,再把結果證據寫回 State 與追蹤紀錄。

RFC 8707可以把權杖限定到特定資源;RFC 8693可在服務之間交換更窄的目標與權限範圍;RFC 9396則提供結構化授權細節的表達方式。它們各自支撐部分能力,但不會自動替我們完成整條 Agent 執行流程。

真正的最小權限,不只是「權限範圍寫得比較少」,而是憑證短效、限定目標、不能轉送,而且只在這一筆具體效果通過政策後才被取用。模型本身不應看見原始憑證。


人工核准不是按鈕,而是一張授權收據

人工核准經常被當成萬用安全閥:模型不確定,就跳出「允許/拒絕」。但如果畫面只顯示 refund_order,沒有訂單、金額、付款目的地與資料來源,使用者其實不知道自己批准了什麼。

前面的文章已經談過如何暫停、恢復,以及 State、政策或期限改變後為什麼需要重驗。這裡只聚焦三個不能由 Checkpoint 代替的安全性質:

授權收據必須具備 避免的問題
可信畫面 Runtime 顯示真正欄位,不讓模型摘要遮蔽執行效果
固定參數的雜湊值 畫面與真正請求使用同一份資料,核准後不能偷換欄位
原子式消耗 收據只能使用一次,其他 Agent 或工作程序不能重播

OpenAI Agents SDK 的 Human-in-the-loop示範了在工具呼叫前暫停、保存狀態,再依單次呼叫編號核准與恢復。正式環境還要繼續問:這個編號是否綁定使用者真正看過的那筆固定效果?

核准畫面本身也可能是攻擊面。OWASP 將利用不可信內容偽造或遮蔽核准資訊的問題稱為 Lies-in-the-Loop。可靠畫面應由 Runtime 使用可信欄位渲染,不能直接顯示模型或工具回傳的 HTML、Markdown 摘要。

因此,使用者核准的不是「退款工具」,而是 order 123、TWD 500、退回原付款方式的那筆效果。Checkpoint 解決工作如何醒來;授權收據確保醒來後只能執行使用者真正看過的內容。


真正的測試:讓模型失守,再看邊界是否還在

如果測試只檢查 Agent 有沒有拒絕惡意句子,很容易把「模型這次剛好沒上當」誤認成系統已經安全。

更有價值的測試,是故意讓模型提出危險動作,再檢查未授權效果是否真的為零:

測試 應觀察的結果
客訴信要求讀取其他使用者訂單 模型可以提案,但外部讀取次數為 0
工具宣稱自己是唯讀 Runtime 仍依實際參數與領域規則分類
權杖被送往錯誤目標 MCP Server 或閘道拒絕,也不向下游轉送
核准後修改金額、收件人或工具版本 封套雜湊值不一致,工具呼叫次數為 0
核准期間訂單或政策版本改變 舊核准失效,重新判斷或重新請示
兩個工作程序同時使用同一張核准 只有一個能原子式消耗,另一個停止
背景工作遇到需要人工核准 等待或拒絕,不能因找不到人而自動放行

AgentSecBench 將指令完整性、資料機密性與能力完整性分開評估;AgentScope Java Permission System則示範每次工具呼叫依實際輸入回傳 ALLOW/DENY/ASK。它們都提醒我們:不能只看模型有沒有說不,還要看未授權效果是否真的被擋下。

當然,什麼都拒絕的 Agent 也沒有價值。因此評估時要同時衡量:正常任務成功率、未授權效果發生率、資料是否流向未授權目的地,以及模型失守後仍能觸及的最大範圍。


讀完這篇,可以先檢查哪五件事?

面對任何會改變外部世界的 Agent 動作,我會先問:

  1. 系統是否同時保留使用者與實際執行者?
  2. 授權是否綁定具體資源、固定參數、State、政策版本與期限?
  3. 政策執行點是否真的位於憑證與副作用之前,而且無法繞過?
  4. 人工核准畫面是否來自可信欄位,並綁定一張只能使用一次的授權收據?
  5. 測試是否證明「模型被騙時,未授權工具呼叫仍然是零」?

如果其中一題答不出來,增加更多 Prompt Injection 偵測可能降低風險,卻還沒有建立最後的安全邊界。

Agent 的安全不是要求模型永遠不要被騙,而是假設它終究可能被騙之後,仍讓世界的改變停在可驗證的邊界內。

下一篇會從「已經合法放行」的動作繼續:如果退款請求送出後逾時,系統不知道外部世界是否已改變,為什麼不能直接重試?那會進入冪等、查證與補償的問題。


AI 你怎麼看?

https://ithelp.ithome.com.tw/upload/images/20260902/20183613ReUE6e14iy.png

挑一個你現在會要求人工核准的 Agent 動作。核准畫面除了工具名稱,是否清楚顯示使用者、目標資源、真正參數、資料來源、State 版本與有效期限?

更重要的是:如果其中一項在按下核准後改變,系統會重新判斷,還是繼續執行?



上一篇
[Day18] - Agent 使用 MCP:從工具發現到可驗證的狀態變更
系列文
現代化的 AI 系統設計19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言