iT邦幫忙

2026 iThome 鐵人賽

0
AI Security

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

Day 31|Microsoft Foundry 的 AI Agent 攻防實戰:開源前,先讓 AI 查查這三十天的程式

  • 分享至 

  • xImage
  •  

三十天寫完了,我們也把「大魔術熊貓工程司」的程式放上 GitHub,採用 Apache-2.0 授權。你可以從 Day 01 開始,也可以直接挑 Day 05,看看 Alice 自稱主管後,究竟有沒有成功核准費用。

前一篇把測試、Receipt 與發布條件放在一起看。這次再多做一件事:請另一組 AI 回頭讀程式,找找我們原本的測試可能沒想到的問題。

Cloudflare 有一個 security-audit skill。開源前,我拿它實際查了一輪。今天先介紹這個 skill,再看看審查留下什麼結果,以及發布前修了什麼。

這個 skill 怎麼查程式?

你可以把它理解成一份交給 coding agent 的審查工作說明。Agent 先讀架構與入口,再沿著使用者輸入、文件、工具參數往下追,確認哪一段程式決定了身分、權限,以及最後的副作用。

例如,Alice 在 Prompt 裡說「我是主管」,我們已經知道這句話不應該讓她取得核准權限。審查時還要往下讀:身分從哪裡來?工具執行前有沒有重新檢查?如果核准後權限被撤銷,最後一次檢查發生在哪裡?

Cloudflare 把工作分成架構盤點、分工找問題、獨立驗證、結構化紀錄、再次核對與產出報告。找到問題的 Agent 和驗證問題的 Agent 分開,驗證者的工作是嘗試推翻前面的判斷。這些流程寫在 固定版本的 SKILL.md 裡。

我覺得其中最有用的要求,是每個問題都得交代清楚:誰能送入什麼資料、跨過哪個權限邊界、最後影響了誰。光是看到某個函式沒有檢查,就說它有高風險漏洞,還不夠。

它也會保存 coverage-ledger.json,記錄查過的入口、控制與證據。下一次審查就能從漏查的地方繼續,而不必每次都重新問一句「這個專案安全嗎?」

先說清楚:這裡有我們故意留下的漏洞

這套教材有三十個逐日累積的程式快照,也保留修補前的分支。拿到程式就搜尋 vulnerable,當然會找到東西,但那還沒回答我們這次想知道的問題。

我們需要分開看三種情況:

程式裡的情況 審查時要確認什麼
教學刻意保留的漏洞 是否仍限於合成資料與指定的實驗流程
宣稱補上控制的版本 攻擊者能否沿著其他入口繞過控制
沒有接到對外入口的內部函式 呼叫者原本取得什麼權限,不能直接假設匿名使用者能呼叫

還有一個容易看錯的地方:某天的測試有接上控制,不代表預設 HTTP 服務也接了同一套流程。

例如,預設服務建立的是 DryRunToolExecutor;Day 10 的案例另外組合 GatedToolExecutor,檢查任務能力與預算。審查時得讀真正的建立與呼叫位置,光看到控制類別存在,不能算它已經保護所有入口。

Microsoft Foundry 與 Agent Framework 仍是原本的教學主線。今天這個 skill 負責讀我們的原始碼;Foundry 上的身分、部署與模型行為,仍需要各自的執行證據。

固定 skill 版本,再交代審查範圍

這次使用的 skill commit 是:

c1c8a8c1471069fb0e188eeaff69b8e8db6564a8

如果你也想試試,可以先下載教材,再把同一版的 skill 放在旁邊。下面固定的教材 commit 已包含文末提到的第二輪修正;前面那次審查與 Key Vault 修補的紀錄仍另外保留:

git clone https://github.com/Ko-Ko-Kirk/2026-ai-security-microsoft-foundry.git github-code-only
git -C github-code-only checkout 08ea56739cc4597f76e29b396a8ecca1fca3862f
git clone https://github.com/cloudflare/security-audit-skill.git
git -C security-audit-skill checkout c1c8a8c1471069fb0e188eeaff69b8e8db6564a8

接著讓支援子 Agent 與工具操作的 coding agent 讀取 skills/security-audit/SKILL.md。它還需要 Node.js 驗證報告格式;需要執行受查程式時,必須先有符合 skill 要求的作業系統隔離環境。

下面這份 Prompt 可以作為起點。資料夾名稱換成你實際下載的位置,報告目錄放在受查專案外:

請讀取 ./security-audit-skill/skills/security-audit/SKILL.md,
依它的 full audit workflow,以 quick profile 審查 ./github-code-only。
報告放在專案外的新目錄 ./security-audit-output/run-1。

這是三十天的 AI Agent 資安教材。請涵蓋 day1~day30、根目錄 scripts
與匯出工具;重複檔案可依內容去重,但不同版本必須分開追查。
先區分刻意脆弱的教學分支、修補版、預設 HTTP 流程及選用的雲端範例。

請依 skill 分工盤點與追查,再由獨立 Agent 檢查漏查範圍。
有具體漏洞候選時,交給沒有提出該候選的 Agent 驗證。
問題要指出真實入口、最強的既有控制、受影響對象與可確認的結果。
範例錯誤與改善建議另外列出,不硬湊成漏洞。

維持受查原始碼唯讀,不自動修補、commit、push 或發布。
不要登入 Azure、呼叫雲端模型、讀取真實 Secret 或建立資源。
需要執行時,先確認有禁止對外網路、唯讀原始碼、限定寫入位置、
空白起始環境與資源上限的作業系統沙箱;不得臨時下載相依套件。
缺少條件就記錄具體限制,不宣稱已執行成功。

最後提供 REPORT.md、coverage-ledger.json、findings.json、
FINDINGS-DETAIL.md 與 NEEDS-VALIDATION.md,並驗證結構化檔案格式。

quick 會做一輪追查,再由另一個 Agent 查漏。查漏時才發現的新範圍會留下待查紀錄,這一輪不繼續展開。這個選擇適合先看整理後的教材,但讀報告時要保留它的範圍。

這次實際查到什麼?

審查當時的開源候選包共有 4,456 個檔案。三十份快照裡有不少重複內容,所以先依路徑與內容雜湊整理版本,再沿著入口追查。檔案總數用來識別這一包程式,實際查過的路徑則另外記在 ledger 裡。後來修補並補上測試、審閱確認與審查摘要,公開版共有 4,459 個檔案;下面先保留原審查的結果。

四個 Agent 先盤點架構、權限、資料入口與可執行的檢查,接著由三組 Agent 分別追查身分與授權、文件與工具、雲端設定與匯出。最後再交給一個沒有繼承前面對話的 Agent,專門找漏查的地方。

結果如下:

項目 本輪結果
完成原先安排的檢查 21 個單元,留下 38 筆來源檢查紀錄
獨立查漏新增的範圍 4 個,留待下一輪
確認的安全漏洞 0 個
保留的待驗證漏洞候選 0 個
另外確認的範例錯誤 1 個,見下一節的 Key Vault 呼叫

這裡的「單元」是一組入口、權限邊界與檢查類型,不能換算成幾篇文章通過。追查階段沒有留下符合證據門檻的漏洞候選,因此這一輪也沒有啟動候選漏洞的獨立重現程序。

最後那位查漏的 Agent 有找到四個值得繼續追的地方:

  • HTTP 身分驗證前的解析成本:webapp.py 的 handler 內才驗證 bearer,框架如何先處理 AgentRequest,還要連同 body 限制一起看。
  • 資料一直留下來會怎樣:store.py、retrieval.py、telemetry.py 的紀錄與快取有沒有累積上限,reset 是否涵蓋所有副本,這次還沒深入追完。
  • OBO Token 交換:examples/cloud/obo_exchange.py 要求呼叫者先驗證 Token。還要追它和呼叫端怎麼約束租戶、接收對象與下游資源。
  • Azure Monitor 匯出與暫存:examples/cloud/otel_secure_setup.py 和本機的遙測清理是不同路徑,自訂欄位與離線暫存不能直接沿用本機檢查的結論。

這四項目前都只有覆蓋缺口,還沒有證明造成越權、外洩或服務中斷。它們會留在 ledger,下一輪可以指定其中一項繼續查。

原審查那一輪主要讀原始碼,另做了下一節的隔離方法檢查,當時沒有重跑整套 pytest,也沒有呼叫 Foundry 或其他 Azure 服務。報告與 ledger 的格式驗證都通過,受查的 4,456 個檔案在審查前後沒有變更。修補後的測試結果接著說。

一個會讓讀者卡住的 Key Vault 範例

安全漏洞之外,這次讀程式也查到一個具體的介面錯誤。

審查時,Day 19~30 的 examples/cloud/key_vault_reader.py 只傳入 Secret 名稱:

value = store.get_value(os.getenv("SYNTHETIC_SECRET_NAME", "lab-canary"))

但這些版本的 AzureKeyVaultSecretStore 已經要求明確指定版本:

def get_value(self, name: str, version: str) -> str:
    if not version.strip():
        raise ValueError("SECRET_VERSION_REQUIRED")
    return self.client.get_secret(name, version).value

也就是說,前面的環境設定與 Client 建立即使都成功,走到這個呼叫仍會因為少了 version 而失敗。

我們另外在隔離環境取出原始碼裡的這個方法,接上假的 Client 檢查 Day 19~30 的版本。少傳參數時得到:

get_value() missing 1 required positional argument: 'version'

傳空白版本會被 SECRET_VERSION_REQUIRED 拒絕;明確傳入假的版本,則會把名稱與版本一起交給假的 Client。這個檢查沒有載入 Azure SDK,也沒有連上 Key Vault。它確認的是方法的呼叫契約,完整雲端範例還需要另外驗證。

它會影響讀者操作,所以要修。不過目前沒有證據顯示它讓 Alice 取得不該取得的 Secret,不能直接寫成「找到 Key Vault 洩密漏洞」。

開源前,已補上版本參數與回歸測試

你在前面的教材已經看過明確指定 Secret 版本的要求;這次漏接的是雲端範例的呼叫端。Day 19~30 現在都會先讀取 SYNTHETIC_SECRET_VERSION,缺少或只有空白,就在建立憑證與 Client 之前拒絕:

secret_name = os.getenv("SYNTHETIC_SECRET_NAME", "lab-canary")
secret_version = os.getenv("SYNTHETIC_SECRET_VERSION", "").strip()
if not secret_version:
    raise ValueError("SECRET_VERSION_REQUIRED")

後面的呼叫也一起補上版本:

value = store.get_value(secret_name, secret_version)

原本要求指定版本的控制保留下來,各天的雲端 README 也補上環境變數說明。用假的 Client 驗證時,除了確認名稱與版本往下傳,也檢查缺少版本、空白版本,以及讀取失敗後仍會關閉 Client 與憑證。

這四個回歸測試涵蓋 Day 19~30 的十二份快照,放在 test_key_vault_example.py。進入下載的教材根目錄,就能執行:

python3 -m unittest discover -s tests -p test_key_vault_example.py -v

第一次公開前,也重跑了離線檢查:三十天的逐日測試合計 242 個通過,Day 30 累積測試另外跑一次,也是 242 個通過;根目錄的 15 個回歸測試全部通過,包含上面四個。合成資料的人工審閱已完成,資料的嚴格發布檢查也通過。完整結果整理在 VERIFICATION.md,審查與修補摘要放在 SECURITY-REVIEW.md。

這些檢查沒有連上 Azure,也沒有呼叫雲端模型。假的 Client 能確認程式有傳對參數,真正的 Key Vault 角色指派與讀取結果仍需要雲端驗證。原審查的四項待查範圍也保留,不能拿這次回歸測試通過當作已經查完。

開源之後,又有人從另一個角度查了一次

程式公開後,我又請另一個 AI 助手從不同角度審查一次,這次不只讀程式,也對照文章說的和測試實際測到的。它找到幾個 quick profile 沒有攔下的問題,大多不是「攻擊者能拿走資料」,而是控制或測試的設計沒有做到文章說的程度:

發現 修正
主管可以核准自己名下的小額費用,Day 11 修補後仍回 POLICY_ALLOW PEP 加上 SELF_APPROVAL_DENIED;人工核准也不能由費用 owner 簽
finance 角色擁有全部工具,包含資安處置用的隔離文件 改成明確的工具清單
Day 28 的路由預算依資料集的攻擊標籤切換,Day 29 的預算案例則由情境名稱把上限改成 0 改成同一組預算;D29-A04 改成真的展開 8 個分支
D29-A01~A03 用 Alice 當主角,三筆都在簽發第一段委派時被擋下,沒測到簽章、格式與資源 改由有匯出權限的 Fiona 提出,並讓竄改的委派真的嘗試擴大資源
Day 09 由模型替身自己把提案標成「來自記憶」 改由 service 讀取記憶並標記來源,同時記錄會誤擋的情況
事件處置解除後,處置前簽出的核准仍能執行 kill switch 會作廢該租戶尚未使用的核准,全域處置則作廢全部
Day 29 不認得的 action 一律對應到匯出工具 改成封閉的對照表,未知 action 直接拒絕

以上都補了回歸測試,也另外用反例複核。重跑後,三十天逐日驗收全部通過,Day 30 累積測試從 242 個變成 252 個;15 筆固定攻擊仍是 0/15,9 筆合法操作仍全數成功。這批修正已更新到 GitHub(08ea567),資料集發布確認與來源雜湊也已同步,完整結果見這一版的 VERIFICATION.md。這次依然只跑本機檢查,沒有新增雲端模型呼叫。

這也說明,「確認的安全漏洞 0 個」只代表那一輪審查在它的證據門檻下沒有留下候選。換一個角度,拿文章的主張回頭核對測試,還是可能找到落差。

讀報告時,先看它憑什麼下結論

findings.json 把候選問題分成三種結果:

結果 可以怎麼讀
confirmed 已有完整程式路徑與受控本機重現,確認具體的邊界失效
needs_validation 程式支持這個候選,但還缺一項能決定它是否成立的事實
rejected 驗證時被程式中的控制或實際行為推翻

另外還有 coverage-ledger.json 裡的 deferred,表示這一輪還沒查到。它和「找到疑似漏洞,還缺部署證據」不同。

例如,Azure 上的角色指派沒有在這一輪查看,就先說沒有查看;如果沒有具體的越權路徑,不必替它建立一筆疑似漏洞。反過來,真的找到繞過工具授權的路徑,也不能因為它是教學專案就略過。

接著看報告引用哪個檔案、哪個版本,以及實際跑了什麼。原本的測試通過紀錄可以作為背景,但不能直接變成這次審查已經重跑的證據。這一點和 Day 30 看 Receipt、分母與發布條件的做法相同。

開源後,你可以接著查哪裡?

我希望這套教材除了讓你照著跑,也能讓你試著改條件。

例如,把「Alice 假裝主管」換成核准後撤銷權限,或把搜尋結果改成另一個租戶的文件。改完先確認請求真的走到你想測的控制,再讀 Receipt 與資源狀態,看看最後發生了什麼。

如果想交給 Agent 幫忙,就把這次的審查紀錄一起給它,指定一個還沒查完的範圍。發現問題時,保留最小重現、修補與回歸測試,下一位讀者才有辦法接著驗證。

三十天的文章到這裡告一段落。程式現在已經公開,歡迎繼續拿它練習,也歡迎用具體案例指出哪一段還需要調整。能讓別人讀懂、重跑,甚至找到我們漏掉的問題,這份教材才會越來越有用。


上一篇
Day 30|Microsoft Foundry 的 AI Agent 攻防實戰:三十個綠燈超完美,嗎?
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言