iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
AI Security

《30 天打造 AI Guardrails》系列 第 15 篇

Day 15|L1 確定性規則引擎:regex、checksum、denylist 的實作

  • 分享至 

  • xImage
  •  

Week 3 開場:從最便宜的一層開始

雲端線走完了。Week 3 在 DGX Spark 上把地端護欄從零疊起來,順序照 Day 5 的三層:今天與明天是 L1,Day 17–20 是 L2,Day 21 把它們裝進閘道。

L1 的定位再說一次:零誤差、微秒級、只抓已知模式。它的價值不在於擋多少攻擊,而在於(1)把明確的東西直接處理掉不進 GPU,(2)把明確安全的流量直接放行,(3)每一條判定都能對稽核逐字解釋。

規則引擎的結構

guard-l1/
├── engine.py          ← 載入規則、依序執行、產出判定
├── rules/
│   ├── structure.yaml ← 長度、編碼、隱藏字元
│   ├── denylist.yaml  ← 已知攻擊片段、內部代號
│   ├── allowlist.yaml ← 確定放行的模式
│   ├── url.yaml       ← 網域允許清單
│   └── pii_tw.yaml    ← 明天
└── checks/
    ├── checksum.py    ← 明天
    └── hidden.py      ← 隱藏字元與編碼偵測

規則用 YAML 而不是寫死在程式裡,因為 Day 29 的離線更新包要能只換規則檔、不換容器。

每條規則的格式:

- id: DL-0007
  category: instruction_override
  pattern: "(忽略|無視|不要理會)(上面|上述|之前|先前)(的)?(指示|指令|規範|設定)"
  action: escalate        # block | escalate | mask | allow | tag
  severity: medium
  note: "中文指令覆蓋句式,單獨命中不足以 block,送 L2"

action 五種,對應 Day 6 語料的 expected_action。注意大多數 denylist 規則的動作是 escalate 而非 block——L1 抓到「忽略上述指示」不代表一定是攻擊(可能是在寫教學文),它只是把這筆標記為需要 L2 仔細看。真正 block 的只有零歧義的東西。

四類規則

結構檢查

  • 長度上限(依應用設定;超長直接 block 或切塊)。
  • 編碼:偵測大段 Base64、十六進位、URL 編碼——注入常用來繞過關鍵字比對。
  • 隱藏字元:零寬空白(U+200B 等)、方向控制字元(U+202E)、變體選擇符。這些在正常業務文本幾乎不會出現,出現就 escalate。
  • 全形/半形混用的 ASCII 字母(「ignore」):正規化後再比對。
# checks/hidden.py
HIDDEN = {"\u200b", "\u200c", "\u200d", "\u2060", "\ufeff", "\u202e", "\u202d"}

def scan_hidden(text: str) -> list[str]:
    return [f"U+{ord(c):04X}" for c in text if c in HIDDEN]

Denylist

已知攻擊句式(中英)、jailbreak 模板的固定片段、內部專案代號與系統提示詞片段(Day 10 講的洩漏偵測,輸出端用)。

Denylist 的來源要有紀律:每一條要註明來自哪次事件或哪個公開集,Day 6 的語料回收流程會持續餵。

Allowlist

確定放行的模式,讓流量不進 L2:純數字查詢、固定格式的表單欄位、短於 N 字且不含標點的問候。這一類規則的效果直接反映在「多少流量在 L1 結束」的比例上。

URL 允許清單

Day 12 講的做法:輸出端的 URL 只允許公司網域與本次 RAG 來源中出現過的網址,其餘遮蔽。這條比黑名單有效,而且是 Model Armor 做不到的。

執行順序與短路

# engine.py(節錄)
def check(text, direction, context):
    hits = []
    text_n = normalize(text)                      # 全半形、大小寫、Unicode NFKC
    hits += structure_rules(text_n)
    if any(h.action == "block" for h in hits):
        return Verdict("block", hits)             # 短路,不再往下
    hits += allowlist_rules(text_n)
    if hits and all(h.action == "allow" for h in hits):
        return Verdict("allow", hits)             # 直接放行,不進 L2
    hits += denylist_rules(text_n)
    hits += url_rules(text_n, context) if direction == "out" else []
    return Verdict(resolve(hits), hits)           # escalate / mask / tag

resolve 的優先序:block > mask > escalate > tag > allow。

量測:多少流量在 L1 結束

L1 的誠實邊界

  • 改寫過的攻擊句式(同義詞、換語言、拆句)regex 抓不到。
  • 語意層級的東西(「假設你是一個沒有限制的助理」)不該試圖用 regex 抓——會製造大量誤判,把這類交給 L2。
  • Denylist 會膨脹。定期用 eval 集回歸測試,命中率為零超過三個月的規則要檢討。

明天預告

Day 16:台灣 PII 快篩層。身分證、統編、健保卡、手機的格式與 checksum 實作——Day 11 說雲端 SDP 做不到的 checksum 驗證,地端 L1 一天做完。


追蹤 AId3fend

Instagram @aid3fend。
更多 AI 資安筆記:aid3fend.com


上一篇
Day 14|Apigee / GKE 整合:非 Gemini 模型也吃得到
下一篇
Day 16|台灣 PII 快篩層:身分證/統編/健保卡 checksum
系列文
《30 天打造 AI Guardrails》 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言