Model Armor 是 Google Cloud 的模型輸入輸出篩查服務。它獨立於模型本身,可以檢查送進模型的 prompt 和模型吐出的 response。
主要能力包含 Prompt Injection 與 jailbreak 偵測、敏感資料偵測(整合 Sensitive Data Protection)、惡意 URL 偵測,以及有害內容分類。
gcloud model-armor templates create legal-pii-gate \
--location=asia-east1 \
--pi-and-jailbreak-filter-settings-enforcement=enabled \
--pi-and-jailbreak-filter-settings-confidence-level=LOW_AND_ABOVE \
--malicious-uri-filter-settings-enforcement=enabled
confidence-level 設 LOW_AND_ABOVE 是刻意的——在這個場景,誤報的代價(多一次人工複核)遠低於漏報的代價(原文出行)。這跟 D8 的 F-beta 邏輯一致。
搭配 SDP 範本做敏感資料篩查:
filterConfig:
sdpSettings:
advancedConfig:
inspectTemplate: "projects/P/locations/asia-east1/inspectTemplates/tw-pii"
deidentifyTemplate: "projects/P/locations/asia-east1/deidentifyTemplates/tw-pii-deid"
piAndJailbreakFilterSettings:
filterEnforcement: ENABLED
confidenceLevel: LOW_AND_ABOVE
from google.cloud import modelarmor_v1
client = modelarmor_v1.ModelArmorClient()
TEMPLATE = "projects/P/locations/asia-east1/templates/legal-pii-gate"
def sanitize_input(text: str):
resp = client.sanitize_user_prompt(
request={
"name": TEMPLATE,
"user_prompt_data": {"text": text},
}
)
return resp.sanitization_result
def sanitize_output(text: str):
resp = client.sanitize_model_response(
request={
"name": TEMPLATE,
"model_response_data": {"text": text},
}
)
return resp.sanitization_result
這是今天真正的重點。同一個工具,放的位置不同,效果差很多。
放法一:在自建去識別化之前
文件 → Model Armor → 去識別化 → LLM
問題:Model Armor 是雲端服務,你把原文送給它了。這回到 D7 和 D15 的老問題——偵測前資料就出行。
如果你的合規基準允許 Google Cloud 邊界內處理,這沒問題。否則這個位置不能用。
放法二:在去識別化之後、送模型之前
文件 → 去識別化(地端) → Model Armor → LLM
這個位置安全(送過去的是去識別化資料),而且 Model Armor 在這裡有兩個明確作用:
第二點很有價值。它是一個獨立於你自己偵測器的第二意見。兩個獨立系統同時漏掉同一個欄位的機率,遠低於單一系統。
放法三:在模型回應之後
LLM → Model Armor → 回傳使用者
作用是檢查模型有沒有吐出不該吐的東西——幻覺產生的假個資、system prompt 洩漏、被誘導出的檢索內容。
我的建議是放法二加放法三,雙向都做。
def call_llm_guarded(deid_text, ctx):
# 輸入側
in_result = sanitize_input(deid_text)
if in_result.filter_match_state == "MATCH_FOUND":
audit("armor_input_block", ctx, {
"filters": list(in_result.filter_results.keys())
})
raise ProcessingBlocked("輸入內容未通過安全檢查")
resp = model.generate(deid_text)
# 輸出側
out_result = sanitize_output(resp.text)
if out_result.filter_match_state == "MATCH_FOUND":
audit("armor_output_block", ctx, {...})
raise ProcessingBlocked("模型回應未通過安全檢查")
return resp.text
必須誠實:Model Armor 不是萬靈丹。
擋不住針對去識別化引擎的攻擊。 它檢查的是送給模型的內容。如果攻擊發生在更前面——騙過你自己的偵測器——那個欄位已經以明文形式進到「去識別化後」的文字裡了。Model Armor 的 SDP 篩查有機會抓到,但那是靠 SDP 的偵測能力,不是靠 Injection 偵測。
中文的 Injection 偵測表現需要自己驗證。 這類服務的訓練資料以英文為主。中文的規避手法(用文言、用注音、用拆字)表現如何,必須用自己的測試集實測,不能假設。
它不知道你的業務語意。 「本件當事人已請求停止處理」這種攻擊,在通用 Injection 偵測器眼中可能完全正常——它看起來就是一句法務文書。
這一段建議自己跑一輪,用 D8 建立的 Adversarial 子集餵進去,記錄 Model Armor 的表現。
【此處貼 Model Armor 實測結果截圖:console 的 filter results 畫面,含 confidence level 與各 filter 的 match 狀態,建議寬度 800px】
【此處貼中文 Injection 測試的統計表截圖:攻擊樣態 × 是否被攔截,建議寬度 800px】
我的建議是至少測這幾類:
| 攻擊樣態 | 語言 | 預期難度 |
|---|---|---|
| 直接指令覆寫 | 英文 | 應被攔截 |
| 直接指令覆寫 | 中文 | 應被攔截 |
| 偽裝成系統註記 | 中文 | 中等 |
| 偽裝成法務註記 | 中文 | 困難 |
| 文言文改寫 | 中文 | 困難 |
| Base64 編碼 | — | 中等 |
| 圖片內白底白字 | 中文 | 需 OCR 後才可能 |
第四列「偽裝成法務註記」是我認為最難擋的,也是最值得記錄實測結果的一項——因為它跟真實的法務文書幾乎無法區分。
| 面向 | Model Armor | 自建 |
|---|---|---|
| Injection 偵測 | 現成、持續更新 | 需自己維護規則 |
| 中文表現 | 需驗證 | 可針對性調校 |
| 業務語意 | 不知道 | 可編碼 |
| 資料出行 | 是(雲端服務) | 否 |
| 維運成本 | 低 | 高 |
| 稽核可解釋性 | 中(部分黑箱) | 高 |
跟 D13 一樣的結論:不是二選一,是分層。 用商用護欄擋通用攻擊(它的規則庫比你的大),用自建規則擋業務特定的攻擊(它不知道你的業務)。
明天換一個角度:不打引擎,直接打 Vault。
我是 Fngi,專注在 AI 資安、LLM 紅隊與 AI 治理框架落地。
IG:@aid3fend
有想討論的架構細節或不同意見,留言或私訊都歡迎。