上一篇講那條企業內的 agent flow,Slack bot 進來、gateway 在中間、domain service 在下面。中間那層 gateway 我只簡單的寫了一行就帶過去:認證、caller attribution。
當時其實有個重點值得深入研究一下:gateway 真正重要的功用是「進行 identity 交換的地方」,這篇就是接著說為什麼需要這樣做,以及 agent identity 的重要性。
當時在設計 in house agentic AI 的時候,最開始的想法是趕快做出 MVP ,所以不得不跳過許多重要的部分,其中有很大一塊就是 agent identity - 一個 AI agent 如何獲取有效的身份並有辦法被驗證 ?
回到我們的 slack bot,今天有個問題我們沒辦法回答:當這個 slack bot 代替某個 engineer 去查雲端後端 service 狀態的時候,這個「誰」如何知道?我有辦法知道嗎 ? 如果今天有兩個不同的人同時在用這個 bot,log 分得出來嗎?如果 agent 再去叫另一個 agent,那條線要怎麼接?
當今天我們實作的 Agent 只是像是 chat bot 或者查詢 (read-only),有可能造成的破壞性相對較小,e.g. 不正確的回答、資料不小心洩漏。但如果給予了 write permission ——重開機器、改設定、送訊息、部署—— 這就是今天這篇文章要探討的部分。
Auth flow 多了一個主角
傳統的授權模型其實很好懂。使用者登入,系統知道是誰,判斷他能不能做這件事。Service-to-service 也差不多,一個 service account 配一組權限,就差不多是這樣。
但這個 model 在 AI Agent 出現之後 (特別是 durable agentic AI),中間多了一個新的角色:
Alice (人類工程師) ── 委託 ──> Agent (AI 小幫手) ── 呼叫 ──> MCP tool/其他工具
今天試想一個 use case: Alice 有權限並想要重開 remote device。但在這個委託情境裡,Agent 不該靠自己的常駐權限重開 device,而應該使用 Alice 這次委派的權限。
所以 request 打到 tool 那一端的時候,我們要問的不是:
這個 agent 有沒有權限重開 remote device?
而是:
Alice 有沒有授權這個 agent,在這段時間內,重開這一台 remote device?
要特別注意: 這兩句實際的作用域相差很多。前者是把權限給了 agent;後者是委託,只給 agent 這次需要的那部分,而且會過期。
這就是我們要討論的 Agent identity 這件事 - 我們需要知道到底 agent 的權限來自於哪裡。
那不如給一個 service account 就好了 ?
我想最直覺的做法就是這個:開一個 service account (svc),把 agent 可能需要的權限都掛上去,打完收工。
這絕對可以 work,而且一開始非常方便。我自己也是先這樣搭起來。
不過,問題是會慢慢長出四個東西。
一、audit log 說不出是誰。 這就是我當初在 gateway 留那個 email/owner 欄位的原因。最主要的原因就是回答:「這個動作到底是誰授權的」,這一個問題。
二、權限是常駐的,不是跟著 task 走的。 Agent 為了能重開 remote device,拿到的是「可以重開機器」這個能力。但是如果它下一秒去做一件完全不相干的事,這個 agent 依然有這個 permission,並且能隨時重開 remote device。
三、blast radius 可能擴大到那個帳號能做的所有事。 Agent 被 prompt injection 影響的時候,如果沒有其他限制,受影響的範圍可能擴大到那個 service account 本來就有的全部權限。
四、policy 判斷可能完全正確,但它吃進去的名字是假的。 這個最容易被忽略,後面單獨講。
身分不是一件事,是四個問題
我一開始對於 agent identity 這件事並沒有很深入的去看。後來想到一個比較有用的做法,是把 使用者 > agent > tool 這個 flow 拆開來看,看裡面藏了幾個不同的問題。
至少四個:
| 問題 | 誰來回答 | |
|---|---|---|
| 0 | 是誰連進來的? (是哪一個 end user/agent) | OAuth / OIDC、mTLS |
| 1 | 這是哪個程式? (跑的是不是核准過的那份 workload) | SPIFFE / SPIRE、attestation |
| 2 | 它代表誰? (那個人的授權書,在不在、還有效嗎) | 授權服務;RFC 8693 的 act 負責把關係寫下來 |
| 3 | 這次動作核准嗎? (授權範圍裡到底有沒有這個 action) | OPA / Cedar 之類的 policy engine |
混在一起是最容易把設計搞錯的地方。
最常見的一個問題是:「所以到底要用 SPIFFE 還是 OAuth token exchange ?」- 這問題本身不夠精確!
SPIFFE 回答的是問題 1 —— 這是哪個 workload。RFC 8693 處理的是問題 2 的表達方式 —— 怎麼把「A 代表 B 執行某個動作」寫進一個 token。兩個問題都要回答,但不一定得同時採用這兩套技術,因為「我很確定你是哪支程式」跟「我很確定你現在有權做這件事」根本是兩回事。
另一個更常見的混淆,是把問題 0 跟問題 2 連在一起。這兩個是:
- 0 是 authentication:現在跟我講話的這個對象 (agent)。
- 2 是 delegation:這個對象說它代表 Alice,並且我有可信的授權證據,能確認 Alice 真的授權過。
所以理想狀態長什麼樣
如果上面四個都真的回答好,一次 tool call 大概該有這些性質。
每次都重新驗。 不是登入一次之後整個 session 都相信。Credential 會過期、會被撤銷,而 agent 的 task 常常跑好幾個小時,所以前面驗過一次,不代表現在還成立。
權限是委派得到的,而且只能愈來愈小 (least privilege)。 Alice 授權「重開 device group A」,agent 再 delegate 給 sub-agent 的時候,sub-agent 只能拿到更窄或相等的,不能突然變成「重開所有機器」。這件事叫 attenuation。而且需要fail closed - 當你不確定它的權限有沒有變大,就當成有。
有期限。 授權寫清楚到什麼時候為止。時間到還沒做完就重新要,而不是因為這個 task 曾經合法過,所以永遠合法。
能撤銷。 而且 revoke 不能只是在 database 改一個欄位。衍生出去的 credential、cache、其他 delegation state 都需要能在 revoke 這個動作發生的時候被撤銷。
判斷在 tool boundary,由程式做。 不是在 system prompt 裡寫「請不要做危險的事」,而是模型之外有一段 deterministic 的程式,只回答 Yes or No。
無法 bypass。 這個 rule 比前面全部都重要。如果 agent 的 runtime 還有另一條路可以不經過以上所說的檢查而直接使用 tool,這會產生 security issues。
Audit log 要分得出證據的強弱。 「這次 request 的 caller 是 X」跟「這條 delegation chain 宣稱源頭是 Alice」不是同一種強度的事實,不要最後全部塞進同一個 user 欄位。
一次 tool call 該經過什麼
把上面這些落到 request path 上,大概是這樣。有些檢查可以由可信的授權服務先完成,tool 再驗證它簽發的結果:
Client(agent 或 app)
|
| 帶著自己的憑證
v
--------------------------------------------------
1. 認證 caller <-- 問題 0
驗簽章 / issuer / audience
驗演算法(以伺服器允許清單為準,不能只信 token 宣告)
驗有效期
這個 subject 是我服務的對象嗎
--------------------------------------------------
2. 驗 workload 憑證 <-- 問題 1
這支程式是不是核准過的那份
attestation evidence 對不對
--------------------------------------------------
3. 解析授權鏈 <-- 問題 2
這個 caller 對應到哪一份授權
確認授權來自可信來源,委託關係是否有效
需要逐跳驗證的設計,是否完整檢查了授權鏈
每一跳的有效權限都不超出上一跳嗎 (attenuation)
這次的動作和資源在範圍內嗎
--------------------------------------------------
4. Policy 判斷 <-- 問題 3
可以嗎(誰, 動作, 對象)
--------------------------------------------------
5. 真的去呼叫工具
--------------------------------------------------
6. 寫 audit log
驗過的 caller / 宣稱的 principal / 工具 / 結果
這裡要先講一個我自己原本搞錯的地方: RFC 8693 的 act 可以記下現在是誰代表誰,巢狀 act 也能留下一整條歷史。但我原本以為那條歷史脈絡就是步驟 3 的答案,去翻規格才發現不是:
For the purpose of applying access control policy, the consumer of a token MUST only consider the token’s top-level claims and the party identified as the current actor by the
actclaim. Prior actors identified by any nestedactclaims are informational only and are not to be considered in access control decisions.
也就是說,那條鏈是給人事後看的,不是拿來當場做判斷的。步驟 3 裡面提到的 attenuation,需要自己設計、自己執行。
這裡面有三個地方特別需要注意:
認證要在最前面
檢查的順序本身就是防護的一部分。
假設 request 進來先解析 tool name,找不到就回「沒有這個工具」,找到才開始認證。那兩種回應合起來就是一個 oracle:一個根本還沒通過認證的人,可以一個名字一個名字試,慢慢問出這個系統到底掛了哪些能力。所以連「這個 tool 存不存在」,有時候都不該在認證之前透露。
policy 判斷機制
這是我覺得最容易出錯的地方。現在大家都同意權限不能交給模型決定,所以最後會寫成一段 deterministic 的判斷:
可以嗎(
誰 = "alice",
動作 = "重開機器",
對象 = "A 群組"
)
方向完全正確。但跑到這一行的時候,那個 "alice" 是從哪來的 ?
如果它來自 conversation history、session state、或任何模型寫得到的地方,那這是一個可能早就被改過的 input 也就是來源不可信任。Prompt injection 不需要打敗那道門,它只要改掉通行證上的名字。
所以步驟 1 確認 caller,步驟 3 確認它代表的 end user。送進 policy 的兩份身分都必須來自可信的認證與授權資料,並一路被護送到真正呼叫 tool 的那一刻,中間不能讓模型改寫。
縮小範圍的 permission 其實不好定義
Agent 再 delegate 給 sub-agent 的時候,sub-agent 不該拿到更多權限。概念上超簡單。
真的要寫 enforcement 就沒那麼簡單了,因為你得先定義 如何 narrow scope:
- 資源:
https://api.example/projects/app包含.../app/issues嗎?那.../apple呢?第二個顯然不該算,所以不能只做字串 prefix,要比到路徑分隔。 - 動作:
update_issue比issue.*窄嗎?那 action 的命名空間要怎麼定義? - 條件:父層寫「只能在上班時間」,子層沒寫。這算一樣還是變寬?
如果子層沒有繼承父層的限制,最後一個的答案就是變寬。要比的是最後生效的權限,不能只看子層文件上寫了什麼。
所以 attenuation 不是一句「child scope 必須是 parent scope 的 subset」就結束,你得真的去定義什麼叫 subset。而原則還是同一條:fail closed - 無法判斷就拒絕。
這套機制解決不了什麼
這裡刻意留幾個邊界:
Bearer token 被偷走還是能用。 步驟 1 最直覺的做法就是收一個簽好的 token 進來驗,那只解決一半 - 被偷走的 token 在過期以前,從其他機器拿來用,也可能通過檢查。要真的把 credential 跟持有者綁在一起,需要 DPoP(RFC 9449) 或 mTLS(RFC 8705) 這類 sender-constrained 的機制。
Task 跑到一半掛掉,如何重新要回權限? Agent 的 task 常常跑好幾個小時,所以大家都希望 runtime 能 durable execution。但如果 runtime 的恢復流程假設權限是環境給的,而且相對穩定,這裡就會碰到問題。一旦授權變成一份會過期的文件,這個假設就不再成立。重啟之後手上那份可能已經過期,而授權的人也不在線了。重新去要一次權限、自動延長、停下來等到人類來授權,三條 path 各自都有不同的代價。
Audit log 是 agent 自己寫的。 如果 runtime 本身被 compromise,它同時也是自己歷史紀錄的作者。Identity chain 做得再完整,都沒有辦法解決這件事。
Agent identity 還沒有一個完整的標準。 上面描述的每一塊都有成熟的 building block:OAuth、SPIFFE、policy engine、sender-constrained token、workload attestation。但把它們組成「agent identity」這整件事:delegation 怎麼表示、attenuation 怎麼比、long-running task 怎麼辦、信任怎麼跨 runtime 傳、等等,目前業界並沒有一個統一的標準。
中間那兩個 — 權限在 task 中途失效、以及 audit log 的作者就是被稽核的對象 — 都不是在 identity 這一層能解決的。下一篇從這裡接下去,談執行的那一層跟授權的那一層為什麼必須分開。
參考資料
身分與授權的基礎元件:
- IETF, RFC 8693 — OAuth 2.0 Token Exchange:
actclaim 怎麼表達「A 代表 B 行使」,delegation 的標準詞彙 - IETF, RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP):把 token 綁到持有者的金鑰上,降低只有 token 外洩時被冒用的風險
- IETF, RFC 8705 — OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens:一樣是 sender-constrained,走憑證而不是應用層
- IETF, RFC 7519 — JSON Web Token:token 格式本身
Workload identity:
- SPIFFE / SPIRE 概念文件:workload attestation 到 SVID 的完整流程,回答「這是哪支程式」
- SPIFFE ID 與 SVID 規格
Policy enforcement:
- Open Policy Agent:把授權判斷外部化成可測試的 policy
- AWS Cedar:另一套 policy language,設計上強調可分析性
Agent 跟工具之間:
- MCP Authorization 規格:HTTP transport 有一套以 OAuth 2.1 為基礎的授權規範(OPTIONAL,stdio 明確不適用)。它管的是 client 對 server 這一段,而且明文禁止 token passthrough、點名 confused deputy。但完整的 workload identity、多跳 delegation 與 per-task policy,仍需要額外設計
- MCP 2026-07-28 spec 說明:protocol core 轉向 stateless 的理由
延伸:
- OWASP Top 10 for LLM Applications:prompt injection 跟 excessive agency 的風險描述
- NIST SP 800-207 Zero Trust Architecture:「每次存取都重新驗證」這條原則的出處