iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0

Day 13 我用 Promptfoo 反覆測惡意 SOP,但還有一種文件根本不需要藏指令:它只要屬於別的使用者、部門或 tenant,被當前 Agent 搜出來就已經出問題。

這種失敗不一定會出現在最後回答。未授權文件只要進入 prompt,內容就可能出現在 trace、cache、日誌或後續工具參數。因此我不能等模型回答後才刪掉敏感字句。在 retrieval 開始以前,候選文件就必須受權限限制。

今天真正呼叫 LLM 的位置

Live 模式的原則是:模型永遠排在文件授權之後。ChatOpenAI 不負責判斷使用者能否讀文件,也不應看到被拒絕的標題、摘要或 chunk。

server-side principal
  → tenant filter
  → document ACL
  → retrieval
  → post-retrieval authorization
  → content rail
  → Live LLM

Day 14 的 Live 實驗現在使用 server-side principal campus-a / student。程式先從三份文件中擋下其他 tenant 與 admin-only 文件,再把唯一獲准的 VPN SOP 組進模型訊息。只要前面任一步拒絕,文件內容就不會出現在真實模型的 context。

我先把 RAG 路徑改寫成這樣

後端驗證的 principal
  → tenant 與文件 ACL 前置過濾
  → 只對已授權候選集合做 retrieval
  → 取回後再次授權
  → NeMo Retrieval Rail 檢查內容
  → model-visible context

這裡有兩種很容易混在一起的檢查:

邊界 回答的問題
Tenant/ACL 授權 這個身分有沒有權限讀這份文件?
Retrieval Rail 這份已授權文件的內容,適不適合放進模型 context?

NeMo Guardrails 官方將 Retrieval Rails 放在 RAG retrieval 完成之後,用來處理 retrieved chunks。這個位置很適合做指令型內容、敏感資料或其他 content checks,但它不會自動知道我應用程式的 tenant 或文件 ACL。NVIDIA NeMo Guardrails:Guardrails Configuration

第一層:檢索前先縮小候選文件

Day 14 的 mock principal 由後端建立:

principal = Principal(
    user_id="student.demo",
    tenant_id="tenant-a",
    roles=frozenset({"helpdesk"}),
)

tenant_id 和 roles 不會從使用者 prompt 或 SOP 內容推測。如果真實系統有登入機制,這些資料應該來自經驗證的 session、token claims 或後端 identity service。

這次候選資料有四份:

文件 Tenant ACL 前置決定
SOP-A-VPN-001 A helpdesk/admin 允許。
SOP-A-VPN-INJECT-001 A helpdesk 授權通過,留給後面 content rail 檢查。
SOP-A-ADMIN-001 A admin 當前角色不符,不進入 retrieval。
SOP-B-VPN-001 B helpdesk 不同 tenant,不進入 retrieval。

因此 vector search 或其他相似度搜尋只會在前兩份文件中進行。這不只是把最後結果遮住,而是讓未授權文件一開始就不參與排名與 context 組裝。

Azure AI Search 的 document-level access control 也是在 indexing 保留權限 metadata,並在 query time 依使用者或群組身分排除未授權文件。我的 mock 不是 Azure 實作,但它要表達的邊界相同:權限是 query 的條件,不是 prompt 裡的建議。Azure AI Search:Document-Level Access Control

第二層:取回後為什麼還要再驗一次

只做前置過濾,意味著我完全相信 retriever 的實作不會改壞、cache 不會混到別的 tenant,而且權限在查詢期間不會變動。這些條件都可能失效。

所以我在 demo 裡故意模擬 retriever 錯誤塞回 SOP-B-VPN-001。取回後的授權複檢不看結果排名,只重新核對 server-controlled principal 和文件 metadata;這份跨 tenant 文件會留下 post_retrieval_authorization blocked,然後被移除。

前置過濾是主防線,檢索後複檢是 defense in depth。如果實際搜尋服務支援原生 ACL 或 security filter,我會優先讓它在 query time 執行,而不是先取回全部內容再靠 Python 刪除。

第三層:NeMo Retrieval Rail 檢查的是內容

經過 tenant 與 ACL 後,SOP-A-VPN-INJECT-001 仍然是當前身分有權限讀取的文件。但它的內容含有假造 SYSTEM MESSAGE,所以還不應進入模型 context。

我在 guardrails/day14/config.yml 放了 NeMo Guardrails 0.23.0 的 regex Retrieval Rail 設定:

rails:
  config:
    regex_detection:
      retrieval:
        patterns:
          - '(system|developer)\s*(prompt|message)'
          - 'ignore.{0,32}(previous|prior|all).{0,20}(instructions?|rules?)'
          - '忽略.{0,16}(指令|規則)'
        case_insensitive: true
  retrieval:
    flows:
      - regex check retrieval

NVIDIA 官方文件對 regex check retrieval 的行為定義很具體:命中 retrieval patterns 的 chunks 會被移除,只剩下未命中的 chunks 作為 context。NVIDIA NeMo Guardrails:Regex Detection Integration

本機頁面為了不需要 API key,會讀取同一份 YAML 並做 deterministic preview。它不會假裝 NeMo runtime 已經執行。要實際用 NeMo 0.23.0 載入設定,可以安裝 optional dependency 後執行:

uv pip install -e '.[guardrails]'
python -m app.validate_nemo_config

預期會列出:

NeMo config loaded: regex check retrieval

這些 regex 只是可解釋的入門案例,不是完整的 injection detector。我會繼續用 Day 13 的 corpus 抓已知繞法,也會在後面比較其他 retrieval rails 與紅隊測試。

image

這次畫面與測試證明了什麼

固定情境從四份 mock 文件開始。前置授權剩下兩份,檢索後故意混入的跨 tenant 結果被複檢擋下,指令型內容又被 Retrieval Rail 預覽隔離,最後只剩一份文件可進入 context。

我新增的單元測試會分開檢查 tenant、ACL、檢索後複檢和 NeMo YAML patterns。完整 suite 是 92 個 unit tests 通過,Promptfoo 是 15 passed、0 failed、0 errors。

這些結果證明固定 mock 流程保留了授權順序,不代表生產環境的 identity provider、向量資料庫、cache 與 ACL 同步都已經安全。真實系統還要處理權限變更的延遲、default deny、審計日誌,以及後端服務帳號是否擁有過大權限。

Day 15 會從文件邊界轉到工具邊界:當 Agent 同時看到太多功能重疊的工具,為什麼不是能力變強,而是選錯工具的機會變多。

本日程式碼

day-14-live-document-authorization


上一篇
Day 13|我把惡意 SOP 丟進 Promptfoo,Agent 到底會不會照做?
下一篇
Day 15|我給 Agent 20 個工具後,它反而更常做錯事
系列文
從 LLM、Agent 到 Guardrails:30 天打造可控、安全、可驗證的 AI Agent 共 15 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言