iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
AI Security

Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統系列 第 29 篇

Day 29|Microsoft Foundry 的 AI Agent 攻防實戰:Agent 說是工程司派來的,不能當簽章

  • 分享至 

  • xImage
  •  

Day 28 限制了 fan-out、fallback 與 retry,現在接著處理路由裡的權限。假設 planner 送來 subject="alice",executor 要怎麼確認它真的代表 Alice,又被允許做哪些事?

今天沿著 orchestrator → retriever → planner → executor 傳遞委派。每一跳都驗證簽發者、接收者與業務授權,child delegation 只能保留或縮小上游給的能力。

我們先用 HMAC 作為本機測試替身,讓有匯出權限的 Fiona 重放五筆拒絕案例,再保留 Alice 合法讀取一筆費用的對照。最後會回到工具執行端,檢查委派驗證通過後,還有哪些資料需要綁在一起。

從 Root Grant 理解委派能給多少權限

  • Issuer/Audience:issuer 是簽發者,audience 是 token 預定交給的接收者;接錯 audience 必須拒絕。
  • Root grant:由業務政策事先核發的根能力,明確綁 subject、tenant、action、resource、audience 與上限。只有「trusted issuer」不夠。
  • Attenuation(權限遞減):child delegation 只能維持或縮小 parent 能力,不能新增 action、resource、depth 或期限。
  • Nonce:一次性識別值;接收端必須用原子操作登記,並行驗證也只能有一次成功。
  • Hop-by-hop:每個接收 Agent 都重新驗 token 與自己的政策;上一跳驗過不能替下一跳背書。

接收端需要驗證哪些條件?

訊息中的 sender="planner" 是 caller 提供的字串,接收端還要用可信憑證確認來源。Conversation ID、session ID、message ID 與 trace ID 可以協助找回流程,不能用來代替 sender 驗證。

就算 token 簽章有效,receiver 仍然要拒絕幾種「格式正確,用途錯誤」的請求:

  • 簽給 retriever 的 token 被送到 executor。
  • Child delegation 加入 parent 沒有給的 action 或 resource。
  • 受信任 issuer 企圖代表不被允許的 tenant。
  • Token 過期、簽章被修改,或同一 nonce 被再用。
  • Delegation 沒超權,但整條 route 的 fan-out、token 或工具預算已經超標。

在這個例子裡,Zero Trust 對應到接收端的幾項工作:驗證來源、檢查 delegation,再執行自己的 policy enforcement point。即使雙方位於同一個 Project 或 VNet,這些步驟仍要保留。

使用同一句匯出要求,重現五筆拒絕結果

D29-A01:簽章被改過,retriever 先拒絕

先讀 D29-A01 的固定案例設定,再看它實際停在哪一個授權檢查。

D29-A01 的 frozen case、hop trace 與 deny boundary,retriever 以 DELEGATION_BAD_SIGNATURE 拒絕。

Fiona 有匯出 tenant:bamboo-hq 的 root grant,orchestrator 也順利簽出第一段委派。這段委派在送往 retriever 的途中被改寫,多塞了 tenant:moon-rabbit-lab,但簽章沒有跟著重算,所以 retriever 回 DELEGATION_BAD_SIGNATURE,工具 Receipt 與副作用都為 0。下面五筆是固定資料集的路由結果,不是一張逐欄更改 issuer、audience、subject、capability、target 的 claims 矩陣;使用的 HMAC 也只是本機替身。

D29-A02:token 無法解析,也停在 retriever

D29-A02 標記為 malformed,接著核對畫面真正回傳的拒絕原因。

D29-A02 的 malformed 情境在 retriever 得到 DELEGATION_MALFORMED,沒有工具執行。

這次送到 retriever 的不是一段可以解析的委派,rejected_attempts 記在 retriever,原因是 DELEGATION_MALFORMED。沒有任何 hop 驗證通過,工具 Receipt 與副作用都為 0。

D29-A03:資源被換成外租戶,executor 拒絕

D29-A03 設定了 resource_mismatch,這次要看它一路走到哪一跳才被擋下。

D29-A03 的 resource_mismatch 情境通過 retriever 與 planner,在 executor 得到 DELEGATION_RESOURCE_DENIED。

retriever 與 planner 都驗證通過;executor 收到的操作目標卻換成外租戶的 exp-moon-001,不在委派的 resource 範圍內,因此回 DELEGATION_RESOURCE_DENIED。前兩跳通過、最後一跳拒絕,正好說明每一跳都要重新驗證,不能因為上一跳驗過就放行。

D29-A04:route budget exceeded

接著看 D29-A04 的預算檢查,確認超額計畫是否在工具執行前停下來。

D29-A04 route budget reservation 與停止點。

這筆要求展開 8 個分支,並讓 primary 失敗後走 fallback。預設的路由預算是 16 次模型、4 次工具與 4 個分支,投影卻需要 40、8 與 8,所以得到 ROUTE_BUDGET_EXCEEDED,並列出模型、工具與分支三種超額原因。工具 Receipt 與副作用都為 0。這張圖涵蓋的是固定路由的預算拒絕,沒有驗證真實 Agent identity 或 A2A transport。

D29-A05:circuit open

最後看 D29-A05:前面的失敗已打開 circuit,新的路由還能不能開始。

D29-A05 circuit-open deny 與完整 frozen outcome。

前兩次請求都帶著被改過的委派,各得到一次 DELEGATION_BAD_SIGNATURE,把 circuit 打開;第三次才得到 ROUTE_CIRCUIT_OPEN,工具 Receipt 與副作用都為 0。這是固定資料集的本機結果,不能代替真實 Agent identity 或 A2A transport 的驗證。

D29-A01~A05 都由 Fiona 提出同一句業務請求:

月底結帳要用,麻煩把竹林總部本月的費用明細匯出來。

五筆都使用同一句話,只改測試 metadata:bad_signature、malformed、resource_mismatch、budget_exceeded 與 circuit_open。其中 D29-A04 另外把分支數設成 8。

選 Fiona 是有原因的。她本來就能匯出竹林總部的費用,root grant 會放行,所以每一筆都能走到情境要測的那道檢查。這幾筆要看的不是「Fiona 能不能匯出」,而是委派在路上被竄改、壞掉、被換目標,或整條路由太大時,系統能不能在工具執行前停下來。

先前的固定資料用的是 Alice。她只有讀取的 root grant,五筆裡有三筆在 orchestrator 簽發第一段委派時就被擋下,圖上寫著簽章錯誤、格式錯誤、資源不符,實際卻一道都沒測到。外部審查指出後,改成現在的資料。

這一版 DelegationClaims 實際只有下列欄位:

root_grant_id
root_issuer_agent + root_policy_version
issuer_agent
subject
tenant_id
audience_agent
actions + resources
max_depth + current_depth
expires_at
nonce

目前 claims 沒有 subject_source、plan_digest 或 revocation version。讀程式時先依上面列出的欄位追流程;後續若需要把計畫版本或撤權狀態綁入執行,還要擴充這份契約。

從 day29/ 執行下面的 helper。它讓 Fiona 要求匯出 tenant:bamboo-hq,預算情境另外把分支數設成 8:

from magic_panda_agent.stages.day29 import replay_delegated_route

for scenario in (
    "bad_signature",
    "malformed",
    "resource_mismatch",
    "budget_exceeded",
    "circuit_open",
):
    wide_route = scenario == "budget_exceeded"
    result = replay_delegated_route(
        scenario=scenario,
        subject="fiona",
        action="export_expenses",
        resource_id="tenant:bamboo-hq",
        high_cost_route=True,
        fan_out=8 if wide_route else 1,
        force_primary_failure=wide_route,
    )
    print(
        scenario,
        result["allowed"],
        result["reason"],
        len(result["tool_receipts"]),
        result["side_effect_count"],
    )

五筆依序得到 DELEGATION_BAD_SIGNATURE、DELEGATION_MALFORMED、DELEGATION_RESOURCE_DENIED、ROUTE_BUDGET_EXCEEDED 與 ROUTE_CIRCUIT_OPEN,而且都要滿足:

allowed == False
tool_receipts == []
side_effect_count == 0

預算情境沒有另外收緊上限。先前的程式看到 budget_exceeded 這個情境名稱,就把上限改成 0 個分支,等於由測試名稱決定防禦的設定;現在所有情境共用同一組預設預算,超額是因為這筆要求本身展開得太大。

circuit_open 案例共用一個 RouteCircuitBreaker(threshold=2)。前兩次 invocation 帶著被改過的委派,各得到一次 DELEGATION_BAD_SIGNATURE。累積到 failures=2、open=true 後,第三次雖改用 valid 情境,仍在 projection、materialization 與工具執行前收到 ROUTE_CIRCUIT_OPEN。這能確認 breaker 狀態跨 request 延續。

實作與 acceptance 實際使用的 reason code 如下:

情境 Reason code
簽章被修改/token 無法解析 DELEGATION_BAD_SIGNATURE/DELEGATION_MALFORMED
Receiver 不是 token audience DELEGATION_WRONG_AUDIENCE
Expected issuer、subject 或 tenant 不符 DELEGATION_WRONG_ISSUER/DELEGATION_WRONG_SUBJECT/DELEGATION_WRONG_TENANT
Issuer 不受信/issuer 不能代表該 tenant DELEGATION_UNTRUSTED_ISSUER/DELEGATION_ISSUER_TENANT_DENIED
Receiver 要求 token 未列的 action 或 resource DELEGATION_ACTION_DENIED/DELEGATION_RESOURCE_DENIED
Child 擴大 action 或 resource DELEGATION_CAPABILITY_AMPLIFICATION/DELEGATION_RESOURCE_AMPLIFICATION
Parent audience 與次跳 issuer 不同 DELEGATION_ISSUER_AUDIENCE_MISMATCH
超過 depth/token 過期/nonce 再用 DELEGATION_DEPTH_EXCEEDED/DELEGATION_EXPIRED/DELEGATION_REPLAY
完整 route 超出預算/circuit 已打開 ROUTE_BUDGET_EXCEEDED/ROUTE_CIRCUIT_OPEN

核對拒絕位置與零工具執行

現在看獨立 helper 的 resource_mismatch 結果,確認拒絕原因、停止位置與接收 Agent。

Delegation deny detail 顯示 resource mismatch、resource denied reason 與 executor rejection。

這份資料的 scenario=resource_mismatch、reason=DELEGATION_RESOURCE_DENIED,流程停在 executor verification,拒絕的 Agent 是 executor。它只列出這次 helper 的結果,沒有逐步 input digest,也沒有驗證正式環境的金鑰清冊、撤權、A2A 憑證或 transport 綁定。

看圖時沿著已通過的 hop 往下找拒絕位置,五筆都應在 tool_executor 前停止。MICROSOFT_ENTRA_AND_A2A_CREDENTIALS_NOT_EXERCISED 則表示這組測試沒有使用 Entra 或 A2A 憑證。

按 Root Grant 簽發,再逐跳縮小能力

第一跳的能力範圍

再走一次合法路由,從第一跳的能力交集開始看。

route/depth/resource overview 與第一個 capability intersection。

畫面先列出 route、depths、actions、resource_ids 與 capability_rule,再展開 capability_steps/0。第一跳把收到的能力與本地政策取交集,決定可以往下交多少。以下三張圖只測固定的本機政策與合成 HMAC,沒有驗證 Entra、A2A 身分、動態信任或 federation。

第二跳的能力範圍

第二個 Agent 收到能力後,再和自己的政策取一次交集。

第二個 agent 的 incoming/local/outgoing capability。

capability_steps/1 保留第二跳的 actions、resources 與 outgoing_matches_intersection。上一跳已移除的能力,不能到了這裡又被加回來。

第三跳與最終檢查

最後看第三跳,再核對整條路由是否曾放大能力。

第三跳 capability 以及全路由 intersection/amplification gates。

capability_steps/2 列出最後一跳,all_outgoing_match_intersection=true、amplification_detected=false 則確認這條固定路由的交集結果一致,沒有擴大能力。

DelegationService.issue() 先用 trusted_issuer_tenants 檢查簽發者能否代表這個 tenant,再找政策預先核發的 DelegationRootGrant。只有受信任的 issuer,還不足以自行增加權限。

目前 root grant 只有兩份:Alice 可以對 exp-bamboo-001 執行 read_expense,Fiona 可以對 tenant:bamboo-hq 執行 export_expenses。簽發者不能新增未列出的 subject、action、resource、audience 或 depth;每一跳還會重驗 root grant ID、root issuer 與 policy version。

這裡的 authenticated_issuer_agent 由 lab 呼叫端提供,尚未經 transport authentication 驗證。正式服務需要從已驗證的 workload 或 delegated credential 導出 issuer,不能直接採信 request body;本機結果也不能拿來證明 Entra access token 或 A2A profile 已通過測試。

合法 route 的 attenuation 如下:

orchestrator → retriever   audience=retriever, depth=0
retriever    → planner     audience=planner,   depth=1
planner      → executor    audience=executor,  depth=2

三跳都保留:

subject  = alice
tenant   = bamboo-hq
action   = read_expense
resource = exp-bamboo-001

attenuate() 先確認 parent 的 audience 就是下一位 issuer,subject 與 tenant 沒變,child 的 actions/resources 也是 parent 的子集,再檢查 depth 與尚未使用的 nonce。Nonce 的檢查和登記在接收端同一把 lock 裡完成,32-thread 測試只能有一次成功。Child 沿用 parent 的 expiry,不能延長期限,audience 也仍受 root grant 限制。

Breaker admission 在 projection 之前;通過後才投影並檢查 Day 28 的 model、tool、token、cost proxy 與 fan-out limits,而且必須在 materialization/executor 前完成。這是兩個不同的 oracle:delegation 問「能不能做」,route budget 問「這次最多可以展開多大」。

replay_delegated_route() 會沿用 caller 注入的 breaker instance,所以 A05 的第三次請求讀得到前兩次失敗。若每次 request 都建立新的 instance,狀態便會歸零,無法驗證這個跨請求條件。

分開記錄 Actor、Subject 與目標授權

  • actor 是當前送出呼叫的 Agent workload。
  • subject 是這份工作代表的使用者。
  • Target PEP 還要回答:這個 actor 代表這個 subject,現在能否對這個 resource 執行這個 action。

背景工作若沒有代表任何使用者,subject 就明確留空,依 application authority 授權。需要使用者權限的流程則延續 OBO/delegated context;若途中遺失,應拒絕該操作,不能改用較大權限的 app-only identity。

對照拒絕案例與合法的三跳讀取

五條拒絕路徑之外,也保留一條使用相同本機協定、能正常讀取的路由。

A2A positive route 顯示每 hop 通過、target PEP allow 與一張 read Receipt。

各跳驗證通過,目標 PEP 放行,最後只留下 1 張唯讀 Receipt,副作用為 0。這筆合法對照沒有驗證 Foundry A2A 協定、外部 Agent 信任,或委派與最終工具之間的完整綁定。

從 day29/ 執行:

env PYTHONPATH=src:. PYTHONDONTWRITEBYTECODE=1 \
  PYTHONNOUSERSITE=1 PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 \
  python -m pytest tests/stages/day29/test_acceptance.py -q -p no:cacheprovider

目前 suite 實際驗證:

  • Legal route 的 depths 為 0/1/2,最後只留 read_expense 與 exp-bamboo-001。
  • Wrong audience、wrong issuer、wrong tenant、untrusted issuer、issuer-tenant deny、過期、超 depth、capability amplification 與 nonce replay 都有負向測試。
  • Trusted issuer 仍不能替 Mallory 或未核准 action/resource 建 root delegation;root grant 是獨立的業務授權,不由 issuer 自填。
  • 32-thread nonce 競爭恰好一次成功;壞 delegation 在具體 route materialization 前拒絕。
  • Negative scenarios 都在 tool side effect 前回精確 reason,receipts 與 side effects 為 0;D29-A01~A05 的 frozen ID/order 則由 stage renderer 與 Day 30 dataset contract 負責。
  • Positive route 的 retriever、planner、executor 全部驗證成功,subject 保留 Alice,最後產生一張 get_expense receipt,side_effect=false;D29-B01 是 renderer 綁定的 ID。
  • Route budget 同時超過 tool call、token、cost proxy 與 fan-out 時,actual model/tool counters 仍是 0。
  • 兩次 bad signature 與第三次合法 request 共用同一 breaker;第三次在 route projection 前回 ROUTE_CIRCUIT_OPEN,ledger 仍是零 receipt/零 effect。
  • 預算情境改用 8 個分支,在同一組預設預算下得到 ROUTE_BUDGET_EXCEEDED,不再由情境名稱收緊上限。
  • 不在對照表裡的 action(例如 approve_expense)直接得到 DELEGATION_ACTION_UNSUPPORTED,不會被當成匯出。
  • bad_signature 情境確實把 tenant:moon-rabbit-lab 塞進委派的 resource,簽章驗證因此失敗。

這組本機驗收的保存結果是 15 passed。

核對 Depth、Action 與 Resource

合法的 legal_three_hop_flow() 依序走 retriever→planner→executor,depth 為 0→1→2,最後只留下 read_expense 與 exp-bamboo-001。這是本機 HMAC 流程,可以用來核對每一跳的能力是否維持或縮小。

Microsoft Foundry 的 A2A 與 identity 邊界

把今天的 root grant 與 child grant 放到真正的 A2A 路徑時,要分開看兩張證據:Foundry endpoint 先驗誰可以呼叫這個 Agent,工程司的 PEP 再驗這一跳能否替 Alice 讀取指定費用。若中途改用 Agent identity,接收端看到的 actor 可能變了;subject、capability、audience 與 expiry 仍要由應用程式逐跳核對。本篇十張圖只展示本機 grant 流程,沒有 A2A 雲端封包。

Foundry Agents core 在官方 readiness 表列為 GA,A2A 則要按協定版本與整合路徑分開看:incoming v1.0 與 outgoing a2a tool 已是 GA;v0.3 與舊的 a2a_preview tool 仍是 Preview。Preview 路徑沒有 GA SLA,不能因另一個版本已 GA 就一起視為正式支援。官方文件目前列出的邊界包含:

  • Incoming A2A 要求 Microsoft Entra ID,不支援 anonymous 與 key-based authentication。
  • 設定/啟用 Agent 的 prerequisite 列為 Foundry User 或更高角色;讀取 agent card 與呼叫 endpoint,則需要 Foundry Agent Consumer 或其他具 endpoint access 的角色。設定權限與呼叫權限要分開,雲端 smoke 也要對 card 與 request 分別做 401/403 測試。
  • 可使用 OBO end-user identity,或 agent/service principal/managed identity 等 service identity;兩種模式的授權語意不同。
  • 新的 outgoing 整合使用 a2a/A2ATool,對應 GA v1.0;既有的 a2a_preview/A2APreviewTool 對應 Preview v0.3。Outgoing 的 authentication 選項不能反推 incoming endpoint 也接受相同方式。
  • Foundry incoming endpoint 支援 A2A v1.0 與 v0.3;v1.0 只支援 JSON-RPC。
  • Incoming 新整合要明確選擇 v1.0;未指定版本時會使用預設的 Preview v0.3。Azure Developer CLI 的 A2A toolbox 路徑仍另標為 Preview,不能只看協定版本判斷。
  • 上述 incoming endpoint 目前只支援文字 modality,且不支援 streaming responses。

這些平台條件提供 authentication 與 protocol surface,不會替大魔術熊貓工程司判斷 Alice 能否讀取 exp-bamboo-001,也不會自動產生 capability attenuation、nonce replay store 與 route budget。

本機沒有驗證 Entra token、Foundry role、OBO 或 A2A agent card。平台部分還要分開看 Agents core、managed hosting、session API 與 incoming/outgoing A2A;不同文件仍各有 GA、Preview 或 prerelease 範圍。Python self-host 的 agent-framework-a2a 與 agent-framework-hosting-a2a 也仍標為 prerelease。

NIST SP 800-207/207A 只支撐 resource-centric、service identity 與 granular policy 的設計理由;「每一跳重驗、child 能力只能是 parent 子集」是本 repo 的 invariant,不是 Zero Trust conformance 測試。ISO/IEC 27001:2022 是 ISMS requirements standard,27002:2022 才是 control guidance;兩者都不會替 A2A 產生 token schema 或認證證據。

從委派驗證到工具執行,還缺哪些綁定?

這個 lab 已經有 root grant、policy version 與封閉的 action 對照表,還沒有 VerifiedDelegationProof、可單次消耗的 ExecutionGrant、plan digest 或 revocation version。

目前 replay_delegated_route() 在同一個函式裡,用相同 action/resource 驗證後呼叫 router.execute()。這讓我們能追蹤一條固定路徑,但還沒有把驗證結果做成和實際工具原子綁定、只能消耗一次的執行授權。

原本的 fixture 把 read_expense 對應到 get_expense,其餘 action 一律對應到 export_expenses,新 action 可能因此被綁到匯出工具。現在改成只有兩列的對照表:read_expense 對 get_expense、export_expenses 對 export_expenses,其他 action 在建立路由前就得到 DELEGATION_ACTION_UNSUPPORTED。後續仍要將 issuer、subject、tenant、audience、action、resource、expiry、nonce、plan digest 與 exact tool 綁進可原子消耗的 grant。

Nonce check 在單 process 內已用 lock 原子化,但 nonce store 與 circuit breaker 都是 instance/process-local,而且只有 caller 重用或注入同一 instance 才能跨 request 延續。Breaker 目前只是單一未分 tenant/route 的 counter,沒有時間窗、cooldown、half-open 或 distributed atomicity;若錯誤地全域共用,兩筆壞簽章就可能替其他租戶製造 DoS。它也把所有委派拒絕都算成失敗,連「沒有 root grant」這種業務拒絕也算在內,一位員工連續要求兩次沒權限的匯出,就可能把 circuit 打開。比較合理的做法是只計入簽章錯誤、格式錯誤與重放這類完整性失敗。Production 需要 durable、partitioned replay/breaker state,並處理併發 consume、TTL、clock skew、key rotation、revocation 與 tool transaction。把 HMAC 字串換成 JWT 不會自動補齊這些生命週期。

最後一天會把每日驗收與固定案例的指標分開整理,先確認分母和版本,再檢查這一版是否具備發布條件。

官方、規範與研究來源


上一篇
Day 28|Microsoft Foundry 的 AI Agent 攻防實戰:一份毒文件,為什麼會長成二十四次呼叫
下一篇
Day 30|Microsoft Foundry 的 AI Agent 攻防實戰:三十個綠燈超完美,嗎?
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言