dsh-sensitive-guard:把密鑰擋在模型上下文之外

前言

智能體開發裏有一個反覆出現的場景:把帶密鑰的配置文件、.env、連接串丟進對話,或者讓工具執行後把輸出送回上下文。憑證一旦進入上下文,模型可能把它原樣寫進日誌、代碼和回覆裏。靠提示詞提醒「不要泄露密鑰」並不可靠,因爲模型拿到文本時已經看到了明文。

dsh-sensitive-guard 就是爲這件事做的插件:不是提醒,而是在用戶輸入和工具結果進入上下文之前就把憑證替換成 <SG_xxxxxxxxxxxx> 佔位符,明文加密留在本機保管庫,需要用的時候再點開或導出。

下面介紹這個插件:它安裝在哪、攔截哪些入口、認得出什麼、配置怎麼改,以及哪些邊界要提前知道。

定位

插件名稱 dsh-sensitive-guard,維護者 wangxy496,分類 admin-security,許可證 MIT,當前版本 0.5.0(以 npm 包爲準)。目標只做一件事:密鑰、令牌、密碼在進入模型上下文前被換成 <SG_ 前綴的代號,明文以 AES-256-GCM 加密保存在本地保管庫。模型拿到的是代號,用戶點一下鎖能看到真值。

代號裏 12 位十六進制是 HMAC-SHA256(vault.key, 明文) 的前綴。密鑰只在本機,所以拿到代號的人沒法靠窮舉短密碼反推;同一個密鑰每次得到同一個代號。0.3.4 及更早版本用的是 sha256(明文) 前綴,短密碼可以離線猜出來,升級後舊代號仍保留可解鎖,新捕獲一律換成新算法。

核心功能

插件分 host 端和客戶端兩半:host 端抓數據、管保管庫、開接口;客戶端加設置頁和對話裏的鎖形膠囊。

註冊點 端 作用
agent/pre-step host 鉤子 用戶輸入進上下文前脫敏
tools/post-execute host 鉤子 工具結果進上下文前脫敏
llm/stream host 鉤子 出網的流式文本兜底清洗
sensitive_guard_status、sensitive_guard_export host 工具 查狀態;把明文導出成 0600 臨時文件
/guard/* HTTP 路由 設置頁取數、揭示明文、暫停/關閉保護、規則清單與開關、手動錄入
settings.section 客戶端 slot 設置頁裏的「敏感保護」
conversation.user.text.node 客戶端 slot 對話裏的鎖形膠囊

前提只要 tools 一個。webServer 不存在時脫敏照常工作,只是設置頁和揭示用不了。

攔截點有兩層:用戶輸入走 agent/pre-step,工具結果走 tools/post-execute;llm/stream 再洗一遍出網的流式文本做兜底,只處理 text-delta 和 reasoning-delta,tool-call-delta 直接放過,否則工具參數會被替換成佔位符。

安裝

要求 dsh 0.2.0 以上、Node 18 以上。Windows 11 和 Ubuntu 22.04 均實測,macOS 未測。資料裏給了三種裝法,這裏列前兩種。

第一種直接編輯 ~/.dsh/profiles/<profile>/package.json:

{
  "dependencies": {
    "dsh-sensitive-guard": "0.5.0"
  },
  "dsh": {
    "profile": {
      "bundles": ["dsh-sensitive-guard"]
    }
  }
}

bundles 裏保留原有條目,只追加這一行。然後在同目錄跑 pnpm install,重啓 dsh。

第二種讓 dsh 自己裝,在會話裏說「安裝這個目錄下的插件:/絕對/路徑」,它會走 install_bundle。

裝完後可以驗證。發一句:

我的 api key 是 sk-proj-abcdefghijklmnopqrstuvwx

應該顯示成 我的 api key 是 sk- 加一個鎖形膠囊。點開能看到明文,20 秒後自動遮回。再打開「設置 → 敏感保護」,最下面一行 inline reveal: 顯示 ok 即正常。

日常使用

用戶消息裏的佔位符會渲染成可點的膠囊,顏色按類型分:密碼紅、token 紫、key 綠、其它藍,點開後統一變橙。點擊走本機接口,明文不經過模型。

注意:只有用戶消息的膠囊能點。AI 回覆和工具結果走另一套節點渲染,宿主沒有留「包一層」的口子,強行做就得整體替換助手消息渲染,宿主升級會白屏,所以作者沒做。這兩類位置的明文要去設置頁看。

設置頁分兩個標籤。「敏感數據管理」是保管庫,列表保留名稱/值、分類、規則三列,頂部「添加敏感數據」是彈窗,填名稱、明文、類型(可以自定義分類);明文直接加密進保管庫,不寫 rules.json,之後這段值出現就自動遮。小鎖臨時查看明文,旁邊的「複製明文」按鈕走本機接口取完整明文寫到剪貼板,明文不進模型上下文。

「自動檢測規則」列出 242 條規則(221 條上游 + 21 條內置),可按名稱/關鍵詞搜索、按全部/已開/已關篩選、每頁 20 條翻頁,逐條開關。默認「已開啓優先」排序,14 條默認關閉的規則集中在最後兩頁。

模型拿不到明文。要連庫或調接口時讓它先導出:

sensitive_guard_export(<SG_xxx>)   明文寫到 0600 文件,返回路徑
命令裏用 $(cat /那個/路徑)         明文不進上下文

工具只有兩個:sensitive_guard_status 看狀態和條目元數據,不含明文,連前 8 位預覽都不返回;sensitive_guard_export 負責導出,文件 15 分鐘後過期。

一個常被當成 bug 的點:模型讀 application.yml 時看到的是佔位符,拿它去連庫必然失敗。要麼先導出,要麼臨時點一次暫停。

認得出什麼

系統規則庫內置 221 條來自 gitleaks 的廠商規則(1Password、Adobe、Adafruit、Age、AWS、GCP、Azure、Stripe、Slack 等,MIT 許可,見 NOTICE)。每條帶關鍵詞預篩、正則和可選熵值閾值,命中後才跑正則,不會 221 條全量掃一遍。默認開 207 條高置信規則;10 條「近似轉換」和 4 條寬鬆規則默認關,在規則標籤裏自己開。上游還有一條 pkcs12-file 按文件名 .p12/.pfx 判定,文本脫敏用不上,規則頁標爲「不適用」。規則數據由 scripts/build-gitleaks.mjs 從上游 TOML 轉換生成,升級時重跑即可。

覆蓋的廠商前綴:sk-、sk_、ghp_、glpat-、npm_、hf_、AKIA、ASIA、xox*-、AIza、whsec_、SG.,以及 PEM 私鑰塊、JWT、Bearer 令牌。

賦值和環境變量也認:password=、secret:、api_key:、SECRET_KEY=、DB_PASSWORD=。

JSON 和結構化數據一塊處理。工具讀 .env、cat config.json 返回的常是一整段 JSON,{"password": "Abc!23456"} 這種值沒有廠商前綴,只看值形狀認不出來。現在只要字段名是 password、passwd、pwd、token、credential、api_key、db_pass、dsn、connection_string 這類(含下劃線、連字符、駝峯變體),值一律換代號,鍵名、層級和別的值都不動;已經解析成對象的工具結果同樣處理。

示例和佔位不遮:your_password_here、${DB_PASS}、<your-token>、changeme,以及 password_policy、token_count 這種只是描述字段的名字。

中文說法也能識別:數據庫密碼是…、我的 token 是…。中文語境的密碼從 6 位起就攔(我的密碼是 123456);英文和 key=value 形式從 8 位起,否則 the secret is hidden 裏的 hidden 會被當成密碼。句尾語氣詞和標點留在佔位符外面。

連接串和命令行也覆蓋:mysql://user:pass@host、postgresql://…、JDBC 參數裏的 password=、X-Api-Key:,以及 mysql -uroot -pabc12345、redis-cli -a xxx;只認這幾個客戶端,docker run -p 8080:80 不會被誤傷。

被換行打斷的 key 也認:sk-proj-… 粘貼時常被插入 \r、\n 或 \r\n,這類按跨行匹配把兩段拼回完整值再入庫,佔位符與不換行時完全相同。

熵值檢測默認關閉,rules.json 里加 "entropy": true 纔開。它抓沒有前綴的隨機串,但只對 base64 或混合字符集有用;純 hex 串上限是 4.0 bits/char,永遠夠不到 4.5 的默認閾值,8f3a9b2c… 這種開了也抓不到;閾值降到 3.9 又會誤傷英文長句。

配置

配置文件是 <數據目錄>/rules.json,存盤後兩秒內生效,不用重啓。Windows 下是 %USERPROFILE%\.dsh\sensitive-guard\,Linux 下是 ~/.dsh/sensitive-guard/。

{
  "extraRules": [
    { "id": "my-internal", "pattern": "X-WX-Token:\\s*([A-Za-z0-9]{20,})", "flags": "i", "group": 1 }
  ],
  "disabled": ["jwt"],
  "enabledRules": [],
  "outboundScrub": true,
  "entropy": false,
  "entropyThreshold": 4.5,
  "allowCidrs": ["192.168.0.0/16"]
}

group 要遮擋的捕獲組序號,0 表示整個匹配。自定義正則的性能需要自己負責,別寫 (a+)+、(.*)* 這類結構——它跑在同步鉤子裏,回溯爆炸會卡住宿主。

disabled 寫規則 id 關掉單條默認開的規則,enabledRules 寫 id 打開一條默認關的規則;設置頁開關就是維護這兩個列表。outboundScrub: false 會關掉出網補洗。allowCidrs 默認空數組,意思是隻認本機。

網絡訪問

五個 HTTP 接口默認只接受本機迴環:桌面版的瀏覽器和 dsh 在同一臺機器,天然滿足。只有 dsh 以 --host 0.0.0.0 啓動、又從另一臺機器的瀏覽器打開時會拒。兩個辦法:一是在服務所在機器的瀏覽器裏打開 127.0.0.1:3080,不用改配置;二是在 rules.json 里加 "allowCidrs": ["192.168.0.0/16"],存盤兩秒生效,設置頁頂欄會出橙色提示。

沒配的時候頁面不會白屏,會降級成只讀:能看到有哪些條目、什麼類型、什麼時候入庫、命中幾次,看不到值和 8 位預覽。放行之後除了網段匹配還有三道檢查,都不用配:Host 頭必須是 IP 字面量(防 DNS rebinding);POST 必須是 application/json(跨站 text/plain 表單發不進來);Origin 必須和 Host 同源。

要清楚代價:開放網段意味着該網段裏任何設備都能讀到完整明文,不只是預覽。只在可信內網開,用完把這一行刪掉。沒做登錄是因爲加一套認證會讓點小鎖多兩步,不如把網段收窄。

已知限制

  1. 對話裏的膠囊只覆蓋用戶消息,原因見「日常使用」。
  2. 不做出口改寫。平臺的 tools/pre-execute 只有 allow、deny、cancel、ask 四個動作,沒有改參數的掛點,所以用憑據只能走導出文件。
  3. 保管庫防的是誤讀和落盤明文,不防已經拿到你賬戶權限的人。加密是 AES-256-GCM,Windows 上密鑰文件用 icacls 收緊到當前賬戶。
  4. 歷史會話不受保護。pre-step 在宿主把用戶原文寫進日誌之後才執行,session/event 又只是觀察者,插件沒有機會在落盤前改。舊會話裏的明文還在磁盤上,那批密鑰該輪換。
  5. exports/ 下是唯一落盤的明文,15 分鐘後在下次導出時清理。
  6. 保管庫最多留 500 條,超出按最後命中時間淘汰。
  7. 保管庫壞掉時會放行,保證對話不中斷,但會計數,可在 sensitive_guard_status 裏看到。

小結

dsh-sensitive-guard 適合需要在對話裏貼憑證、又不想讓明文進模型上下文或日誌的場景:它把攔截點放在進上下文之前,用本機加密保管庫存真值,並提供點開、複製和導出三種取值方式。安裝前建議通讀源碼和 LICENSE/NOTICE,確認它註冊的鉤子和 HTTP 路由符合你的環境。插件以當前 dsh 進程權限運行,能讀會話內文本,也能在放行網段下把明文暴露給該網段。

目錄頁:https://www.skillhub.cn/plugins/wangxy496/dsh-sensitive-guard

GitHub:https://github.com/wangxy496/dsh-sensitive-guard

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜