雲端線走完了。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 的只有零歧義的東西。
# 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]
已知攻擊句式(中英)、jailbreak 模板的固定片段、內部專案代號與系統提示詞片段(Day 10 講的洩漏偵測,輸出端用)。
Denylist 的來源要有紀律:每一條要註明來自哪次事件或哪個公開集,Day 6 的語料回收流程會持續餵。
確定放行的模式,讓流量不進 L2:純數字查詢、固定格式的表單欄位、短於 N 字且不含標點的問候。這一類規則的效果直接反映在「多少流量在 L1 結束」的比例上。
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。
Day 16:台灣 PII 快篩層。身分證、統編、健保卡、手機的格式與 checksum 實作——Day 11 說雲端 SDP 做不到的 checksum 驗證,地端 L1 一天做完。
Instagram @aid3fend。
更多 AI 資安筆記:aid3fend.com