iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
AI Security

AI Agent 憑什麼動手?30 天拆解 Agent Identity、Delegation 與 Authorization系列 第 2

Day 02|一個 Agent 到底算使用者、應用程式,還是 Workload?

  • 分享至 

  • xImage
  •  

Core Question

Tool 應把 Agent 視為哪一種 security principal,才能同時識別 runtime 與它所代表的人?

今天的問題

想像公司有一個「差旅報帳 Agent」。Alice 把發票丟進對話框,說:「幫我查這筆差旅是否符合規定,符合的話就送出報帳。」Agent 讀取差旅政策、查詢 Alice 的部門與預算,最後呼叫 submit_expense Tool。

Tool 收到請求時,究竟該認誰?

  • 認 Alice:因為費用屬於她,也是她發起任務。
  • 認「報帳 Agent」這個 application:因為企業核准的是這套應用。
  • 認目前執行中的 container / process:因為真正建立網路連線、持有 credential 並呼叫 Tool 的是它。

若硬要三選一,三個答案都不完整。比較精確的說法是:Agent 在部署與執行上是 workload,在 OAuth 互動中可能扮演 client;當它代表 Alice 行動時,Alice 仍是 delegated subject。 它不是 Alice,也不能因為被稱作「應用程式」就省略實際 runtime 的身分。

這篇的答案因此不是替 Agent 選一個帳號類型,而是拆開三個問題:

  1. 誰在執行?——可驗證的 Agent workload / runtime。
  2. 它代表誰?——User Identity 與 delegation context。
  3. 這是哪一套被治理的 Agent?——Agent definition、deployment、版本與 owner 等 metadata。

只有 Tool 同時看見這些不同層次,才有足夠資訊判斷這一次 Action。

為什麼這不是傳統 IAM 問題

傳統 web application 的執行路徑通常相對固定:使用者按下「送出」,後端依既定程式呼叫 API。Agent 則先把自然語言 intent 轉成計畫,再動態選 Tool、組 parameters,甚至連續執行多個 Action。同一個 Agent deployment 也可能同時處理許多使用者和 task。

這帶來一個容易被忽略的新邊界:規劃 Action 的邏輯身分、真正發出呼叫的 workload,以及被代表的使用者,不再是同一件事。

OAuth 2.0 本來就把 resource owner 與 client 分成不同角色;client 是在 resource owner 授權下代表其存取 protected resource 的應用程式,而且這個詞不限定它跑在 server、desktop 或其他裝置。也就是說,OAuth 的 application/client 語彙能描述部分關係,卻不會自動回答「這次是哪個 Agent instance 在哪個 task 中選了這個 Tool」。參見 RFC 6749, Section 1.1

Agent 還會讓 identity context 在執行途中改變。例如同一個 runtime 先用自己的 authority 讀公開政策,接著代表 Alice 查她的費用,之後又遇到需主管批准的高風險 Action。若系統只有一個 principal 欄位,這些責任會被壓成一團。

所以本篇不是在比較「human account、service account 哪個比較好」,而是在問:當 Agent 自主決定下一個 Action 時,Tool 要如何分辨 delegator、actor 與執行 instance,避免把身分驗證成功誤當成完整授權?

Threat / Failure Scenario

失敗設計:把 User Token 當成 Agent 身分

最直覺的作法,是讓 Agent 把 Alice 的 access token 原封不動送給 Tool:

{
  "iss": "https://identity.example",
  "sub": "user:alice",
  "aud": "expense-api",
  "scope": "expense.submit"
}

Tool 驗證後知道 Alice 可以送出報帳,卻不知道:

  • 是 Alice 親自操作,還是 Agent 代她操作?
  • 是正式的報帳 Agent,還是一個拿到 token 的其他 workload?
  • 是哪個 deployment、版本或 runtime instance 發出請求?
  • 發生問題時,能否只撤銷有問題的 Agent,而不凍結 Alice 的帳號?

這時只要任一可存取 token 的 Agent runtime 被入侵,Tool 看到的仍然只有 Alice。Audit log 可能很完整地記下「Alice 送出報帳」,卻精確地記錯執行者。

反過來,只使用 Agent 的 service identity 也有問題:

{
  "iss": "https://workload-issuer.example",
  "sub": "agent:expense-assistant",
  "aud": "expense-api"
}

現在 Tool 知道 caller 類型,卻不知道它正在代表 Alice 還是 Bob,也無法把 resource ownership、使用者 consent 或部門規則放進 policy。若所有 instance 再共用同一個值,instance 隔離與精準撤銷也一起消失。

兩種設計的共同錯誤,是試圖讓單一 sub 同時表示「受益或被代表的人」與「目前持有並使用 credential 的 actor」。sub 並不是「把所有相關人物塞進來」的欄位;在 OIDC 中,它和 iss 一起用來穩定識別 End-User。參見 OpenID Connect Core 1.0, Section 5.7

核心概念

1. Human identity:誰的資源、誰的意圖

User Identity 識別 Alice,讓 policy 能回答「她是否可對這筆費用發起報帳」。但 Alice 的權限是 Agent 可取得 authority 的上限之一,不是 Agent 的 runtime identity,也不代表每個自動化 Action 都應完整繼承她的權限。

在 delegated Action 中,可以把 Alice 視為 subject:Action 是為她執行、作用在她有關的資源上。之後幾天會再細談 delegation 的範圍;今天先抓住一件事:保留 subject,不等於隱藏 actor。

2. Application identity:哪一套被註冊與治理的 Agent

企業通常需要一個穩定的 Agent definition,例如:

  • agent_id: expense-assistant
  • owner: Finance Platform Team
  • approved tools: policy lookup、expense query、expense submit
  • model / prompt / code version
  • risk tier 與允許的 deployment environment

若採 OAuth,這套應用也可能有 client_id。但 client_id 回答的是「哪個 client 取得或使用這項授權」;它不必然等於當下 workload 的密碼學身分,更不等於使用者。RFC 8693 也把 client_id 定義為請求 token 的 OAuth client identifier,和 actor claim 分開表達。參見 RFC 8693, Section 4.3

此外,Agent definition 本身多半是 control-plane object,不一定直接發出網路請求。把「Agent 名稱」放進 header,只是自我宣告;只有 trusted issuer 將它與經驗證的 deployment / workload 綁定,Tool 才能把它當成 policy input。

3. Workload identity:此刻誰真的在呼叫

Agent 最終仍由 process、container、VM 或其他 runtime 執行。因此在 data plane 上,它首先是一個 workload。Workload identity 應讓接收端驗證:「這個 caller 是企業信任域內、被核准部署的報帳 Agent workload。」

SPIFFE 提供一個 vendor-neutral 的具體參考:SPIFFE ID 是 trust domain 內識別 workload 的 URI;SVID 則是 workload 向 resource 或 caller 證明該身分的可驗證文件。參見 SPIFFE ID / SVID specification。例如:

spiffe://corp.example/agents/expense-assistant

這個 ID 不應塞入人類名稱。它回答的是「哪個 workload」,不是「代表哪個 User」。SPIFFE 也刻意讓 path 的語意由組織定義;因此把 deployment、environment 或 instance 全編進 URI 並非唯一做法,應依隔離與 credential lifecycle 決定。

4. Definition、deployment、instance:不要把粒度混為一談

「報帳 Agent」其實至少有三層:

層次 例子 適合回答 是否一定是獨立 principal
Agent definition expense-assistant 產品用途、owner、核准 Tool 不一定;常是治理物件
Deployment prod/taipei/v3 環境、版本、policy profile 視隔離與 credential 邊界而定
Runtime instance instance-7f3a 哪次啟動、哪個執行個體 不一定;至少要能關聯 credential 與 audit

若每個 replica 共用同一 workload identity,Tool 仍可透過可信的 instance_id 做調查,但未必能單獨撤銷某一個 instance。若風險要求 per-instance revocation,就必須讓 credential issuance 和 instance attestation 有更細粒度的綁定;光在 JWT 加一個字串不會創造可信身分。

換句話說,principal 粒度要對齊 enforcement 與撤銷需求。不是每項 metadata 都要變成帳號,但每個拿來作授權的 attribute 都要有可信來源。

5. Subject 與 actor:不是二選一

RFC 8693 提供了很有用的 delegation 語彙:subject_token 表示「代表誰請求」,actor_token 表示「authority 被交給誰行使」;在 JWT 中,act claim 可以識別目前 actor。

套到本例,可形成這樣的 composite context:

{
  "iss": "https://authorization.example",
  "sub": "user:alice",
  "act": {
    "sub": "spiffe://corp.example/agents/expense-assistant"
  },
  "client_id": "expense-assistant",
  "aud": "expense-api",
  "scope": "expense.submit",
  "agent_deployment": "prod-tpe-v3",
  "agent_instance": "instance-7f3a",
  "task_id": "task-20260812-0042"
}

這不是建議所有系統照抄的 token schema。agent_deploymentagent_instancetask_id 是本文示意的 private claims;實際部署也可能用 token introspection、mTLS identity、request context 或 policy service 組合這些資料。重點是語意分離:

  • sub:被代表的 User。
  • act.sub:目前執行 authority 的 Agent workload。
  • client_id / agent_deployment:受治理的應用與部署資訊。
  • agent_instance / task_id:這次 runtime 與任務的關聯資訊。
  • aud / scope:token 的接收對象與授權範圍;它們仍不是針對具體 Action 的完整 policy。

RFC 8693 特別提醒,act 內的 claims 用來識別 actor,不應把 expnbfaud 等 token validity claims 塞入其中;接收端仍要驗證最外層 token。參見 RFC 8693, Section 4.1

Architecture Pattern

Naive / Unsafe Design

User Token ──> Agent ──(沿用 User Token)──> Tool ──> Resource

此設計把 User Identity 當 caller identity,Tool 無法分辨 Agent 與使用者直接操作;Agent 也可能把 broad user token 帶往原本不該接收它的 Tool。另一個常見變形是只用一個固定 service account,讓所有使用者、deployment 與 instance 都投影成相同 principal。

Recommended Design

https://ithelp.ithome.com.tw/upload/images/20260907/201201519IJkUkCVuL.png

這裡的關鍵不是一定要使用 JWT 或 SPIFFE,而是兩條證據流在 Authorization Service / Tool Gateway 匯合:

  1. User Identity 證明 delegated subject 是 Alice。
  2. Workload credential 證明 direct actor 是核准的 Agent runtime。
  3. Trusted registry / issuer 將 workload 綁到 Agent definition、deployment 與可用 attributes。
  4. Authorization Service 產生 audience-bound、短效且受限的 composite context。
  5. Tool Gateway 在每個 Action 執行前,將 subject、actor、Action、Resource 與 task context 交給 PDP。

NIST Zero Trust Architecture 強調,不應只因網路位置或資產所有權就給予 implicit trust,且 subject 與 device 的 authentication / authorization 是進入 enterprise resource 前的獨立功能。對 Agent 而言,可推導出同樣的原則:**「它跑在公司環境」不能取代可驗證 workload identity,更不能取代逐 Action policy。**參見 NIST SP 800-207

Trust Boundary

本例至少有四個邊界:User session、Agent runtime、Authorization / Policy plane、Tool / Resource。每次跨界都不能相信 Agent 自報的 user_idagent_idinstance_id;PEP 必須知道哪些欄位來自 User IdP、workload issuer、Agent registry 或自己的 request context。

Identity Flow

  1. Alice 完成 user authentication,取得可識別她的 session / token。
  2. Agent runtime 透過 runtime attestation 或等價機制取得 workload credential。
  3. Agent 要執行 submit_expense 時,同時提出 User delegation 與自己的 actor proof。
  4. Authorization Service 驗證兩者並產生給 expense-api 使用的短效 context。
  5. Tool Gateway 驗證 issuer、signature、audience、expiry 與 actor binding,再交給 PDP。
  6. Audit event 同時記錄 subject、actor、deployment、instance、task、Action、decision 與 outcome。

Authorization Decision Point

PDP 不應只問 scope 是否包含 expense.submit,而應至少評估:

subject = user:alice
actor = spiffe://corp.example/agents/expense-assistant
deployment = prod-tpe-v3
instance = instance-7f3a
task = task-20260812-0042
action = submit_expense
resource.owner = user:alice
amount = 12800

可能的 policy 是:Alice 可送自己的費用;actor 必須是 production 的核准報帳 Agent;deployment 版本不得在 denylist;金額超過門檻則要求 human approval。這也說明 authentication、delegation 與 authorization 是三件不同的事:知道 Alice 和 Agent 是誰,仍不代表這一筆 12,800 元的 Action 必須被允許。

小型 PoC

這個 PoC 不建立 IdP,也不解析 JWT;它只用 Python standard library 比較三種已解碼 claims set,檢查 Tool 的 audit event 能否同時回答 User、Agent、deployment、instance 與 task。真正系統必須先完成 signature、issuer、audience、time validity 與 credential binding 的驗證,不能直接相信下列 dictionary。

將以下內容存成 identity_layers.py 後執行 python3 identity_layers.py

claim_sets = {
    "user_only": {
        "sub": "user:alice",
        "aud": "expense-api",
        "scope": "expense.submit",
    },
    "agent_only": {
        "sub": "spiffe://corp.example/agents/expense-assistant",
        "aud": "expense-api",
        "client_id": "expense-assistant",
    },
    "layered": {
        "sub": "user:alice",
        "act": {
            "sub": "spiffe://corp.example/agents/expense-assistant"
        },
        "aud": "expense-api",
        "scope": "expense.submit",
        "client_id": "expense-assistant",
        "agent_deployment": "prod-tpe-v3",
        "agent_instance": "instance-7f3a",
        "task_id": "task-20260812-0042",
    },
}


def audit_view(claims):
    actor = claims.get("act", {}).get("sub")
    fields = {
        "represented_user": (
            claims.get("sub")
            if claims.get("sub", "").startswith("user:")
            else None
        ),
        "direct_actor": actor,
        "agent_app": claims.get("client_id"),
        "deployment": claims.get("agent_deployment"),
        "instance": claims.get("agent_instance"),
        "task": claims.get("task_id"),
    }
    missing = [name for name, value in fields.items() if value is None]
    return "complete" if not missing else "missing: " + ", ".join(missing)


def authorize(claims):
    expected_actor = "spiffe://corp.example/agents/expense-assistant"
    if claims.get("aud") != "expense-api":
        return "DENY: wrong audience"
    if claims.get("sub") != "user:alice":
        return "DENY: no delegated user"
    if claims.get("act", {}).get("sub") != expected_actor:
        return "DENY: unverified Agent actor"
    if claims.get("agent_deployment") != "prod-tpe-v3":
        return "DENY: unapproved deployment"
    return "ALLOW: subject and actor are independently visible"


for name, claims in claim_sets.items():
    print(f"{name:10} | audit={audit_view(claims)}")
    print(f"{'':10} | decision={authorize(claims)}")

預期輸出:

user_only  | audit=missing: direct_actor, agent_app, deployment, instance, task
           | decision=DENY: unverified Agent actor
agent_only | audit=missing: represented_user, direct_actor, deployment, instance, task
           | decision=DENY: no delegated user
layered    | audit=complete
           | decision=ALLOW: subject and actor are independently visible

PoC 證明的不是「claims 越多越安全」,而是沒有 principal separation,Tool 就連寫出正確 policy 與 audit event 的材料都沒有。反過來,欄位齊全也不代表可信;production PEP 還必須驗證每個欄位的 provenance,並只接受由可信系統綁定的 context。

Cloud / Platform Mapping(Optional)

這個 pattern 不要求特定平台。User Identity 可由支援 OIDC 的 IdP 提供;workload identity 可採 SPIFFE / SPIRE、平台原生 workload identity 或具等價 attestation 性質的機制;composite context 可由 OAuth Token Exchange、token introspection 或 policy gateway 實作。選型時應先問「能否分開 subject 與 actor、能否驗證 runtime、能否限制 audience 與 lifetime」,再比較產品名稱。

今天得到什麼

  • Agent 不是使用者、應用程式、workload 的三選一:delegated subject、受治理的 application 與 direct workload actor 必須分層表達。
  • sub 不能同時模糊地代表 User 與 Agent;代表誰和誰在執行是兩個不同問題。
  • Agent definition、deployment 與 instance 不必全是獨立 principal,但凡進入 policy 的 attribute 都必須有可信來源與可驗證綁定。
  • Tool 的最小授權 context 應包含 subject、actor、Action、Resource 與必要的 task / runtime attributes。
  • 身分驗證成功只提供 decision input;是否允許具體 Action,仍由 PEP / PDP 逐次決定並留下 evidence。

下一篇

今天把 User、Agent application 與 workload runtime 拆開了,下一個問題也隨之出現:如果企業為了方便,讓所有 Agent workload 又共用同一組 Service Account,這些剛建立的邊界是否會再次被壓平?Day 3 將從 blast radius、撤銷與歸責三個角度回答:為什麼不能讓所有 Agent 共用一組 Service Account?

參考資料


上一篇
Day 01|Chatbot 變成 Agent 後,安全邊界到底多了什麼?
下一篇
Day 03|為什麼不能讓所有 Agent 共用一組 Service Account?
系列文
AI Agent 憑什麼動手?30 天拆解 Agent Identity、Delegation 與 Authorization6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言