iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
AI Security

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

Day 20|Microsoft Foundry 的 AI Agent 攻防實戰:Private Endpoint 也有風險

  • 分享至 

  • xImage
  •  

Day 18 已把 Foundry、Search 與 Key Vault 的權限分開,Day 19 也讓舊 secret version 失效。現在先做一個尚未在 V2 雲端執行的假設:如果大魔術熊貓工程司替 Foundry resource 建立 Private Endpoint,而且 Portal 顯示 Approved,整條 Agent 系統是否就已經私有?這條雲端路徑目前是 PENDING-CLOUD;本文沒有 portal capture,也沒有把假設寫成已取得的證據。

答案要看你問的是哪一條路。Foundry inbound、Foundry 到 PaaS dependency、Agent 到工具的 egress,各有自己的 DNS、public network access(PNA)、身分與路由政策。Private Endpoint 的綠燈只證明那一個 connection 被核准,不是封包拿到「良民證」,可以一路免檢查。

先備名詞:私網不是一顆總開關

  • Private Endpoint:把某一個 Azure 服務端點映射到 VNet 內的私人 IP;它只處理指定資源與連線。
  • PNA(Public Network Access):資源是否仍接受公網入口。Private Endpoint 已核准,不代表 PNA 已關閉。
  • Private DNS:讓原本的服務 hostname 在 VNet 內解析到私人 IP。DNS 正確仍不等於 TLS、身分與 RBAC 正確。
  • Inbound/Egress:進入服務的流量與服務向外送出的流量;兩邊要分開做 allow/deny 測試。
  • Canonical resource ID:政策與 adapter 共用的唯一、無歧義資源表示;encoded slash、dot segment 或空 segment 必須拒絕,不能各自解讀。

Threat:一張網路圖把三條路畫成同一條

1. Client/BFF → Foundry endpoint             (inbound)
2. Foundry/Agent platform → Search/KV/Storage(PaaS dependencies)
3. Agent runtime → private tool/approved API  (runtime egress)

路徑一:Client/BFF 到 Foundry

需要一起驗:

  • Foundry Private Endpoint connection 已核准。
  • VNet 內使用原本的服務 hostname查詢時,Private DNS 回 private IP。
  • PNA 符合決策,若要求 private-only 就必須停用 public access。
  • VNet 外沒有因 public fallback 而成功。
  • VNet 內網路可達後,錯誤 principal 仍得到 401/403。

DNS 回 private IP 只證明名稱解析;TCP 443、TLS、authentication 與 DataActions 仍要各自測。Microsoft 的 troubleshooting 也提醒,403 通常是 authentication/RBAC,而不是 Private Link 自己壞掉。Configure network isolation(查閱:2026-08-03)

路徑二:Foundry 到每個 PaaS dependency

Foundry resource 自己有 Private Endpoint,不會替 Search、Storage、Cosmos DB、Key Vault 與 monitoring 自動建好相同路徑。Standard Agent setup 的客戶自管資源要逐項建立 private path、DNS 與 data role:

Dependency 要保存的網路證據 身分/資料控制
Azure AI Search PE、private DNS、PNA deny data role + Day 15 tenant filter
Key Vault PE、privatelink.vaultcore.azure.net 解析、PNA deny Secrets User + Day 19 rotation
Storage 對應 blob/file PE 與 DNS container/object data role
Cosmos DB 實際採用 API 的 private path Agent state 與應用 tenant 分區
Application Insights 實際 topology 支援的 private path export 前 redaction、reader RBAC

這裡要把 inbound 與 Agent outbound 分開讀。Inbound Private Endpoint 會依實際 setup
有不同支援路徑;涉及 customer-owned Search/Storage/Cosmos DB 與 Agent outbound
isolation 的文件,目前指向 Standard setup + private networking。Basic 使用
Microsoft-managed backing resources,不能拿它當成「我已私接自己的 Search」的
證據。另一方面,另一份 isolation 文件仍列出 Basic private-network template;因此
文章不把一句 setup 標籤推廣成整套保證,而是保存實際 template、runtime、dependency
ownership,並逐條測 packet path。若 Search indexer 也要穿越 Private Endpoint,還要
檢查它的 execution environment;否則可能出現 indexer 沒資料、Agent 查詢卻沒有
明顯錯誤的 silent failure。

路徑三:Agent runtime 出站

即使 Azure dependencies 都走 Private Link,poisoned tool description 仍可能誘導 Agent 呼叫未核准 MCP server 或 Internet endpoint。網路可達不代表工具可用,工具可用也不代表這次 action 有權執行。

正式系統需要同時保存網路與授權證據,但 V2 Lab 沒有假裝用一個物件包辦全部。NetworkBoundaryPolicy.validate() 只檢查:

allowlisted host
+ bare HTTPS origin(僅 host,可省略 /,port 只能是 443)
+ trusted egress-allow outcome
+ identity-auth posture
+ PNA/private-DNS posture

Day 18 的 RBAC 會在前一層檢查 principal + plane + action + scope。網路 policy 刻意只接受 bare origin,任何 path、query 或 fragment 都回 EGRESS_ENDPOINT_DENIED;真正的 API path 與 object authorization 必須由可信 adapter/PEP 處理。這個設計比較保守,卻不能被寫成 path-level egress authorization,因為程式碼裡沒有那個能力。

Attack:讓 route 每次只錯一個欄位

畫面判讀目標: 看見相同 principal/RBAC 下 public route 如何因缺少 network checks 取得 Receipt。

Route comparison 顯示 public route 修補前有 Receipt、修補後無 Receipt。

本機 route policy 綠燈不能冒充封包已走私網。 可觀察狀態:before Receipt=1,after Receipt=0;沒有 socket 或 DNS lookup。 Claim boundary:沒有真實 network IO、Private Endpoint 或 Azure service receipt。

從完整應用的上層看,這一天仍只是很普通的需求:「幫我查一下國內出差住宿費的上限。」使用者不會說 dependency=searchegress_allowed=true,更不會替 DNS 與 PNA 宣布合格。本機 stage 為了單獨量網路邊界,不呼叫模型,而是直接把同一個 dependency intent 與不同 route posture 交給 policy。

V2 Lab 只使用 .invalid endpoint,而且不開 socket。DependencyRoute 是合成的 route posture;其中 egress_allowedidentity_auth 是由測試框架注入的狀態,不是封包、Firewall 或 Entra 自己出具的證明。正式 adapter 必須由 trusted configuration 與真實檢查填入,不能接受 prompt 或 request body 自己填 true

三個主要 fixture:

  1. compliant:所有欄位正確,作為正向分母。
  2. public:在 component matrix 中同時使用未核准 host、PNA 開啟、private DNS 未解析、identity auth 關閉。
  3. blocked_egress:host、bare origin、identity 與 private posture 都正確,只把 egress_allowed 改成 false

接著逐欄 mutation:

  • HTTPS 改 HTTP/FTP。
  • 加入 userinfo、非 443 port、query、fragment 或前後空白。
  • bare origin 後面加上 path、query 或 fragment。

before/after cumulative replay 的 principal 已通過 Day 16 workload identity,也具備
Day 18 Search reader fixture;這一組 public_route 特別把 identity_auth=True 固定住,
只比較 network delta。before 回 LEGACY_NETWORK_ROUTE_UNCHECKED 並留下一張 local
executor Receipt;after 的第一個 fail-closed reason 是
DEPENDENCY_ORIGIN_MISMATCH。這不是 component matrix 的
EGRESS_HOST_DENIED:前者表示 trusted registry 已先判定「這個 dependency 不該去
這個 origin」,後者才是較底層 allowlisted-host preflight。PNA、private DNS、identity
與 host 的各欄 failure 仍在 component matrix 個別保存,不要把兩組資料混成同一列。

D20-A03 再做一個比較刁鑽的交換:Search 與 Vault 的兩個 host 都在網路 allowlist,
卻把 Search action 指向 Vault origin。純 host allowlist 會放過,
TrustedDependencyRegistry 仍依 dependency、origin、plane、action、scope 與 auth mode
的完整 tuple 回 DEPENDENCY_ORIGIN_MISMATCH。兩個都合法,不代表可以互換座位。

這個對照很重要。若把 RBAC 也同時拿掉,receipt 歸零可能只是 403,並不能證明網路政策有工作。

Fix:registry 綁交易,再驗 RBAC 與 bare origin

畫面判讀目標: 確認 dependency、plane、action、scope、identity 與 origin 綁在同一 transaction。

Network policy UI 顯示 dependency registry、RBAC 與 origin 的逐欄 deny。

網路可達、工具可用與本次 action 有權是三個問題。 可觀察狀態:wrong fields 各有固定 deny;compliant private route 保留 allow positive control。 Claim boundary:registry 不觀察 DNS、socket、TLS 或 service-side authorization。

TrustedDependencyRegistry 是 server-owned configuration。它從已註冊的功能路徑派生 dependency,不接受 prompt 自報
dependency=search。它先把 dependency 綁到核准的 origin、data plane、action、
scope 與 authentication mode;之後才交給 Day 18 RBAC 與
NetworkBoundaryPolicy。順序是刻意的:先擋 confused deputy,再處理「這條路本身
是否符合 private/egress posture」。

repository 內的 API 是 NetworkBoundaryPolicy.validate():成功回空 list,失敗則回一個或多個固定 reason code。

from magic_panda_agent.network import (
    DependencyRoute,
    NetworkBoundaryPolicy,
)

policy = NetworkBoundaryPolicy(
    allowed_hosts=frozenset({"search.private.example.invalid"}),
    require_private_path=True,
)

route = DependencyRoute(
    name="search",
    endpoint="https://search.private.example.invalid",
    public_network_access=False,
    private_dns_resolved=True,
    egress_allowed=True,
    identity_auth=True,
)

failures = policy.validate(route)
assert failures == []

目前實作只會回下列 failure code:

EGRESS_HOST_DENIED
EGRESS_ENDPOINT_DENIED
EGRESS_POLICY_DENIED
IDENTITY_AUTH_REQUIRED
PUBLIC_NETWORK_ACCESS_ENABLED
PRIVATE_DNS_NOT_RESOLVED

bare-origin contract 接受 https://host、尾端 /,以及顯式 :443 的兩種形式;scheme 大小寫與 host 大小寫會正規化。HTTP、FTP、userinfo、非 443 port、path、query、fragment、前後空白與 malformed URL 全部得到 EGRESS_ENDPOINT_DENIED。這份 contract 沒有真的查 DNS,也沒有打開 socket。

Microsoft Foundry 的 managed network 要在建立前做決策

截至 2026-08-03,Foundry managed virtual network 提供 allow Internet outbound 與 allow only approved outbound 等模式,也能從 managed network 建立到 Search、Key Vault、Storage 等支援服務的 managed Private Endpoint。官方文件目前說明這些 managed PE 需使用 CLI 建立,Portal UI 尚未提供;Foundry resource managed identity 還要具備建立/核准 connection 所需權限。Managed virtual network(查閱:2026-08-03)

這裡有幾個會影響成本與復原的限制:

  • managed network 啟用後不能停用;兩種 isolation mode 也不能任意互換。
  • networkInjections 等屬性要在 resource 建立時設定,不能事後補上。
  • allow-only-approved 加入 FQDN rule 時會建立付費 managed Azure Firewall。
  • FQDN outbound rules 只支援 TCP 80/443。
  • firewall 不能跨 Foundry accounts 共用,SKU 建立後不能修改。
  • managed network 目前沒有 outbound traffic logging。

所以 network mode 不是部署後再按一個「更安全」按鈕。它會影響 resource 拓撲、費用、觀測能力與重建策略,應在建立 Foundry resource 前留下 decision record。

GA/Preview 要按實際路徑標示

Microsoft 的 GA overview 將 Foundry core platform、RBAC 與 virtual network integration 列入 GA enterprise capabilities,但同一份 readiness 表仍將 Tracing VNet 標為 Preview,並提醒不是所有 GA 功能都完整支援 network isolation。Foundry GA overview(查閱:2026-08-03)

此外,network isolation 文件仍以 hosted (preview) agents 描述部分 outbound injection 路徑;hosting 總覽把 managed Hosted Agents 標為 GA,Agent Framework 的 Hosted Agents 子頁卻仍標 Preview,部分 session/API 也要求 preview surface。第一方文件的粒度與更新節奏沒有完全對齊,文章因此不寫「Foundry networking 全面 GA」,而是逐項記錄:

  • inbound Private Link/PNA:依採用的 GA resource path 驗證。
  • managed VNet/managed PE:依 region、CLI 與當時文件確認。
  • Hosted Agents/hosting integration:依實際 managed surface、session API、SDK/package 與 region 逐項核對,不用母項標籤替子路徑升級。
  • Hosted Agent 的特定 outbound injection path:依 network isolation 文件保留 Preview/待實測邊界。
  • Tracing VNet:Preview。
  • 個別 tools:查 catalog label 與其 public/private network 行為。

產品母項的 GA 標籤不能替子路徑升級;同樣地,某個 hosted integration 是 Preview,也不代表所有 Foundry RBAC 或 Private Link 都是 Preview。

Test:本機 route 綠了,雲端封包仍是 pending

畫面判讀目標: 辨認只允許 bare HTTPS origin 的 endpoint contract。

五筆 endpoint matrix 顯示四筆 unsafe shape 的兩個 failure codes,bare HTTPS origin 無 failure 且 allow。

只接受 registry 派生的 bare origin,redirect 每一跳仍需重驗。 可觀察狀態:前四筆皆同時回 EGRESS_HOST_DENIED 與 EGRESS_ENDPOINT_DENIED,bare_https_origin 的 failure_codes 為空且 allowed=true。 Claim boundary:URL validation 不防 DNS rebinding、redirect 或實際 egress routing。

進入 day20/ 後執行:

uv run pytest tests/stages/day20/test_acceptance.py -q

V2 acceptance 必須確認:

  • compliant private route 的 validate() 回傳空 list。
  • public route 同時指出 host、identity、PNA 與 DNS 問題。
  • 公開 endpoint matrix 固定只有 plain_httpcredential_in_originnon_default_portpath_suffixbare_https_origin 五筆;前四筆同時回 EGRESS_HOST_DENIEDEGRESS_ENDPOINT_DENIED,最後一筆 failure list 為空且 allow。
  • 較廣的 parser unit regression 另測 query、fragment、空 suffix 與 malformed input,但這些不在公開五筆 matrix 裡。
  • egress_allowed=False 單獨得到 EGRESS_POLICY_DENIED
  • registry 對 wrong plane/action/scope/auth 各回固定 reason;Search/Vault 雙 allowlist
    的 multi-host swap 仍得到 DEPENDENCY_ORIGIN_MISMATCH
  • before public route 產生 receipt;after 同一 route receipt 為 0。
  • after compliant route 仍 allow,證明不是全面封鎖。
  • fixture 只使用 .invalidnetwork_io_performed=false

本次 Day 20 acceptance 為 5 passedcumulative_stage_replay() 的 outcomes 合計
兩筆 Receipt:before public route 一筆、after compliant route 一筆;全部
side_effect_count=0external_callscloud_calls 都是 null,因為這個
preflight 沒有 network observer,不能用「程式裡沒寫 socket」冒充量測到零;blocker
REAL_NETWORK_PATH_NOT_EXERCISED

public-route 與 registry 兩張 UI 各自 fresh-run 一個 deterministic helper,不共用或拼接 capture:

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

修補/test UI 是 endpoint-shape matrix:HTTP、userinfo、非 443 port 與 path suffix 都同時回
EGRESS_HOST_DENIEDEGRESS_ENDPOINT_DENIED,bare HTTPS origin 才能通過。這份 structured capture 本來就沒有 tool
call,所以 Receipt 與 side effect 都是 0;external/cloud call 仍是未量測的 null

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

這三個 helper command 各自只重現 disclosure-safe JSON;正式發布時,本篇 required app UI 圖
必須由目前相關原始碼經 repository UI capture pipeline 產生,schema 3 manifest 必須以 source-tree SHA-256
與檔案數綁定輸入並交由 strict verifier 重算。正式圖仍只支持 deterministic preflight;沒有 socket/DNS
observer、Private Endpoint receipt 或其他 Foundry cloud evidence。

雲端 smoke matrix:PENDING-CLOUD

本篇尚未在 V2 重建 VNet、Private Endpoint、Private DNS、managed VNet、Firewall 或 Standard Agent setup。以下案例都仍是 PENDING-CLOUD

inside VNet:服務 hostname → private IP;443 可達;正確 identity 成功
outside VNet:public endpoint 被拒絕
remove PE/DNS link:dependency fail closed,不走 public fallback
unapproved FQDN/non-bare endpoint:留下 egress deny evidence
wrong identity:網路可達,但服務端回 401/403
Search/KV/Storage:每個 dependency 分別完成同一組測試

本機 NetworkBoundaryPolicy.validate() 只證明 deterministic preflight logic,不能替代 DNS query、socket、Private Endpoint connection、Firewall log 或 packet/flow evidence。

Residual Risk:私網縮小曝露,也會增加復原成本

Private Endpoint 不會阻止合法 identity 被濫用,也不會修正錯誤的 Search tenant filter。已核准 MCP server 仍可能被供應鏈攻擊;redirect 也可能把第一次通過 allowlist 的 request 帶往另一個 origin。正式 adapter 應對 redirect fail closed 或重新授權每一跳,並由 trusted tool registry 派生 dependency/operation。

allow-only-approved outbound 會增加 Firewall、Private Link、DNS 與離線供應鏈的成本。完全關閉 Internet 也可能讓套件下載、映像更新或 incident-response 工具失效;要事前準備 private registry、更新流程與 break-glass,而不是事故時臨時把 PNA 打開。

NIST SP 800-207 不把網路位置當成信任。private IP 只能證明一部分路徑;正確/錯誤 identity、目標 resource、operation 與 401/403 仍要一起測。

明天會在這些確定性邊界前面加入 Prompt Shields。它可以提供 attack signal,卻不會替 identity、RBAC、tenant filter 或 egress policy 做授權決定。

第一方參考資料


上一篇
Day 19|Microsoft Foundry 的 AI Agent 攻防實戰:Secret 搬進 Key Vault,舊影印本為什麼還能用?
下一篇
Day 21|Microsoft Foundry 的 AI Agent 攻防實戰:Prompt Shields 沒吹哨,不代表比賽自動判安全
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言