縱深防禦
Triggerfish 將安全性實作為 13 個獨立、重疊的層。沒有任何單一層足以獨立運作。它們共同形成的防禦能優雅降級——即使某一層被攻破,其餘層仍會繼續保護系統。
安全性 縱深防禦意味著任何單一層的漏洞都不會危及系統。繞過通道驗證的攻擊者仍然面對工作階段 taint 追蹤、策略 hook 和稽核日誌。被提示注入攻擊的 LLM 仍然無法影響其下方的確定性策略層。 :::
13 個防禦層
第 1 層:通道驗證
防禦目標: 冒充、未授權存取、身份混淆。
身份由工作階段建立時的程式碼決定,而非 LLM 解讀訊息內容。在 LLM 看到任何訊息之前,通道適配器會用不可變標籤標記它:
{ source: "owner" } -- 已驗證的通道身份與註冊擁有者相符
{ source: "external" } -- 其他任何人;僅輸入,不視為指令驗證方法因通道而異:
| 通道 | 方法 | 驗證 |
|---|---|---|
| Telegram / WhatsApp | 配對碼 | 一次性驗證碼,5 分鐘過期,從使用者帳戶發送 |
| Slack / Discord / Teams | OAuth | 平台 OAuth 授權流程,回傳已驗證的使用者 ID |
| CLI | 本機程序 | 在使用者機器上執行,由作業系統驗證 |
| WebChat | 無(公開) | 所有訪客都是 EXTERNAL,永遠不是 owner |
| 電子郵件 | 域名配對 | 寄件者域名與設定的內部域名比對 |
LLM 永遠不會決定誰是擁有者。來自未驗證發送者的「我是擁有者」訊息會被標記為 { source: "external" },且無法觸發擁有者級別的指令。此決策在 LLM 處理訊息之前就由程式碼做出。 :::
第 2 層:權限感知資料存取
防禦目標: 過度授權的資料存取、透過系統憑證的權限提升。
Triggerfish 使用使用者的委派 OAuth 權杖——而非系統服務帳戶——來查詢外部系統。來源系統執行其自身的權限模型:
Plugin SDK 在 API 層級強制執行:
| SDK 方法 | 行為 |
|---|---|
sdk.get_user_credential(integration) | 回傳使用者的委派 OAuth 權杖 |
sdk.query_as_user(integration, query) | 以使用者的權限執行 |
sdk.get_system_credential(name) | 被封鎖 — 引發 PermissionError |
第 3 層:工作階段 Taint 追蹤
防禦目標: 透過上下文汙染的資料洩漏、分類資料到達較低分類通道。
每個工作階段獨立追蹤 taint 等級,反映工作階段期間存取的最高分類資料。Taint 遵循三個不變量:
- 按對話計算 — 每個工作階段有自己的 taint
- 僅能提升 — taint 增加,永不降低
- 完全重設清除一切 — taint 和記錄一起被清除
當策略引擎評估輸出時,它會比較工作階段的 taint 與目標通道的有效分類。如果 taint 超過目標,輸出被封鎖。
第 4 層:資料血統
防禦目標: 不可追蹤的資料流、無法稽核資料去向、合規缺口。
每個資料元素從來源到目的地都攜帶來源中繼資料:
- 來源:哪個整合、記錄和使用者存取產生了此資料
- 分類:分配了什麼等級以及原因
- 轉換:LLM 如何修改、摘要或合併資料
- 目的地:哪個工作階段和通道接收了輸出
血統支援正向追蹤(「這條 Salesforce 記錄去了哪裡?」)、反向追蹤(「哪些來源貢獻了此輸出?」)和完整的合規匯出。
第 5 層:策略執行 Hook
防禦目標: 提示注入攻擊、LLM 驅動的安全繞過、不受控的工具執行。
八個確定性 hook 在資料流的關鍵點攔截每個操作:
| Hook | 攔截內容 |
|---|---|
PRE_CONTEXT_INJECTION | 進入上下文視窗的外部輸入 |
PRE_TOOL_CALL | LLM 請求工具執行 |
POST_TOOL_RESPONSE | 從工具執行返回的資料 |
PRE_OUTPUT | 即將離開系統的回應 |
SECRET_ACCESS | 憑證存取請求 |
SESSION_RESET | Taint 重設請求 |
AGENT_INVOCATION | 代理對代理的呼叫 |
MCP_TOOL_CALL | MCP 伺服器工具呼叫 |
Hook 是純程式碼:確定性、同步、有記錄且不可偽造。LLM 無法繞過它們,因為不存在從 LLM 輸出到 hook 配置的路徑。Hook 層不會解析 LLM 輸出中的指令。
第 6 層:MCP Gateway
防禦目標: 不受控的外部工具存取、未分類資料透過 MCP 伺服器進入、結構描述違規。
所有 MCP 伺服器預設為 UNTRUSTED,在管理員或使用者分類之前無法被呼叫。Gateway 強制執行:
- 伺服器驗證和分類狀態
- 工具層級權限(即使伺服器被允許,個別工具也可被封鎖)
- 請求/回應結構描述驗證
- 所有 MCP 回應的 taint 追蹤
- 參數中的注入模式掃描
第 7 層:Plugin 沙盒
防禦目標: 惡意或有缺陷的 plugin 程式碼、資料竊取、未授權的系統存取。
Plugin 在雙重沙盒中執行:
Plugin 不能:
- 存取未宣告的網路端點
- 發出沒有分類標籤的資料
- 讀取資料而不觸發 taint 傳播
- 在 Triggerfish 外部持久化資料
- 使用系統憑證(僅使用者的委派憑證)
- 透過旁通道竊取資料(資源限制,無原始 socket)
Plugin 沙盒與代理執行環境不同。Plugin 是系統需要保護_的_不受信任程式碼。執行環境是代理被_允許_建構的工作區——有策略管控的存取,而非沙盒隔離。 :::
第 8 層:密鑰隔離
防禦目標: 憑證竊取、設定檔中的密鑰、純文字憑證儲存。
憑證儲存在作業系統金鑰鏈(個人版)或 Vault 整合(企業版)中。它們永遠不會出現在:
- 設定檔中
StorageProvider值中- 日誌條目中
- LLM 上下文中(憑證在 HTTP 層注入,在 LLM 之下)
SECRET_ACCESS hook 記錄每次憑證存取,包括請求的 plugin、憑證範圍和決策。
第 9 層:檔案系統工具沙盒
防禦目標: 路徑穿越攻擊、未授權的檔案存取、透過直接檔案系統操作的分類繞過。
所有檔案系統工具操作(讀取、寫入、編輯、列表、搜尋)都在沙盒化的 Deno Worker 中執行,其作業系統級別的權限範圍限定在工作階段 taint 對應的工作區子目錄。沙盒強制執行三個邊界:
- 路徑監禁 — 每個路徑被解析為絕對路徑,並以分隔符感知的配對方式對照監禁根目錄檢查。逸出工作區的穿越嘗試(
../)在任何 I/O 發生之前就被拒絕 - 路徑分類 — 每個檔案系統路徑透過固定解析鏈進行分類:硬編碼受保護路徑(RESTRICTED)、工作區分類目錄、設定的路徑映射,然後是預設分類。代理無法存取超過其工作階段 taint 的路徑
- Taint 範圍權限 — 沙盒 Worker 的 Deno 權限被設定為與工作階段當前 taint 等級匹配的工作區子目錄。當 taint 提升時,Worker 以擴展的權限重新產生。權限在工作階段內只能擴大,永不縮小
- 寫入保護 — 關鍵檔案(
TRIGGER.md、triggerfish.yaml、SPINE.md)在工具層被寫入保護,與沙盒權限無關。這些檔案只能透過專門的管理工具修改,這些工具強制執行自己的分類規則
第 10 層:代理身份
防禦目標: 透過代理鏈的權限提升、透過委派的資料洗白。
當代理呼叫其他代理時,加密委派鏈防止權限提升:
- 每個代理有一個證書,指定其能力和分類上限
- 被呼叫者繼承
max(own taint, caller taint)— taint 只能通過鏈增加 - taint 超過被呼叫者上限的呼叫者被封鎖
- 循環呼叫被偵測並拒絕
- 委派深度受限並被強制執行
第 11 層:稽核日誌
防禦目標: 不可偵測的入侵、合規失敗、無法調查事件。
每個安全相關的決策都以完整上下文記錄:
json
{
"timestamp": "2025-01-29T10:23:45Z",
"user_id": "user_123",
"session_id": "sess_456",
"action": "slack.postMessage",
"target_channel": "external_webhook",
"session_taint": "CONFIDENTIAL",
"target_classification": "PUBLIC",
"decision": "DENIED",
"reason": "classification_violation",
"hook": "PRE_OUTPUT",
"policy_rules_evaluated": ["rule_001", "rule_002"],
"lineage_ids": ["lin_789", "lin_790"]
}記錄的內容:
- 所有操作請求(允許和拒絕)
- 分類決策
- 工作階段 taint 變更
- 通道驗證事件
- 策略規則評估
- 血統記錄建立和更新
- MCP Gateway 決策
- 代理對代理的呼叫
稽核日誌無法被停用。這是策略階層中的固定規則。即使組織管理員也無法關閉自己操作的日誌。企業部署可選擇啟用完整內容記錄(包括被封鎖的訊息內容)以滿足鑑識需求。 :::
第 12 層:SSRF 防護
防禦目標: 伺服器端請求偽造、內部網路偵察、雲端中繼資料竊取。
所有出站 HTTP 請求(來自 web_fetch、browser.navigate 和 plugin 網路存取)先解析 DNS,然後對照硬編碼的私有和保留範圍拒絕清單檢查解析後的 IP。這防止攻擊者透過精心製作的 URL 誘騙代理存取內部服務。
- 私有範圍(
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)始終被封鎖 - 本地鏈路(
169.254.0.0/16)和雲端中繼資料端點被封鎖 - 環回位址(
127.0.0.0/8)被封鎖 - 拒絕清單是硬編碼的,不可配置——沒有管理員覆蓋
- DNS 解析在請求之前進行,防止 DNS 重繫結攻擊
第 13 層:記憶體分類閘控
防禦目標: 透過記憶體的跨工作階段資料洩漏、透過記憶體寫入的分類降級、對分類記憶體的未授權存取。
跨工作階段記憶體系統在寫入和讀取時都強制執行分類:
- 寫入:記憶體條目被強制設為當前工作階段的 taint 等級。LLM 無法為儲存的記憶體選擇較低的分類。
- 讀取:記憶體查詢以
canFlowTo過濾——工作階段只能讀取等於或低於其當前 taint 等級的記憶體。
這防止代理將 CONFIDENTIAL 資料以 PUBLIC 身份儲存在記憶體中,然後在較低 taint 的工作階段中取回以繞過禁止降級寫入規則。
信任層級
信任模型定義了誰對什麼有權限。較高層級無法繞過較低層級的安全規則,但可以配置這些規則中的可調參數。
個人版: 使用者就是組織管理員。完全自主。Triggerfish 無法查看。供應商預設對使用者資料零存取,且只能透過使用者的明確、有時限、有記錄的授予來獲得存取權。 :::
各層如何協同運作
考慮一個提示注入攻擊,惡意訊息嘗試竊取資料:
| 步驟 | 層 | 操作 |
|---|---|---|
| 1 | 通道驗證 | 訊息標記為 { source: "external" } — 非擁有者 |
| 2 | PRE_CONTEXT_INJECTION | 輸入掃描注入模式,進行分類 |
| 3 | 工作階段 taint | 工作階段 taint 不變(未存取分類資料) |
| 4 | LLM 處理訊息 | LLM 可能被操控請求工具呼叫 |
| 5 | PRE_TOOL_CALL | 對照外部來源規則進行工具權限檢查 |
| 6 | POST_TOOL_RESPONSE | 任何返回的資料被分類,taint 更新 |
| 7 | PRE_OUTPUT | 輸出分類與目標的比對檢查 |
| 8 | 稽核日誌 | 整個序列被記錄以供審查 |
即使 LLM 在步驟 4 被完全攻陷並請求資料竊取工具呼叫,其餘層(權限檢查、taint 追蹤、輸出分類、稽核日誌)仍會繼續執行策略。沒有單點失敗會危及系統。
