跳到主要內容
邊界實驗室 · Boundary Lab
正在啟動 Python 環境(首次約 15 秒)...

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_for
  • initiated_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_id
  • request_id
  • session_id 這樣一來, 就算工具最後真的對外建立了 issue, 你也知道:

這是 agent 代 user 執行, 不是單純的 user 手動操作。

Audit log 至少要有五個核心欄位

這一課先給你一個最小集合。 tool call 的 audit log 至少應該有:

  • principal
  • agent_id
  • tool
  • args_hash
  • outcome 這五個欄位的作用不同。 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:

  1. 先驗參數
  2. 再驗 identity binding
  3. 再決定能不能執行
  4. 最後記 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 建 issue
  • initiated_by 是否屬於允許的 agent 群組
  • 這個 principal 與 agent 的組合是否需要額外 approval

也就是說, 沒有 identity binding, 很多 permission policy 根本沒辦法成立。

一張可以帶去 code review 的檢查表

下次你 review tool wrapper 或 action API, 先問這五題:

問題你要找的答案
參數有沒有 runtime validation?不只是 type hints,真的會擋壞輸入
有沒有 enum / 長度 / 上下界檢查?避免模糊字串與離譜數值滑進去
tool call 有沒有 acting_forinitiated_byidentity 是否被顯式綁定
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_forinitiated_by 的授權規則。 可以這樣跟 AI 說:

我有一個會呼叫外部 API 的 tool。請幫我先定義最小 request schema,列出每個欄位要檢查的型別、enum、長度或上下界,再設計一個 wrapper,強制每次呼叫都帶 acting_forinitiated_by,並輸出只含 args_hash 的 audit log。不要存 raw args。

Do

互動示範

DEMO 1可以修改程式碼試玩
DEMO 2可以修改程式碼試玩

挑戰任務

Task 1

請寫一個 safe_tool_wrapper(tool_func)。測試情境:原始 tool 是 create_issue(title: str, repo: str);wrapper 必須先驗 titlerepo 都是非空字串,再記一筆含 principalagent_idtoolargs_hashoutcome 的 audit log。當你用 title=123 去呼叫時,程式最後只印出 blocked

BackNext Lesson →