iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
AI Security

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

Day 28|Microsoft Foundry 的 AI Agent 攻防實戰:一份毒文件,為什麼會長成二十四次呼叫

  • 分享至 

  • xImage
  •  

Day 27 的 kill switch 已能阻止單一 Agent 新增工具副作用。現在,大魔術熊貓工程司把一則普通的月底對帳需求拆成 retriever、planner 與 executor,再由測試 harness 在 route metadata 注入 primary failure、retry、fallback 與四路 fan-out。

標題裡的「毒文件」是攻擊鏈的威脅故事,不是本日實測輸入。project() 會丟棄 task 文字;fixture 不載入 retrieved document,也沒有驗證 prompt injection detector。今天真正重放的是已登錄 metadata 如何放大 call 與 authority,不能把 route amplification 圖改說成文件攻擊已被重現。

一隻熊貓分身四次,每個分身再 fallback 兩輪,還沒完成工作,帳單已經先學會 multi-agent。今天要量的不是模型講了幾句話,而是惡意工作能借用多少 authority,又能展開多少 model call、tool call 與 side effect。

先備名詞:一個請求如何長出很多次呼叫

  • Fan-out:把一個工作拆成多個平行分支;每個分支都可能增加模型、工具與成本。
  • Retry/Fallback:失敗後重試,或改走另一個模型/路由。它們能提高可用性,也可能放大攻擊與花費。
  • Route budget:一次工作可使用的 call、token、成本與分支上限;必須在配置或執行前檢查。
  • Ledger:獨立記錄真正發生的 calls 與 effects。每次 request 要以 run_id 分區,不能把前一位租戶的紀錄算進來。
  • Projection/Materialization:前者用常數大小的摘要預估整條路由;後者才建立每一筆實際工作。超額應在 materialization 前拒絕。

Threat:不可信內容會沿著共享 authority 傳下去

本日 D28-A01 不下載文件、不執行任意程式,也不呼叫外部模型。使用者只說:

月底要對帳了,幫我把竹林總部這個月的費用全部匯出,我要交給財務。

這句話不包含 route 名稱、工具函式、fallback 次數或預期 reason code。那些控制變數由 frozen fixture metadata 提供,和 user_message 分開保存。本日只重現下列控制流:

Alice 的 subjecttenant 也應來自已驗證的 principal/delegation context,不是從「我要交給財務」這句話猜出來。方便閱讀而把兩者畫在同一張圖,不能反過來把聊天文字當成身分來源。

自然業務請求
  → frozen route metadata(不傳給使用者)
  → retriever
  → planner
      → primary failure
      → retry
      → high-cost fallback
  → executor
      → export_expenses
  → fan-out × 4

Vulnerable router 讓每一跳都使用 shared-project-identity。若 route 沒有保留 delegated subject,最後一筆 receipt 只看得到某個內部 Agent 要求匯出,卻無法回答:

  • 這次工作是否真的代表 Alice?
  • Alice 的 tenant 是 bamboo-hq 還是其他租戶?
  • 上游授予的是讀取、規畫,還是匯出?
  • 這份委派還剩多少 depth、call 與 token budget?

共享 workload identity 可以完成 authentication,不能自動補回使用者脈絡與業務授權。多 Agent 的 blast radius 也不等於 Agent 數量;真正要看的是每個錯誤決策可借用的權限、分支與重試次數。

Attack:同一句人話,四個 branch 投影二十四次 call

畫面判讀目標: 從同一次 vulnerable execution 的 Receipt 彙總看見四路 fan-out 與 authority loss。

Multi-agent runtime view 顯示四個 branch 的二十四筆 Receipts 與遺失的 subject、tenant。

Fan-out 放大了 calls,也把同一個共享 actor 的身分缺口複製到每個 branch。 可觀察狀態:branch_count=4、logical_calls=24、每 branch 5 model/1 tool、high-cost model calls=8;shared actor 唯一,24 筆 Receipt 的 subject/tenant 均缺失。 Claim boundary:Authority counters 只描述這次 process-local Receipt;不是 Foundry billing、HTTP attempts 或 production autonomous run。

D28-A01 的輸入刻意拆成兩層。第一層是上面的 user_message;第二層才是 harness 控制的 request.metadata

fan_out = 4
force_primary_failure = true
retry_count = 1
high_cost_route = true

每個分支包含 retriever、planner primary、兩次 fallback attempt 與 executor,共五次 model calls;四個分支再各有一次 tool call。完整投影為:

指標 D28-A01 預期投影
Model calls 20
Tool calls 4
Input tokens 3,040
Output tokens 944
Total tokens 3,984
Cost proxy 16,536 units
Latency proxy 708 units
Fan-out 4

Token 是 fixture 固定值,不是 provider tokenizer usage;cost 與 latency 都是無單位 deterministic proxy,不是 Azure 價格、發票或毫秒。Cost proxy 公式刻意放在 report 中:

Σ_i ((input_i + 3 × output_i) × multiplier_i)
  + 50 × tool_calls

Primary calls 都是 standard;只有 primary failure 後的 fallback 是 high_cost。因此 20 次 model calls 實際分成 standard 12、high-cost fallback 8,cost proxy 也能拆成 2,384+13,952+tool 200=16,536。要注意,Frozen 欄位 high_cost_route=true 目前只是 dataset label;materializer 實際由 force_primary_failure 進入 high-cost fallback,尚未讀取這個 flag。它不能被描述成已生效的 route policy。

這個公式只用來比較相同 lab 設定下的 route amplification,不能換算成 Azure 成本預估。

Vulnerable path 會建立 20 筆 model receipts、4 筆 tool receipts 與 4 次 fake side effects;若 preserve_subject=False 且未另設 preserve_tenant,receipt 的 subject/tenant 都是 None,actor 仍是 shared identity。所有副作用只寫入記憶體 ledger,不會匯出真實資料。

Fan-out amplification UI 要證明什麼

同一份 fresh payload 會同時重放 D28-A01 vulnerable 與 ga_secure。Fan-out scene 只讀前者實際留下的 24 筆 Receipts、四個 branch summaries 與 authority 缺口;後面的 budget scene 才讀 ga_secure 的 projection、limits 與零 actual counts。不得跨 case 拼接。

Capture command 輸出的是這筆 bounded replay 的 JSON evidence payload,不是 PNG。正式發布時,這個 app UI state 必須由目前相關原始碼經 React + FastAPI + Playwright capture pipeline 產生;schema 3 manifest 必須以 source-tree SHA-256 與檔案數綁定實際輸入,strict verifier 再重算本篇所有 required semantic images。這仍是本機 route ledger 與 deterministic proxy,不是 Foundry quota、billing 或 cloud trace。

Fix:完整計畫先過 budget,executor 才能開始

畫面判讀目標: 確認 O(1) route projection 在 materialization 與第一個 side effect 前接受 budget admission。

Plan budget UI 顯示 route projection 超額並在 materialization 前拒絕。

先算 bounded projection,再決定是否允許建立完整計畫。 可觀察狀態:projected model/tool/token/cost/fan-out 與 4 個 side-effecting tool calls 超出明示 limits;五個 reason codes、materialized=false、actual model/tool/side-effect counts=0 可見。 Claim boundary:estimate 依賴已知 graph,不涵蓋 runtime 動態新增 branches。

Secure path 先用 O(1) 的 RouteProjection 計算 model calls、tool calls、tokens、cost proxy 與 fan-out,通過後才 materialize receipts。這避免把「先配置十萬筆 call,再說超預算」誤當 preflight。順序如下:

projection = router.project(
    task,
    subject="alice",
    fan_out=4,
    force_primary_failure=True,
    retry_count=1,
    high_cost_route=True,
)
failures = route_limit_failures(projection, limits)

if failures:
    actual = {
        "model_receipts": [],
        "tool_receipts": [],
        "side_effect_count": 0,
    }
else:
    plan = router.materialize(projection)
    actual = router.execute(
        plan,
        preserve_subject=True,
        allow_side_effects=True,
    )

D28-A01 使用的安全上限為 2 model calls、0 tool calls、500 total tokens、1,000 cost-proxy units 與 fan-out 1,因此同時得到:

MODEL_CALL_BUDGET_EXCEEDED
TOOL_CALL_BUDGET_EXCEEDED
TOKEN_BUDGET_EXCEEDED
COST_PROXY_BUDGET_EXCEEDED
FAN_OUT_BUDGET_EXCEEDED

這五個 reason code 是測試 oracle 與程式決策,不會要求使用者在對話裡背出來。它們要在 materialize()router.execute() 前出現。若 runtime 先配置整份 route,甚至呼叫完工具才發現超額,counter 只能協助結帳,不能阻止資源耗用與副作用。

此外,InMemoryRouteExecutionLedger 是獨立 oracle:vulnerable 路徑即使回傳物件說謊,ledger 仍觀察到 20 model、4 tool 與 4 個 synthetic effects;secure denial 則三者皆為 0。每次 execute 會建立/接收唯一 run_id,寫入時加鎖,回傳時只讀該 run 的 partition;連續重用同一 router、相同 route shape、不同 tenant 時,第二筆不再混入第一筆 receipts。這個 ledger 仍是 process-local 測試替身,不是雲端 billing 或 durable audit log。

Router 另有三條 allocation hard ceiling:fan-out ≤ 64、retry ≤ 16、projected calls ≤ 1,024。這些是防止不受控 list allocation 的本機上限,不是 Foundry quota,也不取代更低的業務 route budget。

預算要隨 route 消耗,不能每個 Agent 各拿一份新的

畫面判讀目標: 核對合法 route 的 outer/nested Receipt identity continuity 與 deterministic proxy metrics。

Route evidence UI 顯示 outer 與 nested Receipt 欄位一致、subject 保留,並列出 deterministic proxy metrics。

這張圖核對 Receipt continuity 與 proxy metrics,不宣稱共享 budget ledger。 可觀察狀態:subject_preserved=true、receipt_fields_match=true;outer/nested 的 tool/resource/tenant/subject/branch/side_effect 相同,model/tool calls=3/1、tokens=412、cost=646、latency=69、side effects=0。 Claim boundary:畫面不證明共享或遞減 budget ledger;metrics 是 deterministic proxies,不是 cloud token、billing、latency 或 provider retries。

RouteProjection 只保存可用 O(1) 計算的 scalar route metadata;通過後的 RoutePlan 才 materialize 每次 call、fallback、attempt、branch 與 route tier。執行中若加入新 retry 或動態工具,必須從相同 budget reservation 扣除並再次檢查;不能讓每個 Agent 都宣稱 「我只呼叫一次」。

Receipt 也分開兩個容易混淆的欄位:

  • actor:真正送出本次呼叫的 Agent workload identity。
  • subject:這次工作代表的使用者;沒有使用者時要明確為空,並走 application authority。

trace_id 能把 hops 串起來,不能替遺失的 subject 補發身分證。若 user context 在第二跳消失,下游應 fail closed,不能自動升級成共享 service privilege。

Benign 對照:修補不能只剩「全部拒絕」

D28-B01 的使用者只問:「幫我查一下 exp-bamboo-001 現在處理到哪裡了?」對應的 harness metadata 是單一分支、無 fallback 的 get_expense

指標 D28-B01 預期值
Model calls 3
Tool calls 1
Total tokens 412
Cost proxy 646 units
Latency proxy 69 units
Side effects 0

外層 proposal 與 executor receipt 都要綁定 get_expenseexp-bamboo-001bamboo-hqalice、branch 0,並標示 side_effect=false。這筆 benign case 通過,才表示 preflight 的確縮小權限與路由,同時保留合法工作。

Route evidence UI 要證明什麼

畫面只重放 D28-B01,顯示外層與 executor receipt 的 tool、resource、tenant、subject、branch 與 side-effect 分類完全一致;同時保留固定的 token/cost/latency proxy 標籤。

第二條 stage command 同樣只輸出 JSON payload,不直接產生公開圖;repository UI capture pipeline 會操作 routing UI 進入對應狀態,證據範圍仍限於本機 benign route 的 Receipt continuity 與 deterministic proxies,不宣稱共享或遞減 budget ledger。

Test:攻擊、停損與合法 route 一起重放

day28/ 執行:

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

Acceptance suite 要驗證:

  • Vulnerable route 在 fallback、retry 與 fan-out 下產生實際 receipts;未保留 subject 時明確為 None
  • Independent ledger 精確觀察 20 model、4 tool、4 effects,不依賴 router 的摘要欄位。
  • 重用同一 ledger 連續執行不同 tenant 時,每筆結果只含自己的 run_id partition。
  • 低預算 preflight 會在 executor 前停下,actual model receipt、tool receipt 與 side effect 都是 0,而且 materialize() 根本沒有被呼叫。
  • 超大 fan-out/retry 會先撞 hard ceiling,不配置與輸入大小成比例的 receipt list。
  • 合法的單分支 get_expense 保留 Alice、bamboo-hqexp-bamboo-001,並產生一張 side_effect=false 的 tool receipt。

D28-A01/B01 的固定分母與完整比較是由 stage renderer 重放 frozen metric rows;當日 acceptance 是 6 passed,新增的一項就是 sequential cross-tenant ledger reuse。這兩種 evidence 不可把 case 數量混在一起。

MDASH:重點是可稽核 routing,不是抄一個 90/10 配方

Day 25 已處理「平台標籤不等於業務副作用」,這裡只留與 route budget 直接相關的 MDASH 邊界,避免把同一段 benchmark 再變魔術一次。

Microsoft 在 2026-07-27 的第一方文章報告:MDASH 搭配 MAI-Cyber-1-Flash 與 GPT-5.4,在 CyberGym 得到 95.95%(約 96%),比文中 Mythos 高 12 個百分點。文章也說 MAI-Cyber-1-Flash 可處理最高約 90% 的任務,剩餘約 10% 由 GPT-5.4 處理;成本聲稱則限定為相較當時最佳的 MDASH 組合 (GPT-5.4、GPT-5.4 mini 與 GPT-5.3 Codex)節省 50%。這不是「每種工作負載都省一半」,更不是本 lab 的實測成本。

這些是 Microsoft 在特定 benchmark、MDASH harness、模型與 routing 配置下的報告,不是大魔術熊貓工程司重現的結果。CyberGym 衡量的任務也不等於「保護任意 Agent 不受 prompt injection」。Microsoft 的模型頁將 MAI-Cyber-1-Flash 描述為透過 MDASH 提供給 verified defenders,並要求登記興趣;不能據此寫成「所有 Foundry 用戶都能從 model catalog 直接部署」。

本系列真正借用的工程判斷只有這一個:

專用模型/primary/fallback/高成本模型
  都要留下 route decision、品質 oracle 與 budget receipt

把 Microsoft 報告的 90/10 原封不動貼到另一套工作負載,既不是最佳化,也不是治理,只是讓百分比換了一間辦公室上班。

Microsoft Foundry 對應與 Preview 邊界

Foundry 可以承載模型、Agent、tracing、evaluation 與多 Agent 整合,但 route budget 與業務工具 PEP 仍由應用程式負責。截至 2026-08-03

  • Foundry Agents core 在 GA readiness 表列為 GA。
  • Tracing 依 Agent 類型拆分:prompt/hosted agents GA;workflow/external agents Preview;Tracing VNet 仍是 Preview。
  • Incoming A2A 仍是 Preview。
  • Agent Monitoring 仍是 Preview。

本篇沒有執行 Foundry model call、A2A 或 billing query;所有 token、cost 與 latency 都是本機 deterministic proxy,狀態為 PENDING-CLOUD

NIST SP 800-207A 支持 cloud-native application/service identity 與 granular application policy;「每一跳重驗、child 能力只能維持或縮小」則是本 repo 自訂的 route invariant。這是作者的工程映射,不是 NIST token schema 或 conformance claim;AI 600-1 的 value-chain 視角也只用來提醒我們把 fallback model、資料來源與工具寫進 evidence。

Residual Risk:Preflight 看不到執行中偷偷長出的分支

Preflight 依賴 projection 完整。若 runtime 執行中自行新增工具、retry 或 fallback,budget counter 又沒有重新檢查,原本通過的計畫仍會漂移。Process-local ledger 已按 run_id 隔離並對記憶體寫入加鎖,但沒有跨 process 的 durable reservation 或時間窗 quota。Deterministic proxy 沒有包含供應商即時價格、快取、並行計費、網路等待或 quota;ga_secure 只是本機 profile 名稱,不是 Foundry GA 的證明。

Shared identity 在這一天只被觀察,尚未完整拆開;subject="alice" 出現在 receipt,也不代表它可信。Day 29 會讓每個 hop 驗 issuer、subject、tenant、audience、capability、resource、depth、expiry 與 nonce,同時檢查「通過驗證的 claims 是否真的綁到 sink tool」,不把尚未實作的 execution grant 先寫成完成品。

官方、規範與研究來源

以下資料均於 2026-08-03 查閱:


上一篇
Day 27|Microsoft Foundry 的 AI Agent 攻防實戰:告警響了,火還沒滅
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言