iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
AI Security

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

Day 26|Microsoft Foundry 的 AI Agent 攻防實戰:工具擋住了,Secret 卻儲存在 Trace 裡

  • 分享至 

  • xImage
  •  

昨天我們用評測結果與工具紀錄,檢查 Agent 有沒有造成不該發生的副作用。今天接著看另一份資料:執行過程留下的 trace。

Trace 能幫我們找到請求卡在哪一步。不過,記錄得越詳細,保存的資料也越多。假設呼叫端傳入 capture_content=true,要求把內容一起留下來,應用程式要不要照做?

Day 25 的程式已經有內容遮罩。今天沿用這道控制,再把「哪些內容可以保存」交回伺服器決定,同時確認調查時需要的關聯資訊還在。

先看這次到底改了什麼

我們繼續用大魔術熊貓工程司的 Alice。她屬於 bamboo-hq,這次卻要求查另一個租戶的費用:

幫我查一下月兎實驗室的費用 exp-moon-001。

這筆請求在 Day 25 與 Day 26 都會被 PEP 以 OBJECT_TENANT_DENIED 擋下,不會產生工具 Receipt。今天比較的是被拒絕之後,trace 留下哪些資料。

觀察項目 Day 25 Day 26
跨租戶費用查詢 拒絕 拒絕
工具 Receipt 0 0
呼叫端要求保存內容 接受,但內容經過遮罩 由伺服器決定,省略 content
Canary 原文命中 0 0
決策與流程關聯資訊 保留 保留

新增的控制是 secure_tracing=True。兩個版本都已經遮罩,差別是修補後由伺服器決定保存範圍,呼叫端不能自行要求留下自由文字,連遮罩後的內容也不例外。

前一天的遮罩繼續保留,才能看清楚今天這項設定多做了什麼。

用 Canary 檢查資料有沒有被留下來

先補幾個等一下會看到的名詞。一筆請求跨越不同步驟的紀錄叫做 trace,裡面每個步驟叫 span。Span 可以附帶 attributes,例如工具名稱、決策或錯誤代碼。負責把資料送到遙測後端的元件叫做 exporter。

測試則會放入一段醒目的合成字串,作為 canary。只要它出現在不該保存的位置,我們就能沿著資料路徑找問題。這次使用的值與注入位置如下:

SYNTH_MAGIC_PANDA_TRACE_CANARY

proposal source/request metadata.trace_fixture/nested exporter fixture

三條路徑分別是 proposal source、request 的 metadata.trace_fixture,以及一份獨立的巢狀 exporter 測試資料。它們都由測試框架注入,Alice 的對話仍然只有前面的查費用要求。

這三份資料分別用來檢查來源欄位、請求 metadata 與 exporter。它們沒有經過成功的跨租戶工具讀取,因此下面看到的 canary 結果,範圍也限於這三條注入路徑。

實際請求沿著 service 執行:

Alice:「幫我查一下月兎實驗室的費用 exp-moon-001。」
  → harness 在 proposal source 與 request trace metadata 注入 TRACE_CANARY
  → magic-panda-procurement-agent
  → ToolProposal
  → PEP:deny
  → SecurityEvent
  → Telemetry.export_span()
  → in-memory trace store

PEP 拒絕查詢後,流程仍然會產生安全事件並輸出 span。也就是說,工具擋住了,後面的紀錄處理仍然需要自己的控制。

下圖把同一筆請求在兩個版本的結果放在一起。先看 content_present,再核對兩邊的拒絕原因與 Receipt。

Trace capture comparison 顯示 before 僅保留已遮罩 content,secure after 省略 content,兩邊均零 canary 與零 Receipt。

Day 25 保留經遮罩的內容:content_present=true、content_redacted=true;Day 26 則省略內容。兩邊都是 OBJECT_TENANT_DENIED、零 canary 與零 Receipt。這是本機測試結果。

跑一次前後比較

在已備妥相依套件的 day26/ 環境,可以使用 trace_capture_summary() 取得這份結果:

from magic_panda_agent.stages.day26 import trace_capture_summary

evidence = trace_capture_summary()

print(
    {
        "delta": evidence["declared_delta"],
        "before_content_present": evidence["before"]["content_present"],
        "before_content_redacted": evidence["before"]["content_redacted"],
        "after_content_present": evidence["after"]["content_present"],
        "after_lineage": evidence["after"]["lineage"],
        "after_correlation": evidence["after"]["correlation_equal"],
    }
)

這段程式先取得 before 與 after 的執行資料,再挑出今天關心的欄位。declared_delta 記錄控制差異,content_present 與 content_redacted 檢查內容處理,lineage 與 correlation_equal 則用來查看來源分類與流程關聯。

預期結果是:

delta == [("secure_tracing", True)]
before_content_present is True
before_content_redacted is True
after_content_present is False
after_lineage == ["custom-runtime"]
after_correlation is True

delta 只有 secure_tracing。兩邊的授權與既有遮罩保持開啟,trace、event、response 的 canary 命中都為 0,response 與 state 中的 Receipt 也都為 0。

要在終端機一次查看這些結果,可以執行:

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

這個命令輸出 JSON,方便對照欄位。它使用記憶體中的 trace store,還沒有把資料送進 Application Insights。

先決定欄位,再做遮罩

工程司把診斷資料與安全紀錄分成兩條處理路徑:

診斷路徑:allowlisted attributes → redact before export → sampled trace
安全路徑:policy decision/approval/receipt → sanitized event → 不依賴 trace sampling

診斷 trace 可以依需求抽樣;授權決策、核准與 Receipt 這些安全紀錄,則不能跟著 trace sampling 一起消失。否則事後想查某次操作,只因為當時沒有被抽樣,就找不到關鍵紀錄。

哪些欄位可以送進 Exporter?

第一步是縮小資料範圍。Telemetry.ALLOWED_ATTRIBUTES 接受案例 ID、工具名稱、決策、reason code、action hash、receipt ID 與 tenant partition 等結構化欄位。prompt、authorization、cookie、任意 metadata 與完整文件內容,預設不送進 exporter。

想像我們正在查一筆拒絕事件:工具名稱、決策與 reason code 已經能回答「哪個工具被什麼規則擋住」。若不需要整份文件,就不必把原文一起存下來。先從調查用途決定欄位,會比事後逐項遮罩更容易管理。

接著才做遮罩。即使欄位名稱在允許清單內,值仍可能意外含有 canary。因此資料交給 exporter 或寫進 store 以前,還要把這些值換成 [REDACTED]。

如果等到查詢畫面才隱藏內容,原文已經存進後端。其他查詢、匯出或讀取路徑仍可能取得它,所以遮罩的位置必須放在輸出以前。

字典的 Key 也可能帶資料

遮罩時很容易只處理 value,漏掉 key。這份實作也會處理字典 key;如果兩個 key 遮罩後變成相同字串,程式會加上可重現的 suffix,避免後一筆靜默覆蓋前一筆。

序列化也有界限:循環參照、過深或過大的資料、過多節點或項目、非有限數值,以及不支援的型別,都要在寫入 store 前停止。set 與 frozenset 會先排序,讓相同資料可以得到穩定結果。

這些處理讓本機測試能涵蓋較複雜的輸入。正式 exporter 仍應優先使用固定 schema,減少任意欄位流進來的機會。

來源與關聯資訊怎麼保留?

這次 custom runtime 還會把 canary 填進 proposal source。若伺服器直接相信 adapter 自報的來源,外部元件就可以替自己的資料加上看似可信的標籤。

實作因此由 server 依 adapter class 決定外部 runtime 的分類:Foundry adapter 對應 foundry-model-output,custom adapter 對應 custom-runtime。任意 source 原文不會直接進入 event 與 response。

這裡還有一層需要分清楚。受信任的 LocalRuleRuntime 會使用 model、retrieved-document、memory-summary 表示內容來源,Day 06 與 Day 09 的 PEP 會用到它們。這些分類仍需保留,不能為了統一欄位,就全部改成同一個字串。

下圖檢查 before 與 after 的 correlation_equal 和 lineage。我們希望移除內容後,同一筆請求的關聯資訊仍能對上。

Correlation detail 顯示 before/after correlation equality、custom-runtime lineage、相同 deny 與零 Receipt。

兩邊的 correlation 都一致,外部 runtime 都被分類成 custom-runtime;拒絕原因與零 Receipt 也保持相同。這份測試沒有涵蓋竄改 traceparent、baggage 或來源認證。

traceparent 可以讓下游 span 接回同一條 trace,baggage 則能攜帶額外 key-value。它們用來關聯流程,不能充當授權證明,也不適合放入 email、tenant 原文、token 或業務角色。

知道兩個步驟屬於同一筆請求,並不代表已經驗證請求者的身分。PEP 仍然要依可信任的身分與業務規則判斷權限。

誰可以讀這些紀錄?

資料經過遮罩後,讀取端仍然需要授權。今天的本機測試用 auditor 與 alice 做對照:auditor 可以讀 sanitized trace,Alice 則收到 TRACE_READER_DENIED。

下圖另外準備一份直接寫入 trace 的測試資料,同時檢查 canary、correlation 與兩個讀取者的結果。

Reader test 顯示零 canary、correlation true、auditor 讀到兩個 spans,Alice 被 reader gate 拒絕。

Canary 命中為 0,correlation 一致;auditor 能讀到 2 個 spans,Alice 被拒絕。這裡只測同一個 process 內的讀取白名單,尚未驗證 Azure RBAC。

這張圖使用獨立的讀取測試,沒有重建完整的 decision、Receipt、error 與 timestamp 鏈。要回查前面的 service 流程,仍得看它自己的資料,不能把兩份 fixture 當成同一次執行。

執行當天的 acceptance test:

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

這組測試主要確認:

  • Day 25 到 Day 26 只新增 secure_tracing=True,兩邊都保留 PEP 與輸出前遮罩。
  • 相同跨租戶請求都被拒絕、Receipt 為 0;before 保存遮罩後內容,after 省略內容。
  • Canary 由測試框架注入,trace、event 與 response 都沒有原文;來源分類與 correlation 符合預期。
  • set、frozenset、tuple、Pydantic model、date 與 datetime 經過 JSON normalization 後才遮罩。
  • 不支援的型別以 UNSUPPORTED_EXPORT_TYPE 拒絕,發生在 store mutation 以前,也不呼叫輸入物件自訂的 __str__。
  • Day 25 的本機 fixture 允許 Alice 讀取;Day 26 拒絕 Alice,並允許 auditor。

這組本機驗收的保存結果是 8 passed。當日 acceptance 沒有驗證 event-type export allowlist 或 receipt correlation;巢狀 defensive deep copy、cycle、bounds 與 key collision 等項目,則另有累積的回歸測試,不能全部算進當日 acceptance。

想分別查看 capture 與 reader 的結果,可以使用:

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

第一個 helper 提供前後內容保存與關聯資訊;第二個提供 direct trace fixture 的讀取結果。兩個命令都產生本機 JSON,APPLICATION_INSIGHTS_EXPORT_NOT_EXERCISED 仍然成立。

Foundry Tracing 與本機 Exporter 的分工

本機 exporter 的欄位白名單是資料送出前的一關;接上 Foundry tracing 後,還要檢查框架自動產生的 span 是否繞過這一關。可以用同一個合成 canary,在 Application Insights 依 correlation 找到這次呼叫,再以兩個不同 reader 身分查看可見欄位。沒有實際 ingestion 與讀取結果,本篇的截圖仍只證明本機 exporter。

Foundry tracing 使用 OpenTelemetry,資料會進入連接的 Azure Monitor Application Insights。Trace 可能含有 prompt、模型輸入輸出、工具呼叫與中間步驟,這些資料的保存範圍、存取權限與保留期限仍需由使用者設定。

官方功能表將 prompt/hosted agents tracing 列為 GA,workflow/external agents tracing 與 Tracing VNet 列為 Preview;private Application Insights 的部分端到端路徑也有限制。選擇整合方式時,要對到實際採用的 Agent 類型與網路路徑。

若採用 Foundry 的 visual designer 或 Portal 內 workflow 執行,還要留意這兩條路徑將在 2026-12-01 停止支援。既有 YAML 可改由 hosted agent 執行;新編排則依官方建議使用 Microsoft Agent Framework。官方遷移說明。

留下足夠調查的資料,也知道哪些還沒測到

今天我們確認了一件具體的事:取消呼叫端要求的內容保存後,本機流程仍保留拒絕事件、來源分類與關聯資訊。讀取端也加入了 auditor 與 Alice 的正反向對照。

範圍仍然限於 magic_panda_agent 已知的 exporter。其他 instrumentation 可能另外記錄 exception、HTTP header、log 或 baggage,需要逐條檢查。本機 store 雖然在寫入時做了 deep copy,讀取 API 目前只回傳新的外層 list,也還不是不可變的資料視圖。

另外,查不到 trace 不一定代表事件沒發生。資料可能尚未送達、被抽樣略過,或查詢者沒有權限。調查時要把這些原因分開,才不會把空白畫面當成安全結論。

NIST CSF 2.0 的 Detect 與 ISO/IEC 27002:2022 的 logging、access control guidance,可以幫我們檢查責任與控制範圍。至於這個 Agent 要保存哪些欄位、誰能讀、遮罩放在哪裡,仍然需要落到實作與測試。

Day 27 會接著處理事件應變:同一個 alert 重送兩次時,系統會不會重複撤權?Kill switch 是否在工具執行前生效?今天留下的決策與關聯資訊,就會成為追查處置流程的起點。

官方與研究來源


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

尚未有邦友留言

立即登入留言