dsh-cookie-janitor:清掉過期 dsh-auth-* cookie,避免 DSH 請求頭溢出 431

前言

DSH 的認證依賴瀏覽器 cookie。每次啓動時 DSH 會按監聽地址籤一條新 cookie,名字裏含端口;端口是隨機的,cookie 歸屬範圍卻只到主機、不含端口。於是瀏覽器每次請求都會把歷史上所有 dsh-auth-* 一起帶上,攢到約 70 條後 Cookie 頭接近 Node.js 默認的 16 KiB上限,服務端在應用代碼執行前就返回 431 Request Header Fields Too Large,表現是白屏、加載卡住或認證失敗。

下面介紹 yan3536 維護的 dsh-cookie-janitor 插件(分類 admin-security,GitHub 1 stars,MIT)。它掛在 Host 的 HTTP 服務上,在請求進入應用之前把屬於已消失端口的舊 cookie 裁掉,並對已經溢出的請求做一次 302 兜底。README 裏給出的離線自測覆蓋 24 個用例。

它解決什麼問題

DSH 的瀏覽器認證 cookie 名由地址推導:

cookie 名 = "dsh-auth-" + base64url(sha256("host:port"))

名字裏帶了端口,而端口並不固定:

  • 桌面版啓動時寫死 --port 0,讓操作系統挑空閒端口;
  • 命令行 dsh --profile web --port 0 同理。

於是 30 天內啓動 N 次就堆 N 條無用 cookie,且有效期不因使用而續期。單條約 221 字節,README 實測 73 條時 Cookie 頭 16277 B 仍能通過,74 條時 16500 B 拿到 431;因爲 Host、User-Agent 等頭同樣佔用額度,實際觸頂會略早,按 70 條左右估算。

已有做法是手動在瀏覽器裏清掉 127.0.0.1 的站點數據,能解一次急,但下次啓動仍然開始重新堆。這個插件把清理放進請求鏈路,不再需要人工介入。

三層行爲

插件按請求發生順序做三件事:

  1. 請求入口:從 Cookie 頭裏摘掉屬於其他端口的 dsh-auth-*,應用看不到這些 cookie;
  2. 響應出口:對被摘掉的那些下發 Max-Age=0,讓瀏覽器從 cookie 庫裏刪除;
  3. 溢出兜底:如果請求頭已經大到 Node 拒絕解析,就把 431 換成一次 302 回到原地址並附清除指令,用戶看到的是刷新一次,而不是崩潰。

當前正在使用的那條 cookie 永遠不動,刪掉它會把用戶從正在使用的實例裏登出。清理完成後第二次請求的 Cookie 頭就只剩一條,之後不再增長。

安裝

從 Git 倉庫裝:

dsh plugin --profile web add github:yan3536/dsh-cookie-janitor

離線調試源碼時,先克隆再用絕對路徑裝:

git clone https://github.com/yan3536/dsh-cookie-janitor
dsh plugin --profile web add /絕對路徑/dsh-cookie-janitor

裝完必須完全退出 DSH 再啓動。插件掛在 Host 的 HTTP 服務上,只有在啓動時才能裝上,刷新頁面不生效。

桌面版用戶把 --profile web 換成 --profile desktop。桌面版 profile 被 Electron 獨佔,必須先完全退出 DSH 再執行命令,否則會報 Open DeepSeek Harness Desktop once to initialize its profile, then fully quit it before running dsh plugin --profile desktop.

卸載:

dsh plugin --profile web remove dsh-cookie-janitor

配置

全部可選,默認值即推薦值。需要改時寫進 profile 的 cordis.patch.yml:

- id: cookie-janitor
  name: dsh-cookie-janitor
  config:
    keepForeign: 0        # 允許保留幾條非當前端口的 cookie;0 = 全清
    pruneRequests: true   # 關掉就只剩溢出兜底
    rescueOverflow: true  # 關掉則 431 原樣返回
    maxHeaderBytes: 0     # >0 時把 Node 的頭上限抬到這個字節數
    attachTimeoutMs: 15000

maxHeaderBytes 只是把症狀往後推,真正止血的是 keepForeign: 0。同一瀏覽器需要同時連多個 DSH 實例時,把 keepForeign 設成實際並用的實例數,插件按瀏覽器發送順序保留最新的那幾條。

會不會誤刪

不會。判定完全按 DSH 自己的算法來:

  1. 只碰 dsh-auth- 開頭的 cookie,主題、語言、第三方 cookie 一律不動;
  2. 當前 Host 對應的那條永遠保留;
  3. 被刪的那些對應的監聽端口在當前進程裏不存在,留着沒有用處。

如果哪天 DSH 換了 cookie 命名規則,插件識別不到任何 dsh-auth-*,於是什麼都不做,最壞情況是退回原狀。

離線驗證

node test/smoke.mjs

24 個用例,不依賴 DSH、不聯網:16 個純函數(cookie 名推導、authority 歸一化、Cookie 頭解析、清理策略的六種情形、過期指令拼裝、原始報文與 target 抓取);6 個端到端(真實 node:http 服務器加裸 socket,覆蓋正常透傳、乾淨請求逐字節不變、20 KiB 頭被救成 302、關掉兜底後恢復原生 431、放寬上限生效、關掉清理後原樣透傳);2 個健壯性(沒有 web 服務時靜默、畸形配置不拋)。

要求與邊界

插件版本 0.1.0,在 DSH 0.2.0-rc.2 上實測通過,要求 Node ≥ 20(用到 Buffer.toString('base64url') 與 replaceAll)。插件沒有聲明 engines.dsh:DSH 的兼容檢查一旦看到這個鍵就按獨佔判定,而預發佈版低於 0.2.0,寫上反而把當前能用的版本擋在外面。

插件只用 webServer 服務的兩個公開成員:server 與 protocol;不復用未導出的內部符號,cookie 名是自己按 sha256(authority) + base64url 復算的,因爲 BrowserAuth、COOKIE_PREFIX、cookieName 沒有從 @deepseek-ai/dsh-client-connection 導出。

需要注意的邊界:

  1. 只覆蓋 Host 自己的 HTTP 監聽。桌面版主窗口走 dsh-app:// 自定義協議,那條路徑的認證 cookie 由主進程內存變量持有,不寫瀏覽器 cookie 庫,本來也不會堆積。
  2. 清理發生在下次請求時,不是安裝的那一瞬間。裝完第一次打開頁面,插件纔看到並清掉舊 cookie;如果此刻請求頭已超限,走溢出兜底。
  3. 不改 DSH 的端口策略。--port 0 是給遠程訪問留的正規能力,插件只在 cookie 側做減法。

適用場景與注意

適合用 DSH web profile 且頻繁重啓、出現過白屏或 431 的本地開發者;也適合在本地排查請求頭問題的維護者。插件以當前 dsh 進程的權限運行,裝的是倉庫裏的 cordis.patch.yml;安裝前應自己讀一遍源碼,確認許可證是 MIT 再啓用。

手動清理可以解一次急:打開 http://127.0.0.1:<端口>,從地址欄左側的站點信息裏刪除 Cookie 和網站數據,或在開發者工具 Application → Cookies 裏逐個刪掉 dsh-auth-*。代價是下次啓動重新開始堆。

小結

dsh-cookie-janitor 的位置很窄:解決的是「端口變化導致歷史認證 cookie 無限累積、最終撞上請求頭上限」這一個具體問題,做法是在請求進入應用前裁掉舊 cookie 並讓瀏覽器刪除它們,同時對已溢出的請求做 302 兜底。它不改變 DSH 的端口策略,也不碰與 dsh-auth- 無關的 cookie。

目錄頁:https://www.skillhub.cn/plugins/yan3536/dsh-cookie-janitor
源碼:https://github.com/yan3536/dsh-cookie-janitor

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

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

小夜