iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
AI Security

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

Day 13|Microsoft Foundry 的 AI Agent 攻防實戰:Agent 供應鏈 Release Gate

  • 分享至 

  • xImage
  •  

昨天固定了 MCP manifest。今天把同樣的問題擴大到整個 Agent release:除了工具設定,prompt、package、container、model descriptor 與 IaC 也會影響行為。

工程司有兩份 system instructions,第二份多了「保留每個 proposal 的 source」。測試故意讓兩份都叫 1.0.0,檢查 gate 是否只看版本號,還是會重新讀取檔案與審查紀錄。

今天先用合成檔案建立發布檢查。Prompt 不會拿去執行,也不部署 Azure 資源;我們先確認內容或審查紀錄對不上時,程式能不能找出原因。

一次 Agent release 到底包含什麼?

Release manifest、package 與 container

先列出發布清單中的 package 與 container,確認套件資料有對回 lockfile。

release manifest、Python package、container artifacts 與 package-lock binding。

manifest 列出必要類別與固定的 digest,旁邊可以核對 package、container 的描述,以及 package_lock_bound=true。這份清單只涵蓋 manifest 已列出的類別,還不能說整條供應鏈都已盤點完成。

Prompt 與 model descriptors

接著把 prompt 與 model deployment 分開,核對各自的內容指紋與來源。

prompt 與 model deployment 的 content digest、descriptor 與 provenance。

畫面列出 prompt 與 model 各自的 digest、version、source path 與 license。兩者都會影響 Agent,不能只用一個籠統的版本號一起帶過;release scope 會在後面的 review descriptor 中核對。

MCP 與 IaC descriptors

再將 MCP manifest 與 Foundry IaC 納入同一份檢查。

vendor MCP manifest 與 Foundry IaC 的 descriptor binding。

畫面列出 MCP 與 IaC 各自的 kind、digest、source 與 license,讓我們先認出這次準備發布的整合與平台設定。Behavior 與 release scope 則在後面的 review descriptor 中核對。

Package 與 container review records

清單列完後,先找 package 與 container 對應的審查紀錄。

package/container 的 reviewed descriptor 與 reviewed digest。

畫面中的 review_records 0–1 列出審查綁定的 descriptor 與 digest,沒有顯示 reviewer 或審查時間。這裡先確認審查是否對到同一份產物的內容與描述。

Prompt 與 model review records

再看 prompt 與 model,確認它們各自對應哪一份審查。

prompt/model 的 reviewed descriptor 與 digest。

畫面中的 review_records 2–3 分別綁定 prompt 與 model 的描述欄位與 digest。日後更換模型或提示詞,就能回頭對照審查所綁定的版本;這張圖沒有列出 reviewer。

MCP 與 IaC review records

最後核對 MCP 與 IaC 的審查,並確認這些紀錄的來源。

MCP/IaC 的 reviewed descriptor、digest 與 synthetic review boundary。

review_records 4–5 綁定 MCP 與 IaC 的 descriptor、digest。這些仍是合成的審查資料,只用來測試本機流程,不能當成可信的公開發布核准。

本系列先固定六類輸入:

Kind 大魔術熊貓工程司的 fixture Gate 要回答的問題
package resolved dependency slice exact version、dependency edge 與 hash 是否可重建
container 合成 Containerfile 必要文字 marker 是否存在,檔案 digest 是否一致
prompt Agent instructions 送入 Agent version 的文字是否就是審查版
model deployment metadata provider、deployment alias、model ID/version/region 是否改變
mcp Day 12 manifest endpoint、description、schema、allowlist、approval 是否一致
iac 合成 Bicep 必要文字 marker 是否存在,檔案 digest 是否一致

這六類是本系列先列出的 release inventory。實際專案還要依部署內容加入資料集、evaluator、policy bundle、feature flag、frontend 或資料庫 migration;有影響行為的輸入,就要能追到相應版本。

package-lock.json 是從整份 uv.lock 節錄出的十個 package fixture,不是另一份可以自由發明版本的清單。Builder 會把當日 snapshot 的 uv.lock SHA-256 寫入 source_snapshot_sha256;驗收再用 tomllib 逐項核對十筆 name/version、dependency names 與 fixture hashes 都存在於同一份 lock。只把 lock digest 抄進 JSON、但裡面的 package rows 對不上,仍會得到 PACKAGE_INVENTORY_LOCK_CONTENT_MISMATCH。

哪些改動會影響一次 Agent Release?

傳統軟體供應鏈已經會遇到 movable container tag、transitive dependency 重新解析、CI artifact 被替換與 provenance 不完整。Agent 再多了幾個會直接改變行為的輸入:

  • prompt 檔案可以在 release 前被替換;
  • model deployment alias 可以指向另一個 model version;
  • MCP description 與 schema 可以漂移;
  • evaluation dataset 可以少掉最難的 attack cases;
  • runtime connection 或 IaC 可以把原本的最小權限換成高權限。

BadAgent 是 ACL 2024 論文,研究如何透過 fine-tuning data 在 LLM Agent 植入並觸發後門。它提醒我們:內容相同與行為安全是兩個不同問題。不過 Day 13 的程式沒有實作模型後門掃描或 clean/trigger behavior regression;今天只回答兩個較窄、可由 repository fixture 驗證的問題:準備發布的檔案 digest 是否符合 pin,以及 review 是否綁定同一份 typed descriptor。這個 descriptor 會記錄預期安全語意,但不會實測模型是否真的照著宣告行為。

同一個版本號,換了檔案或審查描述

讓版本號維持不變,看看換了內容後,舊的審查還能不能被沿用。

Artifact comparison 顯示同為 1.0.0 但 instructions digest 與 review 不一致。

version 相同,content digest 與 review descriptor 卻對不上,因此發布被拒絕。digest 在這裡只指出內容與審查版不同,沒有判斷新內容是否安全。

D13-A02 保留 prompt.version="1.0.0",內容卻換成第二份 system-instructions fixture。實際差異是:

v1:
  Return tool proposals only.

v2 bytes, deliberately relabeled as 1.0.0:
  Return tool proposals only and preserve the source of every proposal.

若 gate 只比對 version,candidate 會通過。V2 會從 fixture bytes 重算 content digest,預期結果是:

old_version=1.0.0
candidate_version=1.0.0
content_changed=true
ARTIFACT_DIGEST_DRIFT
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:content_digest
ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path
ARTIFACT_REVIEW_DIGEST_MISMATCH

這筆 fixture 測的是相同版本號下的內容漂移。Gate 沒有執行 prompt,也沒有判斷新增句子是否惡意;它只確認準備發布的 bytes 已經與核准版本不同。

Review fixture schema 現在是 2.0。每筆 ArtifactReview 不再只保存一個 reviewed_digest,而是保存完整的 reviewed_descriptor 與 reviewed_descriptor_sha256。Descriptor 的 canonical JSON 明確包含:

  • descriptor_schema、artifact_name、kind 與 version;
  • content_digest、license_id、source_path 與 release_scope;
  • synthetic provenance;
  • 結構化 behavior:contract_id、capability 與 side_effect_boundary。

Prompt 的 descriptor 宣告 capability 為 propose-expense-actions,副作用範圍為 proposal-only-no-tool-execution。Reviewer 要確認這些預期契約;程式並沒有從模型實際輸出推導出它們。

D13-A04 專門固定 prompt bytes 與 content digest,只替換 descriptor metadata。六個 adversarial cases 都必須 fail closed:

被替換的欄位 同一 content digest 下的 reason code
kind ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:kind
version ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:version
license_id ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:license_id
source_path ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path
release_scope ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:release_scope
behavior ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:behavior

測試還會把被替換的 review descriptor 重新雜湊,證明「hash 格式正確」仍不能讓它替另一份候選 descriptor 背書;若只改 descriptor 卻不重算 hash,則另得到 ARTIFACT_REVIEW_DESCRIPTOR_DIGEST_MISMATCH。

從檔案重算 Digest,再核對 Review

Gate 會保留每一項失敗原因,方便對回哪份檔案或審查出了問題:

per-kind content contract
  -> artifact digest pin
  -> typed descriptor hash + descriptor-bound review
  -> license allowlist
  -> optional signature verifier
  -> required artifact kinds

核心比對可以寫成:

import hashlib
import json


def descriptor_sha256(descriptor):
    canonical = json.dumps(
        descriptor.as_dict(),
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode("utf-8")
    return f"sha256:{hashlib.sha256(canonical).hexdigest()}"


def integrity_and_review_failures(artifact, pinned_digests, reviews):
    failures = []
    expected = pinned_digests.get(artifact.name)
    if expected is None:
        failures.append("ARTIFACT_NOT_PINNED")
    elif artifact.digest != expected:
        failures.append("ARTIFACT_DIGEST_DRIFT")

    review = reviews.get(artifact.name)
    if review is None:
        failures.append("ARTIFACT_REVIEW_REQUIRED")
        return failures

    descriptor = review.reviewed_descriptor
    if review.reviewed_descriptor_sha256 != descriptor_sha256(descriptor):
        failures.append("ARTIFACT_REVIEW_DESCRIPTOR_DIGEST_MISMATCH")

    candidate = artifact.review_descriptor()
    for field in (
        "kind",
        "version",
        "content_digest",
        "license_id",
        "source_path",
        "release_scope",
        "behavior",
    ):
        if getattr(descriptor, field) != getattr(candidate, field):
            failures.append(f"ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:{field}")

    if review.decision != "approved":
        failures.append("ARTIFACT_REVIEW_NOT_APPROVED")
    return failures

完整比對位於 ArtifactGate._integrity_and_review_failures(),除了範例中的主要分支,還檢查 descriptor schema、artifact name、synthetic provenance、digest 格式、reviewer 與 change reference。artifact_from_file() 會讀取 fixture bytes 重算 digest,不採信 manifest 自填的值。

reviewed_descriptor_sha256 可以找出描述被修改的情況,但它仍是 repository 內部的一致性檢查。這裡沒有外部 key 替 reviewer 或決策簽章,也沒有 transparency log。因此,若有人能同時修改程式、資料與 review,光靠這份 hash 還無法確認是誰核准的。

signature_status 在 fixture 裡一律是 not_evaluated。嚴格模式只有在注入真正的 signature_verifier 後才可能通過;本機沒有 verifier,所以會回 ARTIFACT_SIGNATURE_VERIFIER_UNAVAILABLE。這不是簽章驗證示範,更不是 attestation。

分開看完整性通過與公開發布條件

Public release failures 與 blockers

接著看六類產物的失敗原因,以及公開發布仍缺少哪些條件。

六類 artifact 的 strict failures、blocker codes 與 ready=false。

畫面列出 public_release_failures、兩項 blockers 與 public_release_ready=false。Baseline public gate 沒有要求簽章;strict 檢查找不到 verifier,也不能解讀成簽章通過。合成的 review decision 同樣不能當成可信的人工核准。

Signature 與 synthetic-only 邊界

再分開看簽章是否執行,以及只有合成資料時,發布檢查會不會放行。

signature performed=false 與 synthetic-only readiness blockers。

本圖用合成 review 檢查簽章與發布條件:signature 未執行,strict 模式列出失敗原因,synthetic_only_public_readiness.ready=false。Baseline public gate 沒有要求 signature;strict 模式找不到 verifier,也不能算簽章通過。這裡只測這兩組條件,沒有可信的人工發布核准。

Lab 裡的 review、license decision 與 provenance 都是合成資料,只用來測試 gate。即使暫時清空 license failure,synthetic artifact 與 synthetic review 仍會各自阻止公開發布,所以預期結果是:

artifact_count=6
all_content_digests_match=true
synthetic_reviews=true
package_lock_bound=true
license_id=NOASSERTION
signature_check_performed=false
public_release_blockers=[SYNTHETIC_ARTIFACT_EVIDENCE_ONLY,SYNTHETIC_REVIEW_EVIDENCE_ONLY]
public_release_ready=false

這裡 public release 保持拒絕,是測試的預期結果。只有內容一致,還不足以取得可信任的審查、簽章或授權證據;gate 需要把缺少的條件逐項保留下來。

正常升級 D13-B01 會把 prompt version 明確改成 2.0.0,更新 content digest、source path 與完整 typed descriptor,再建立 synthetic review。它的 after_descriptor_bound_review=[],所以通過本機 integrity/review 子 gate;但 license 仍是 NOASSERTION,不能把 positive_path_passed=true 翻譯成可公開發布。

Microsoft Foundry 的 version 能替我們做到哪裡?

今天的 manifest 若寫「已部署版本 A」,還要從 Foundry 讀回 Agent version、model deployment 與實際工具設定,對照審查時綁定的 digest。Repository 裡的 JSON 即使全部一致,也只能證明提交前的內容;平台上的版本若沒讀回,release chain 中間仍是空白。

Microsoft Foundry 把 Agent 保存成具名、具版本的資產。官方 runtime 文件說明 Agent definition 會組合 model、instructions、tools、parameters 與可選的 safety/governance controls;Hosted Agent 每次建立 version 時,會產生包含 container image、resource allocation、environment variables 與 protocol configuration 的 immutable snapshot。Hosted Agent endpoint 一次只服務一個 version,官方文件目前不支援 version 間 traffic split。

這些平台版本很重要,但它們看不到 repository 裡的全部供應鏈證據。例如:

  • review 是否由可驗證的人或服務做成;
  • prompt 是否真的從聲稱的 source commit 產生;
  • MCP publisher 是否通過審查;
  • license 與 notice 是否允許目前的散布方式;
  • IaC 與實際 deployed resource 是否一致;
  • 部署後讀回的 bytes 是否仍與 manifest 相同。

Foundry project connection 處理連線與 credential 使用方式,MCP approval 處理某次工具呼叫是否等待核准。這些設定也要放進 release manifest,但它們沒有檢查 package、prompt、publisher 或 container 的來源可信度。

比較可追溯的 release chain 應該是:

source commit + resolved lock
  -> package/container/prompt/model/MCP/IaC manifest
  -> descriptor-bound review + license + signature evidence
  -> Foundry Agent or Hosted Agent version
  -> 另行建立的 benign + attack regression report
  -> rollback target

Foundry readiness 頁面將 Agents core 列為 GA;個別 model、tool、SDK、region 與 networking 條件仍需逐項核對。Hosted Agents 文件另有 region、session、registry 與版本限制,不能因為 Agents core 是 GA,就把每個部署組合一起視為 GA。

NIST SSDF 1.1 提供把安全活動整合進 SDLC 的高階架構;ISO/IEC 27001:2022 則把這些活動放回組織的資訊安全管理系統。本文只把 artifact inventory、review 與 release evidence 壓進一道本機 gate;供應商治理、組織層風險處理與 ISMS 稽核都不在這份 manifest 的證據範圍。

執行 Release Gate

同一份 Bytes,Descriptor 仍不可偷換

保留相同的 content digest,只換掉 descriptor 的六種欄位,再檢查拒絕原因。

Descriptor matrix 顯示 digest 未變,但六種 metadata tamper 都被拒絕。

content_digest_unchanged=true,六種欄位變更仍各自得到明確的 descriptor mismatch。檔案內容相同,不代表它的類別、來源或使用範圍也和審查時相同。這裡檢查的是描述是否一致,沒有判斷內容安全性。

Reviewed Upgrade 仍保留 Public Blockers

最後走一次正常升級:改為 2.0.0,重新綁定 digest 與完整描述,再看哪些檢查已通過。

Upgrade timeline 顯示重綁前 failures、descriptor-bound review 通過,以及仍未解除的 public blockers。

升版並重新綁定 descriptor 後,positive_path_passed=true;原本非空的 failures 可以對照出差異。License 與 synthetic review 的限制仍保留,signature verification 也沒有執行,所以這個子檢查通過後,public release 仍不放行。

從 repository root 執行:

cd day13
uv sync --locked
uv run pytest tests/stages/day13/test_acceptance.py -q
uv run python -m magic_panda_agent.stages.day13

鎖定環境的測試紀錄為 14 passed。驗收與畫面資料涵蓋以下案例及拒絕原因:

D13-A01  ARTIFACT_REVIEW_REQUIRED                public_ready=false
D13-A02  ARTIFACT_DIGEST_DRIFT                   public_ready=false
D13-A02  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:content_digest
D13-A02  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path
D13-A02  ARTIFACT_REVIEW_DIGEST_MISMATCH         public_ready=false
D13-A03  ARTIFACT_NOT_PINNED                      integrity_ready=false
D13-A04  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:kind
D13-A04  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:version
D13-A04  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:license_id
D13-A04  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path
D13-A04  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:release_scope
D13-A04  ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:behavior
D13-B01  after_descriptor_bound_review=[]         integrity_ready=true
baseline LICENSE_NOT_APPROVED                    public_ready=false
strict   ARTIFACT_SIGNATURE_VERIFIER_UNAVAILABLE signature_check=false

Acceptance suite 實際另外檢查:

  • 六種 required kinds 一項不少;
  • digest 都由 fixture bytes 重算,不採信 manifest 自報;
  • package inventory 有 exact version、dependency closure 與合法 hash;
  • package inventory 綁定本 snapshot 的 uv.lock digest,十筆內容也逐項對回 lock;
  • JSON artifact 的必要欄位缺漏時,在 digest gate 之前就失敗;
  • review descriptor 的 canonical hash 能重算一致,完整欄位都明確標記 synthetic;
  • 同一 bytes 下替換 kind、version、license、source、scope 或 behavior 都會被拒絕;
  • review descriptor 即使被替換後重新雜湊,仍不能替不同的候選 descriptor 背書;
  • 即使假設其他 gate 都通過,synthetic artifact/review 仍讓 public readiness 為 false。

_load_release() 程式有拒絕絕對路徑與 .. 的分支,但目前 acceptance suite 沒有獨立的惡意 path regression,因此不把它列成已驗證結果。

D13-A03 有驗收測試,但沒有收進畫面資料。第一份 JSON 包含 D13-R01/D13-A01,第二份則依序包含 D13-A02/D13-A04/D13-B01。它們保存的 observation 格式如下:

{
  "same_bytes_descriptor_tamper": {
    "artifact": "expense-agent-prompt",
    "content_digest_unchanged": true,
    "field_failures": {
      "kind": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:kind"],
      "version": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:version"],
      "license_id": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:license_id"],
      "source_path": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:source_path"],
      "release_scope": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:release_scope"],
      "behavior": ["ARTIFACT_REVIEW_DESCRIPTOR_MISMATCH:behavior"]
    }
  }
}

讀結果時,把 artifact 清單、digest 比對與 public blockers 一起看。integrity_gate_passed=true 只代表其中一項檢查通過,還不能決定是否發布。畫面來自本機案例,外部與雲端呼叫沒有觀測資料。

接著比較三種變更:同樣 1.0.0 卻換了內容、digest 不變卻改了六種 descriptor 欄位,以及升到 2.0.0 後重新綁定完整描述的正常流程。

內容一致後,還需要哪些發布證據?

Digest 證明 bytes 相同;簽章證明某個 key 簽過那些 bytes。它們都不能證明 signer 沒被入侵,也不能證明內容沒有惡意邏輯。Reviewer 可能誤判,synthetic review 更沒有外部信任價值。

Day 13 尚未做行為回歸。Descriptor 裡的 capability 與 side_effect_boundary 是 reviewer 要確認的宣告,不是 empirical model behavior。Digest/review gate 通過,只能說候選檔案、宣告的安全語意與審查紀錄對得上;prompt、model 或 MCP 的實際行為仍可能有問題。後續 release evidence 應另存 dataset version、randomness 設定、model deployment、成功/失敗分母與 Receipt oracle,不要只留一張「tests passed」截圖。

NOASSERTION 表示授權資訊不足,不能直接判定侵權,也不足以允許公開發布。在這個範例裡,LICENSE-DECISION.md 沒有完成時,gate 仍會保留拒絕。內容、描述與 review 都對得上,只回答了完整性問題;授權與外部簽章還有各自的判準。

部署完成後,還要從 registry、Foundry Agent version 與 IaC deployed state 讀回實際狀態,對照 release manifest。CI 記錄推送了 digest A,尚未確認 endpoint 正在服務 A;read-back、smoke 與 rollback drill 才能補上部署後這一段。

參考資料

供應鏈 gate 告訴我們哪一組內容準備上線。下一篇要面對另一個現實:版本完全正確,Agent 也可能在回答之外,透過 trace、error 或 outbox 把敏感資料留下來。


上一篇
Day 12|Microsoft Foundry 的 AI Agent 攻防實戰:MCP 被攻擊的手段
下一篇
Day 14|Microsoft Foundry 的 AI Agent 攻防實戰:Tracing 洩漏資料
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言