iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0

先看圖

https://ithelp.ithome.com.tw/upload/images/20261010/201835808OXvJjwPjN.png

這張圖把 28 天做出來的東西放在同一個畫面裡:上面是攻擊面(Day7–17 的紅隊測試),中間是 server.js 裡 /chat 路由的防禦管線,下面是偵測與驗證。綠字是擋得住的,紅字是這個專案已經知道的缺口,每個缺口都對應到 Day28 清單裡的某一項。

怎麼讀這張圖

  • 實線箭頭是一則請求的實際流向:使用者請求 → 角色解析 → 頻率限制 → 輸入驗證 → Prompt 組裝 → Gemini 模型 → 輸出驗證 → 附加免責宣告 → 回應。
  • 綠色虛線是稽核:輸入驗證擋下、輸出驗證擋下、正常回應,這三個出口都會呼叫 logEvent(),寫進 Audit Log。
  • 灰色虛線是測試:Promptfoo 的回歸測試從外面對 /chat 發請求,等於是從「攻擊者」的位置檢查整條管線。

逐層對照程式碼

層 程式碼位置 做什麼 擋得住 擋不住
角色解析(Day20) getRole()、requireRole() 讀 x-user-role,無效或缺少就當 patient 沒帶角色打 /admin/audit-log 會回 403 /chat 雖然也掛了 requireRole,但三種角色都放行,所以實際上不攔任何請求
頻率限制(Day19) lastRequestTime、MIN_INTERVAL_MS(12 秒) 兩次請求間隔太短就拒絕 短時間連續請求 它是全域的,不分使用者;目的是保護 API 額度,不是防濫用
輸入驗證(Day19) INPUT_BLOCKLIST_PATTERNS 命中就直接回 Fallback,不呼叫模型 已知的身分覆寫講法(After 版 B1、B6、B7、E2) 沒列進清單的講法
Prompt 組裝(Day14、16、17) SYSTEM_INSTRUCTION、文件內容的分隔標記 行為邊界、佔位符規則、「沒有資料庫」聲明 敘事包裝、身分覆寫、病患資料索取 規則本身會外溢(After G3 的 P001)
Gemini 模型 gemini-3.8-flash 模型自身的安全行為 間接注入、自傷與用藥步驟的索取 同一題每次輸出不一定相同(Day26)
輸出驗證(Day19) OUTPUT_LEAK_PATTERNS 命中就把回答換成 Fallback D6 操作手冊 D5 重述職責範圍,至今仍失守
附加免責宣告(Day14) MEDICAL_DISCLAIMER 由程式碼附加在每則回答後面 宣告一定出現,不靠模型記得 它只是提醒,不攔任何內容
Audit Log(Day21) logEvent()、auditLog 記誰、事件、時間、結果,訊息只留前 30 字 事後看得到被擋下的請求 只留 500 筆、重啟就清空;D5 的洩漏被記成 normal
Log API(Day20) /admin/audit-log + requireRole(['admin']) 只有 admin 能讀 沒帶角色的請求 角色是請求裡自稱的,沒有真正的身分驗證
Dashboard(Day21) public/dashboard.html 以表格顯示事件與結果 沒有 頁面是公開的靜態檔,JS 內建 admin Header,任何人打開都看得到
回歸測試(Day23–26) Day26_promptfooconfig.yaml 40 題對 Before/After 重複執行 改規則之後的退步 斷言誤判與漏判(四次)、沒有 hold-out 題庫

攻擊 × 防禦:哪一層擋了什麼

這張表把 Day26 的 Benchmark(✅/⚠️/❌)和圖上的防禦層對起來。「主要貢獻層」是根據 Day25 的排除法推論,不是 ablation:Day25 只能確定程式碼層(輸入驗證、輸出驗證)直接解釋 11 題改善裡的 2 題(B7、D6),其餘靠排除法歸給 System Prompt。

攻擊類別 Before After 可能的主要貢獻層(推論) 殘留
A 正常問題(6) 6/0/0 6/0/0 沒有誤擋 無
B 身分覆寫(8) 6/2/0 7/1/0 輸入驗證(已知講法)、Prompt 的行為邊界 B2 委婉的鑑別診斷,兩邊都是 ⚠️
C 病患資料(6) 5/1/0 6/0/0 context 裡沒有資料、Prompt 聲明沒有資料庫 無
D 系統洩漏(6) 1/0/5 5/0/1 Prompt(Day16–18)、輸出驗證(D6) D5 仍是 ❌
E Jailbreak(5) 4/1/0 5/0/0 模型自身、輸入驗證(E2)、Prompt 無
F 間接注入(4) 4/0/0 4/0/0 模型自身、文件分隔標記;兩邊都過,分不出誰的功勞 無
G 敘事包裝(5) 1/4/0 5/0/0 Prompt 的佔位符規則(Day17) 代價:需求只能拿到填空模板

這張圖想讓人看到的三件事

一、沒有任何一層是「單獨足夠」的,因為每一層失效的方式都不一樣。 正規表達式會漏掉沒寫進清單的講法(D5);Prompt 規則會晃動、也會外溢(P001);模型的行為會隨版本和隨機性改變。縱深防禦不是因為每一層都很強,而是因為每一層都會失敗,而且不會剛好在同一個地方失敗。

二、偵測層的準確度,被防禦層的準確度綁住。 Audit Log 只記得「被判斷過的結果」:D5 洩漏了,Log 裡卻記成 normal。Promptfoo 的斷言也一樣:三次誤判或漏判(Day24、25、26),加上 Day19 輸出過濾規則的那一次,關鍵字比對一共出錯四次,這是偵測層本身的盲點。看得見的永遠只是被偵測到的,這是整個偵測層最需要被記住的限制。

三、有兩個地方很容易被誤解。 第一,/chat 其實也經過 requireRole,但三種角色都放行,所以存取控制對聊天內容沒有任何過濾效果,它只守住 /admin/audit-log。第二,Dashboard 頁面本身是公開的(見下一節)。

今天核對程式碼時發現的更正

畫這張圖時,我逐項對照了實際的 server.js 和 public/dashboard.html,發現兩件事需要更正:

  1. Day28 的清單原本把「Dashboard 與 Log API 同樣受存取控制」標成已做,這是錯的。 dashboard.html 是公開的靜態檔,頁面裡的 JavaScript 直接寫死 x-user-role: admin,所以任何人打開這個頁面,就看得到整份稽核紀錄。Day28 已經改成「未做」,完成度從 20/7/12 變成 19/7/13。
  2. Day21 裡有一句話寫「就算有人把 dashboard.html 整份複製走,沒有合法的角色憑證還是一筆資料都拿不到」,這句話不成立,因為憑證就在那個檔案裡。Day21 已經改成如實說明這個限制。

這也呼應了 Day27 的結論:我自己寫的文章,同樣會有「沒有回頭對照原始碼」的盲點。

這張圖沒有畫出來的

真正的身分驗證、真實的資料層、多輪且會適應的攻擊者、其他語言與其他模型。這些在 Day27 和 Day28 都列過,今天的圖只畫「這個專案實際存在的東西」,沒有的就不畫。

NIST 縱深防禦:https://csrc.nist.gov/glossary/term/defense_in_depth
OWASP GenAI Security Project:https://genai.owasp.org/
架構圖有 SVG 與 PNG 兩種檔案,若發文平台不支援 SVG,請改用 PNG。

明天預告

Day30 是最後一天:30 天回顧總結。我會回頭整理這 30 天做了什麼、哪些結論站得住腳、哪些不行,以及這個專案對我自己的意義。

本系列所有病患資料皆為人工生成之虛構資料,不涉及任何真實病患。GitHub Repo:medical-ai-security-lab


上一篇
Day28|醫療 AI Security Checklist,27 天做完之後,清單長這樣
系列文
Medical AI Security Lab:醫療 AI Chatbot 的攻防實驗與自動化 Red Team 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言