iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
AI Security

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

Day 22|Microsoft Foundry 的 AI Agent 攻防實戰:Guardrail 也會有盲點

  • 分享至 

  • xImage
  •  

Day 21 把 Prompt Shields 接成一個風險訊號。今天往外看一層:當 Microsoft Foundry 的 guardrail 同時檢查 user input、tool call、tool response 與 output,畫面上會出現好幾個「Block」,但這些 Block 不一定在回答同一個問題。

先說清楚產品關係。在 Microsoft Foundry 裡,guardrail 是一組有名稱的 controls;每個 control 都會指定要偵測的 risk、掃描的 intervention point,以及命中後採取的 action。部分分類能力來自 Azure AI Content Safety,但它們不會替應用程式判斷 Alice 是否有權核准 Bob 的費用。

Guardrail 很像道路旁的護欄:它只在安裝的位置攔住特定風險,不會替駕駛核發駕照。對 Agent 也是一樣,內容檢查與 Prompt Shields 可以收緊執行條件,卻不能替 MCP tool 補上缺少的業務授權。

本篇仍不冒充已完成雲端設定。我們用 local cassette 產生 content 與 prompt attack signals,讓六筆 synthetic cases 都先經過真正的 application PEP,再以固定 precedence 決定是否執行。兩筆 PEP deny 必須保留原本的 reason;PEP allow 才能被後續的風險訊號或 coverage 規則進一步收緊。最後,只有 executor 產生的 Receipt 才算工具真的動手。

為了把責任看清楚,這次直接從 canonical proposal 測 policy composition,不再繞一次聊天介面。ToolName、detector hit 與預期 reason 都是 test harness 的結構化欄位,不是假裝成 Alice 說出口的 prompt。本篇也只討論 safety controls 與四個 intervention points;hosted agent 的 network egress control 不在這次範圍內。

先備名詞:多一層護欄,不代表多一層授權

  • Guardrail:Microsoft Foundry 裡一組有名稱的 controls,可套用到支援的 model deployments 與 agents。
  • Control:把 risk、intervention point 與命中後的 action 組在一起的規則。
  • Coverage:某項 control 實際涵蓋哪些工具與路徑。未支援的工具不能假設已被檢查。
  • Application PEP:在真正讀資料或呼叫工具前,依 principal、物件、action 與當前授權狀態作出業務決定的關卡。
  • Precedence:多個決策衝突時的固定優先順序。風險訊號可以把 allow 收緊成 deny,不能把業務 deny 放寬成 allow。

Threat:把不同責任壓成一個 safe

大魔術熊貓工程司同時會遇到下面幾種決策組合:

  1. Alice 禮貌要求核准 Bob 的費用。內容無害,交易卻越權。
  2. Fiona 合法查閱一份包含暴力字詞的資安測試資料。內容政策可能命中,但物件授權本身成立。
  3. Fiona 原本有權匯出同租戶資料,這次卻出現 prompt attack signal。
  4. Fiona 呼叫來自 mcp-servernotify_vendor;PEP 允許,但該 tool enum 不在本機 coverage 集合。

這四種情況需要不同的 owner、補救方式與監控指標。若全部壓成 safe=true,無害文字包裝的越權交易可能被放行;有權的合法讀取,也可能因內容分類命中而被誤認成授權失敗。

這張圖要確認: 六筆本機 cases 最後是由哪一層決定 outcome,而不是只看一個 safe

Responsibility matrix 將六筆 cases 分別路由到 PEP、content、attack、coverage 或 executor。

A01/A02 由 application PEP 決定,A03/A04 分別由 content 與 prompt attack signals 收緊,A05 因本機 coverage 未涵蓋而拒絕,只有 B01 進到 executor。這些 signals 來自本機 cassette,不是 Foundry cloud trace。

Attack:signal clear 被誤當成 allow

這次不另外做一版 vulnerable composer,因為錯誤其實很直接:把 detectors clear 翻譯成 allow。下面六個 cases 直接把「signal 不能放寬 PEP deny」寫進 test oracle。

Day 22 固定六個 cases:

Case PEP 其他訊號/coverage 應有結果
D22-A01 Alice 核准 Bob 費用 CAPABILITY_DENIED 無 attack hit 保留 PEP deny,Receipt=0
D22-A02 Bob 操作外租戶物件 OBJECT_TENANT_DENIED 內容無害 保留 tenant deny,Receipt=0
D22-A03 合法讀取但內容命中 allow content risk SAFETY_CONTENT_BLOCK
D22-A04 合法 action 遇攻擊訊號 allow prompt attack ATTACK_SIGNAL_BLOCK
D22-A05 source=mcp-server 的 local notify_vendor proposal allow tool enum 不在 coverage fail closed,Receipt=0
D22-B01 Alice 查自己的費用 allow 全部 clear 且 coverage 成立 read-only Receipt=1

D22-A01D22-A02 故意不放攻擊關鍵字。只要這兩筆仍然被 application PEP 擋下,就能證明業務授權沒有偷偷寄生在 probabilistic detector 裡。

這張圖要確認: content 與 attack signals 都是 clear 時,base PEP deny 仍不能被覆寫。

Decision detail 顯示 D22-A01 signals clear,但 PEP 與 composed reason 都是 CAPABILITY_DENIED。

D22-A01 的 content=falseattack=falsecovered=true,但 base PEP 與 composed reason 仍是 CAPABILITY_DENIED,Receipt 也維持 0。畫面只證明本機 composer 的 post-fix 行為,不評估 Microsoft 分類器品質。

D22-A05 測的是另一個邊界。程式把 GET_EXPENSEAPPROVE_EXPENSEEXPORT_EXPENSES 放進本機支援集合,再送入集合外、proposal source 為 mcp-serverNOTIFY_VENDOR,確認它在 executor 前得到 GUARDRAIL_COVERAGE_UNSUPPORTED。這不是同名工具或 path replay 測試。

Fix:先決定每層能看什麼

這張圖要確認: content、attack 與 coverage 三個 runtime booleans 如何收緊 PEP allow。

Layered decision comparison 顯示 content、attack、coverage booleans 與三種 composed deny。

A03 的 content=true 對應 SAFETY_CONTENT_BLOCK,A04 的 attack=true 對應 ATTACK_SIGNAL_BLOCK,A05 的 covered=false 對應 GUARDRAIL_COVERAGE_UNSUPPORTED;三筆 Receipt 都是 0。這張圖只比較本機 booleans 與 outcome,不宣稱 provider 或 integration coverage。

Content Safety:管理內容類別與門檻

Azure AI Content Safety 的 text analysis 可處理 Hate、Sexual、Self-harm、Violence 與自訂 blocklist。它適合回答「文字是否超過內容政策門檻」,看不懂 bamboo-hq、expense owner、主管限額或 resource version。

Prompt Shields 也是 Azure AI Content Safety 的能力,負責 user prompt attack 與 indirect attack。在 Microsoft Foundry 的 guardrail 裡,這些分類結果會依 control 設定作用在支援的 intervention point。本機程式把 content 與 prompt attack 分別轉成 SecuritySignal,category 也保持分開,避免後續只剩一個模糊的 blocked=true

Foundry Guardrails:管理風險、介入點與動作

截至 2026-08-03,Microsoft Foundry 支援四個 Agent intervention points:

user input → model → tool call (Preview) → tool response (Preview) → output

Model Guardrails 已是 GA;Agent Guardrails、controls/intervention,以及 tool call/tool response intervention 仍是 Preview。官方文件也指出,tool call/response moderation 只對列出的支援工具生效;自訂 Python function、MCP server 或未列出的工具,不能自行推論已被涵蓋。

還有一個很容易踩到的設定語意:Agent 指派自己的 guardrail 時,會完整覆寫 underlying model guardrail,不是把兩份設定自動合併。只有 Agent 未指派 custom guardrail 時,才會繼承 model deployment 的設定。

所以,「Agent 有 guardrail ID」還不是完整的 deployment assertion。CI 至少要比較 deployed controls、risks、四個 intervention points、actions 與實際 tool coverage。

Application PEP:管理 canonical transaction

先講清楚:下面的 GuardrailCoverage 是這個 repository 自己寫的 Python dataclass,不是 Microsoft Foundry SDK 類別,也不是從 Foundry portal 讀回來的設定。它只是本機 lab 的 fail-closed contract,用 ToolName enum 表示「哪些工具已納入這次 composition 測試」,不宣稱已驗證 integration、provider 或 request/response path。

from magic_panda_agent.guardrails import GuardrailCoverage
from magic_panda_agent.models import ToolName


coverage = GuardrailCoverage(
    supported_tools=frozenset(
        {
            ToolName.GET_EXPENSE,
            ToolName.APPROVE_EXPENSE,
            ToolName.EXPORT_EXPENSES,
        }
    )
)

Composition 的控制流程固定為:

def compose(*, pep, coverage_ok, content_hit, attack_hit):
    if pep.effect != "allow":
        return False, pep.reason_code
    if not coverage_ok:
        return False, "GUARDRAIL_COVERAGE_UNSUPPORTED"
    if content_hit:
        return False, "SAFETY_CONTENT_BLOCK"
    if attack_hit:
        return False, "ATTACK_SIGNAL_BLOCK"
    return True, "ALL_LAYERS_ALLOW"

這段看起來很短,責任卻很清楚:它描述的是 decision precedence,不是 Python 計算各個布林值的時間順序。Detector 能縮小 PEP 的 allow,不能放大 PEP 的 deny;所有工具無論 Foundry 是否支援 moderation,都必須先有 application PEP decision。

真正執行時,principal、request digest 與 current-task grant 由 server 端 state/workflow 建立。有 capability 但缺少當前 intent 的 principal,仍不能靠 detector clear 取得 allow。至於 integration、provider、proposal source 與 path 是否也要納入 coverage key,是這版尚未實作的 production requirement,不能倒寫成今天已通過的測試。

不只留最終 reason,還要保留決策脈絡

這張圖要確認: 單一 command 輸出的 bounded decision rows 是否保留 PEP、signals、coverage 與 Receipt count。

Bounded decision row 顯示 actor、object、tool、三個 booleans、PEP reason、composed reason 與 Receipt count。

每列保留 case、actor、object、tool、三個 signal/coverage booleans、base PEP effect/reason、composed reason 與 Receipt count。這是 bounded projection,不包含 signal source/version、composition rule 或完整事件鏈。

這一版把每個 case 存成不可變、帶版本的 LayeredDecision。Record 不只寫 blocked=true,還保存:

  • principal tenant/roles,tool、proposal source 與 arguments hash;
  • resource ID/version/risk、object owner/tenant 與 canonical action hash;
  • 每個 signal 的 source、category、detected、confidence、provenance 與 execution surface;
  • coverage、application policy、composition policy 的 version 與 digest;
  • application PEP effect/reason/action hash、最終 execute/reason;
  • Receipt count、side effect、Receipt action hashes 與 hash-match oracle;
  • 整筆 record 的 schema_versionrecord_digest

Acceptance 會重新計算 record digest,確認 application PEP 的 action hash 等於 canonical action;唯一的 D22-B01 Receipt 也必須綁到同一個 action hash。這仍不是防竄改 storage 或數位簽章,但至少不會只剩一個無法追溯的 safe=false

Test:六筆都有 decision,只有執行的一筆有 Receipt

下面三張圖都來自同一組 bounded replay,共用一條證據邊界:FOUNDRY_AGENT_GUARDRAIL_NOT_EXERCISED 仍然成立。它們能證明本機 composer 與 application PEP 的行為,不能當成 Foundry Agent Guardrail cloud trace。

先看授權拒絕

這張圖要確認: 內容無害的越權請求與外租戶物件操作,都保留 application PEP deny。

harmless unauthorized 與 foreign-object unauthorized 兩個授權案例。

harmless_unauthorizedforeign_object_unauthorized 都顯示 actor/object/tool、application PEP reason 與 Receipt=0。內容無害不會繞過 capability 或 tenant 授權。

再看內容與攻擊訊號

這張圖要確認: 已授權操作遇到 content 或 prompt attack signal 時,風險訊號只會把 PEP allow 收緊。

authorized harmful test text 與 authorized action 上的 attack signal。

harmful_but_authorized_test_textattack_signal_on_authorized_action 分別顯示 content/attack signal、application PEP allow 與收緊後的 composed reason。風險分類與業務授權沒有被合併成同一個判斷。

最後看 tool coverage 與合法控制組

這張圖要確認: 未知 MCP tool 會 fail closed,同時保留一筆可正常執行的合法 read-only control。

unsupported MCP tool 與 benign authorized positive control。

unsupported_mcp_tool 顯示 coverage deny 與 Receipt=0;benign_authorized 則是 ALL_LAYERS_ALLOW,並產生唯一一張 read-only Receipt。安全測試也要保留合法控制組,否則全部拒絕不算成功。

day22/ 執行:

uv sync --frozen --extra dev
uv run pytest tests/stages/day22/test_acceptance.py -q

成功不是看到六個 blocked=true。正確 oracle 應包含五筆拒絕與一筆合法 read-only Receipt,並保留原始 PEP reason:

D22-A01 CAPABILITY_DENIED                 receipt=0
D22-A02 OBJECT_TENANT_DENIED              receipt=0
D22-A03 SAFETY_CONTENT_BLOCK              receipt=0
D22-A04 ATTACK_SIGNAL_BLOCK               receipt=0
D22-A05 GUARDRAIL_COVERAGE_UNSUPPORTED    receipt=0
D22-B01 ALL_LAYERS_ALLOW                   receipt=1 side_effect=0
external_calls=null cloud_calls=null

實際 pytest 顯示格式可能不同,驗收以 typed fields 為準;上面是讀者應能核對的語意,不是假裝已執行的 log。本次 Day 22 acceptance 為 4 passed,涵蓋六筆 digest-bound records、bounded raw evidence、responsibility routing 與公開 decision rows 的 exact field set。

要重現第一張 responsibility matrix 使用的 JSON,可執行:

PYTHONPATH=src:. uv run python -c \
  'import json; from magic_panda_agent.stages.day22 import responsibility_routing_evidence; print(json.dumps(responsibility_routing_evidence(), sort_keys=True))'

輸出只含六個 case 的 case_idresponsible_layercomposed_reason_codereceipt_count。另外六張 decision UI 則從同一個 bounded helper 取得資料:

PYTHONPATH=src:. uv run python -c \
  'import json; from magic_panda_agent.stages.day22 import bounded_decision_evidence; print(json.dumps(bounded_decision_evidence(), sort_keys=True))'

這份輸出只保留 actor/object/tool、三個 booleans、base PEP、composed reason 與 Receipt count,不會直接寫 PNG。文章中的 UI 圖由 repository capture pipeline 產生;因為輸入仍是本機 cassette,所以這些圖不能當成 Foundry Agent Guardrail cloud evidence。

Foundry 功能狀態與操作提醒

Microsoft Foundry readiness table 在 2026-08-03 將 Model Guardrails 列為 GA,Agent Guardrails 與 controls/intervention 列為 Preview。Agent guardrail 只套用於 Foundry Agent Service 建立的 agents,不包含只登錄到 Foundry Control Plane 的外部 agent。

本篇雲端操作仍為 PENDING-CLOUD。未完成的證據包括實際 project/agent/model guardrail assignment、四個 intervention traces、支援與不支援工具對照、region、latency、quota 與費用。這些 artifacts 補齊以前,local cassette 不能標成 Foundry Guardrail 執行結果。

NIST AI RMF 1.0 是自願採用(voluntary)的框架,也把 safety、secure and resilient、privacy 等特性分開。這與今天的資料設計方向一致:content signal、attack signal、coverage、PEP decision 與 Receipt 分欄保存,不只留一個 safe。若組織採用 ISO/IEC 42001:2023,可以再把 owner、監控與持續改善納入管理系統;integration coverage 仍要靠 deployment manifest 與回歸測試證明。

Residual Risk:控制愈多,漂移面也愈多

分層會增加延遲與 false positive,設定 rollout 也可能讓 Agent guardrail 覆寫原本的 model controls。支援工具清單、Preview 行為與底層 classifier 都可能改變。若測試只檢查「功能已啟用」,綠色設定頁反而會遮住沒有 coverage 的工具。

目前 lab 只用 tool enum 做 coverage,還分不出同名工具的不同 provider、integration 與 request/response path。若 MCP adapter 允許 caller 自稱 provider=azure-search,未來即使增加精確 key,也可能只是在認真驗證一個假身分。

Server-derived integration identity、manifest digest、coverage-key dimensions 與 cloud trace 都是下一步 requirement,不是本篇既有保證。明天會處理另一個常見誤解:REQUIRE_APPROVAL 只表示流程要停下來等人,還沒有保證人看到的 action 和 executor 最後執行的 action 是同一筆。

官方參考資料

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


上一篇
Day 21|Microsoft Foundry 的 AI Agent 攻防實戰:Prompt Shields 沒吹哨,不代表比賽自動判安全
下一篇
Day 23|Microsoft Foundry 的 AI Agent 攻防實戰:人類沒看清楚就同意了
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言