iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Security

《30 天為金融法務部門打造 LLM 個資防護閘:去識別化、可控還原與紅隊驗證》系列 第 23 篇

Day 23|Model Armor:商用護欄擺在哪一層才有用

  • 分享至 

  • xImage
  •  

它是什麼

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 在這裡有兩個明確作用:

  1. Injection 偵測——文件裡藏的指令,在這一層被攔下
  2. 殘留個資的最後一道篩查——你的偵測漏掉的,這裡可能抓到

第二點很有價值。它是一個獨立於你自己偵測器的第二意見。兩個獨立系統同時漏掉同一個欄位的機率,遠低於單一系統。

放法三:在模型回應之後

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 後才可能

第四列「偽裝成法務註記」是我認為最難擋的,也是最值得記錄實測結果的一項——因為它跟真實的法務文書幾乎無法區分。

商用護欄 vs 自建護欄

面向 Model Armor 自建
Injection 偵測 現成、持續更新 需自己維護規則
中文表現 需驗證 可針對性調校
業務語意 不知道 可編碼
資料出行 是(雲端服務) 否
維運成本 低 高
稽核可解釋性 中(部分黑箱) 高

跟 D13 一樣的結論:不是二選一,是分層。 用商用護欄擋通用攻擊(它的規則庫比你的大),用自建規則擋業務特定的攻擊(它不知道你的業務)。


明天換一個角度:不打引擎,直接打 Vault。


關於作者

我是 Fngi,專注在 AI 資安、LLM 紅隊與 AI 治理框架落地。
IG:@aid3fend

有想討論的架構細節或不同意見,留言或私訊都歡迎。



上一篇
Day 22|Prompt Injection 的真正目標,是去識別化引擎
下一篇
Day 24|攻擊 Vault:不用偷資料庫也能還原
系列文
《30 天為金融法務部門打造 LLM 個資防護閘:去識別化、可控還原與紅隊驗證》 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言