Tool Misuse 與 Identity Abuse 防禦
能呼叫 tool,不代表它懂 contract。能拿 user token,也不代表它就是 user。
Hook
你做了一個 GitHub assistant。 它可以幫使用者開 issue。 也可以幫忙整理 bug 描述。 某次測試時, 你發現它學會了一件怪事。 它先從上下文裡 把 system prompt 套話套出來。 再拿同一組 token
替 user X 在 GitHub 上發了一個 issue。 問題是, 那個 user 根本沒有按「送出」。 只是 agent 自己判斷 「這看起來應該幫忙建立 issue」。 你回頭看 audit log。 最糟糕的地方不是它發錯內容。
而是 log 上寫的像是: 「user X 建立了 issue」。 agent 自己的痕跡很淡。 你很難分清楚:
- 是 user 要求的
- 是 agent 自主決定的
- 還是 tool contract 本身太鬆 這就是兩種問題疊在一起:
- tool misuse
- identity abuse 前者是:
agent 傳了不該接受的參數, 或用錯了 tool。 後者是: agent 明明是用自己的決策在做事, 對外卻看起來像 user 本人。 這一課要做的, 就是把這兩個洞補成工程規則。
Learn
先說最短版本
tool function 不是聊天介面。 它是 contract。 只要 contract 鬆到 什麼亂格式都能進, LLM 遲早會傳奇怪的東西。 同時, 每次 tool call
也都不該只有「做了什麼」。 你還要知道:
- 為誰做
- 誰發起
- 傳了哪種參數
- 最後結果如何 這就是這一課的主線。
Identity Abuse:表面上像 user,實際上是 agent
OWASP ASI03 把這一類問題叫做 Identity Abuse。 你不用背編號。 但你要看懂現象。 它最危險的版本是: agent 用自己的推理 決定去做一件事, 但外部系統只看到 user 的身份。
例如:
- agent 拿 user token 自己決定開 issue
- agent 拿 user session 自己決定送出表單
- agent 代 user 對外發言,卻沒有標出是 agent 行為 這種設計會讓責任邊界整個模糊。 因為事後你很難回答: 到底是 user 發起, 還是 agent 自行推進。
Tool misuse 的根因,常常只是 contract 太鬆
很多團隊一開始會把 tool 寫得很「方便」。 例如:
-
什麼型別都先收
-
字串長度不限制
-
enum 不檢查
-
None或空字串都默默補預設 這樣做在手寫程式時 也許暫時能跑。 但 LLM 不是可靠的 typed caller。 它很可能會傳: -
title=123 -
repo=["org/app"] -
priority="super-high" -
amount=-5 -
超長字串
-
少欄位
-
多欄位 如果 runtime 不擋, 你就是在把「模型猜的參數」 直接送進 side effect。
Tool input validation:signature 是 contract
所以第一個原則很簡單: tool 的 function signature 必須被當成 contract。 不是參考用。 最小要求至少包含:
- 強型別驗證
- 欄位完整性驗證
- 數值上下界
- 字串長度限制
- enum 限定 你可以用:
- Pydantic
- dataclass + 手動驗證
- schema validator
工具選哪個不是重點。 重點是: runtime 一定要擋。
為什麼不能只靠 type hints
這也很常見。
很多工程師會說:
「我函式簽名不是已經寫 title: str 了嗎?」
但 Python type hints
預設不會幫你在 runtime 檢查。
LLM 傳 123
進來時,
如果你沒有顯式驗,
它就一路滑進去了。 所以 type hints 只是文件。 不是 guard。 真正的 guard 要在 runtime 做。
最小版本的 request schema
先看一個很小的資料結構:
這段 code 很普通。 但它體現的是一個重要態度: tool 收到的不是「大概長這樣」的參數。 而是必須符合一個明確 schema。 沒有通過, 就不做 side effect。
Identity binding:每個 tool call 都要綁 principal
第二個原則, 是 tool call 不能只有 args。 還要顯式帶出 identity binding。 最小版本至少包含兩個欄位:
acting_forinitiated_by它們代表的是不同東西。acting_for是在說:
這次行為是替誰執行。
initiated_by
則是在說:
這次行為是誰發起的。
這兩個欄位分開,
你才看得懂一筆行為的責任鏈。
反例
最糟的設計像這樣: agent 直接拿 user X 的 token。 只要它覺得該做, 就用那個 token 出去做事。 外部系統看到的全是 user X。 agent 自己幾乎隱形。 這就是 identity abuse 最好發生的土壤。
正例
較好的設計會像:
acting_for="principal:user-x"initiated_by="agent:github-helper-01"必要時, 還可以再加:approval_idrequest_idsession_id這樣一來, 就算工具最後真的對外建立了 issue, 你也知道:
這是 agent 代 user 執行, 不是單純的 user 手動操作。
Audit log 至少要有五個核心欄位
這一課先給你一個最小集合。 tool call 的 audit log 至少應該有:
principalagent_idtoolargs_hashoutcome這五個欄位的作用不同。principal是在說替誰做。agent_id
是在說誰真的做了。
tool
是在說呼叫了哪個 capability surface。
args_hash
是在說這次參數的可比對指紋。
outcome
則是在說:
成功、
失敗、
blocked、
partial side effect
是哪一種結果。
為什麼是 args_hash,不是 raw args
這點非常實務。 很多人第一反應會想: 「那我就把整包 args 寫進 log。」 但 tool args 常常包含敏感資料。 例如:
- email 內容
- issue 內文
- API key 片段
- 內部文件摘要 如果你把 raw args
直接寫進 audit log,
你只是把資料邊界問題
搬到另一個地方。
所以比較實際的做法是:
記 args_hash
或做 selective redaction。
你保留可追蹤性,
但不要把原文灑出去。
Wrapper 要同時做 validation 與 audit
第三個原則, 是別把 validation 和 audit 分到太遠的地方。 因為那樣很容易一個有做, 另一個漏做。 比較好的做法是把它包成 wrapper:
- 先驗參數
- 再驗 identity binding
- 再決定能不能執行
- 最後記 audit log 看一個最小骨架:
這段 code 還很小。 它還沒做:
- exception audit
- permission check
- outcome=blocked
- log sink 但它已經把最重要的順序立起來了。
Validation 不只是防惡意,也是在防模型犯笨錯
這一句值得記。 很多人一聽 validation 就只想到 attacker。 但在 agent 系統裡, 你更常在防的是:
- 模型格式猜錯
- planner 漏欄位
- parser 解析錯型別
- tool selection 搞混參數 換句話說, validation
不只是安全機制。 也是可靠性機制。 它會把「看起來像成功,實際上語義錯了」 的那種 bug 提早變成顯性失敗。
Identity binding 也不只是稽核,而是授權前提
很多團隊把 identity field 當成 audit 附件。 這不夠。 在較成熟的設計裡, identity binding 本身就是授權判斷的輸入。 例如:
acting_for是否真的有權對這個 repo 建 issueinitiated_by是否屬於允許的 agent 群組- 這個 principal 與 agent 的組合是否需要額外 approval
也就是說, 沒有 identity binding, 很多 permission policy 根本沒辦法成立。
一張可以帶去 code review 的檢查表
下次你 review tool wrapper 或 action API, 先問這五題:
| 問題 | 你要找的答案 |
|---|---|
| 參數有沒有 runtime validation? | 不只是 type hints,真的會擋壞輸入 |
| 有沒有 enum / 長度 / 上下界檢查? | 避免模糊字串與離譜數值滑進去 |
tool call 有沒有 acting_for 與 initiated_by? | identity 是否被顯式綁定 |
| audit log 有沒有五個核心欄位? | principal / agent_id / tool / args_hash / outcome |
| log 有沒有偷存 raw sensitive args? | 不要為了稽核再造一次資料外流 |
| 如果這五題裡有兩題答不出來, | |
| 那通常不是小瑕疵。 | |
| 而是整個 tool surface | |
| 還沒準備好接 LLM。 |
這一課你先帶走兩句話
第一句:
tool signature 是 contract,不是建議。 第二句: 每次 tool call 都要同時記住「為誰做」和「誰發起」。 最後一課, 我們會把前面三課接起來, 做一個最小 capability-based harness。 讓你看到:
permission check、 tool wrapper、 audit log 真的可以在同一段程式裡一起工作。
AI 協作:學了這個,跟 AI 怎麼配合?
這個技能在 AI 協作中的定位, 是讓你要求 AI 幫你把 tool contract 補完整, 而不是只讓它生出一堆會呼叫 side effect 的鬆散函式。 你的人類優勢:
- 只有你知道哪些欄位真的屬於敏感參數,因此只能進
args_hash或 redacted log,不能原文進 audit。 - 只有你能決定某個 agent 是代表 user 做事,還是代表 system 做事,這直接影響
acting_for與initiated_by的授權規則。 可以這樣跟 AI 說:
我有一個會呼叫外部 API 的 tool。請幫我先定義最小 request schema,列出每個欄位要檢查的型別、enum、長度或上下界,再設計一個 wrapper,強制每次呼叫都帶
acting_for和initiated_by,並輸出只含args_hash的 audit log。不要存 raw args。
Do
互動示範
挑戰任務
請寫一個 safe_tool_wrapper(tool_func)。測試情境:原始 tool 是 create_issue(title: str, repo: str);wrapper 必須先驗 title 和 repo 都是非空字串,再記一筆含 principal、agent_id、tool、args_hash、outcome 的 audit log。當你用 title=123 去呼叫時,程式最後只印出 blocked。