前言¶
DSH 的理念是「一切皆插件」,插件以当前 dsh 进程的权限运行,能改 host 里的东西。对桌面版用户来说,比较磨人的一类故障是:使用过程中重启若干次后,DSH 必然起不来,弹窗报 desktop welcome: Web authentication failed,并建议重装或禁用插件。手动删掉 Cookie 库能救回来,但重启几十次又会出现同样的问题。soyorinhhh/dsh-cookie-doctor 就是针对这个故障的插件:清理堆积的 dsh-auth-* cookie,并从根上阻止它们再次堆积。它由 soyorinhhh 维护,分类是 admin-security,GitHub 上 1 个 star。
下面按「是什么、为什么有用、怎么装、怎么用、需要注意什么」介绍。
这是什么¶
一句话定位:DSH 桌面版「Web authentication failed」崩溃治理插件。它的目标不是帮你绕过问题,而是把根因找出来并止住。
| 项 | 内容 |
|---|---|
| 插件名称 | dsh-cookie-doctor |
| 维护者 | soyorinhhh |
| 分类 | admin-security |
| 版本 | 0.1.0(见 package.json) |
| 许可证 | MIT |
| 运行要求 | Node >= 22.13.0 |
| 源码 | https://github.com/soyorinhhh/dsh-cookie-doctor |
| 目录页 | https://www.skillhub.cn/plugins/soyorinhhh/dsh-cookie-doctor |
插件挂载在 host 进程里,接管 dsh-host-webserver 创建的 http.Server。它不改 DSH 的鉴权逻辑,也不改端口、不碰 host 头;cookie 名照旧按 host:port 哈希,插件只是把这个 session 用不到的历史 cookie 一条条作废。
问题根因¶
sessions cookie 的名字里带了端口,而 cookie 本身不按端口隔离。@deepseek-ai/dsh-client-connection 里是这么算 cookie 名的:
const COOKIE_PREFIX = "dsh-auth-";
/** Canonical request authority used as the cookie name and signed audience. */
function cookieName(authority) {
return COOKIE_PREFIX + encodeBase64Url(createHash("sha256").update(authority).digest());
}
而 authority 来自请求头里的 host:
function requestAuthority(headers) {
const host = header(headers, "host"); // "127.0.0.1:54321"
...
return new URL(`http://${host}`).host;
}
桌面版每次启动都随机换端口,于是:
- 每次启动都算出一个新的 cookie 名(
dsh-auth-+ host:端口 的哈希); - Chromium 存 cookie 不看端口,这些 cookie 全都堆在
127.0.0.1这同一个域下; - shell 的 welcome backend 会把库里该域下的所有 cookie 拼成一个
Cookie头发出去:
async () => (await session.defaultSession.cookies.get({ url: ready.url }))
.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ")
- 每条这种 cookie 大约 220~260 字节,攒到 70 条出头就顶到 Node 默认的
maxHeaderSize(16384 字节); - Node 的 HTTP 解析器在进入业务代码之前就直接返回 431,Electron 那边看到的就是认证失败。
README 给出的实测(test/simulate.mjs 场景 A)是:第 73 次启动时请求头 16423 字节,返回 431。README 还提到,截至 0.2.1-rc.3 官方仍未修复。
插件做了什么¶
三个动作:
| 动作 | 作用 |
|---|---|
server.maxHeaderSize = 256KB |
把天花板抬到约 1000 条 cookie,已经攒了 70+ 条的机器也能立刻启动 |
每条请求都算一遍「本轮该有的 cookie 名」,把其它 dsh-auth-* 用 Set-Cookie: …=; Max-Age=0 过期掉 |
Chromium 自己清理,库里永远只剩 1 条,不再堆积 |
统计写进 ~/.dsh/cookie-doctor/report.json |
能直接看到到底有没有生效 |
过期指令走的是同一条 Chromium 网络栈,所以和当初写下它们的通道完全一致。README 里的实测结果:
| 场景 | 结果 |
|---|---|
| A. 不装插件 | 第 73 次启动 431 崩溃 |
| B. 装插件,从零开始 90 次启动 | Cookie 头恒为 223 字节 / 1 条 |
| C. 装插件,机器已攒 76 条(请求头 17098 字节,已超默认上限) | 不崩,第一次请求就自愈到 1 条 |
另外,README 记录了这个插件在真实 DSH host 里跑过:dsh --profile ctest --port 18099 起来后,带 20000 字节 Cookie 头发请求,返回 401 而不是 431,响应里带上了过期指令。
插件对 apply() 做了整体 try/catch:即使初始化异常也只会打一行警告,不会让 host 启动失败。
安装¶
README 里的安装方式不是 dsh plugin add,而是仓库自带的安装脚本:
node scripts/install.mjs # 装进 ~/.dsh/profiles/desktop
node scripts/install.mjs --dry-run # 先看它要干什么
脚本做的事(不需要联网、不需要 pnpm):
~/.dsh/plugins/dsh-cookie-doctor→ 指向本仓库(Windows 用 junction,不用管理员权限)<profile>/node_modules/dsh-cookie-doctor→ 指向上面的目录,让 Node 能解析到- 在 profile 的
package.json里加上dependencies和dsh.profile.bundles(改之前自动备份)
装完重启 DeepSeek Harness。控制台会打印一行:
[cookie-doctor] 已接管 host HTTP 通道:maxHeaderSize 16384 → 262144,过期策略 开启
卸载:
node scripts/install.mjs --uninstall
按 README 的说法,插件的 bundle patch 路径是 ./cordis.patch.yml,配置项写在这个文件(或 profile 层覆写)里。
已经起不来了怎么办¶
插件只能在 host 起来之后才跑;当请求头已经超过 16KB 时 host 根本收不到请求,所以已经病发的机器得先在外面清一次:
# 1. 完全退出 DeepSeek Harness(连托盘一起退)
node scripts/diagnose.mjs --dry-run # 先体检:会看到 dsh-auth-* 有几十条
node scripts/repair.mjs --dry-run # 看会删多少
node scripts/repair.mjs # 真删,自动备份原库
# 2. 重新启动 DSH
repair.mjs 只删 dsh-auth-* 那些行,别的 cookie 一条不动。删之前会自动生成 Cookies.bak-<时间戳>。
如果连 DSH 都启动不了、只想手动来,README 给出的等价做法是删掉:
%APPDATA%\@deepseek-ai\dsh-desktop\Network\Cookies
以及同目录的 Cookies-journal。效果等价,但会连别的 cookie 一起清掉。
体检脚本¶
node scripts/diagnose.mjs # 看当前堆了多少、会不会炸
node scripts/diagnose.mjs --json # 机器可读
README 给出的输出示例:
cookie 库:C:\Users\me\AppData\Roaming\@deepseek-ai\dsh-desktop\Network\Cookies
cookie 总数:80(其中 dsh-auth-* 80 条,其它 0 条)
127.0.0.1 → 80 条,拼成请求头约 20400 字节
dsh-auth-Ab3x…
…
🔴 已经炸了:请求头超过 16KB,启动必定报 Web authentication failed
Node 默认请求头上限 16384 字节,当前最坏 20400 字节
需要注意:DSH 运行中 cookie 库被 Chromium 独占锁定,体检和修复都要先完全退出 DSH。只想看一眼的话,可以先复制一份库文件,再用 --db <副本> 分析。
配置¶
改 cordis.patch.yml 里的 config(或 profile 层覆写):
| 键 | 默认 | 说明 |
|---|---|---|
maxHeaderBytes |
262144 |
抬到多少。0 = 不动默认值(不推荐,默认值就是那个坑) |
purgeStale |
true |
是否过期掉历史 cookie |
maxPurgePerResponse |
200 |
一条响应最多带多少条过期指令 |
warnAuthCookies |
8 |
超过这么多条就警告一次 |
reportPath |
$DSH_HOME/cookie-doctor/report.json |
统计落盘位置 |
quiet |
false |
关掉日志 |
测试¶
README 提供了两个可以自己跑的测试:
node test/simulate.mjs # 复现故障 + 验证修复(含病发机器自愈)
node test/repair.test.mjs # 体检/修复脚本,用 Chromium 同结构的 cookie 库跑
test/simulate.mjs 的实测输出:
### A. 不装插件(复现线上故障)
首次:请求头 223 字节,库中 1 条
末次:请求头 16423 字节,库中 73 条
❌ 第 73 次启动失败:auth=303 welcome=431
### B. 装插件(从零开始)
峰值请求头 223 字节 / 库中 1 条,90 次启动全程正常
### C. 装插件 + 已攒 76 条(病发机器)
首次:请求头 17098 字节 → 自愈到 1 条,全程正常
几种取舍¶
- 抬高
maxHeaderSize有没有副作用? README 的解释是:host 只监听回环地址,抬高上限只是让「头部异常大」的请求不至于被解析器直接掐掉;真正的访问控制仍然由/api的 loopback + Origin 围栏负责。不想要可以设maxHeaderBytes: 0。 - 为什么不干脆把 cookie 名改成不带端口? 那样
authority就失去了端口绑定(isAuthenticated会校验payload.authority === authority),等于放宽鉴权语义;而且改的是 DSH 自己的代码,插件做不到、也不该越界。所以插件选择「保留语义,只清理垃圾」。
适用场景与注意¶
适合:
- 用 DSH 桌面版,遇到过重启后报
desktop welcome: Web authentication failed,手动删 Cookie 库能救但会复发的人。 - 想先看清楚自己机器上
dsh-auth-*堆了多少条再决定要不要改配置的人。 - 不想重装 DSH、不想禁用插件的人。
需要注意:
- 插件以当前 dsh 进程的权限运行,并在 host 进程里接管 HTTP server。安装前应检查源码与许可证(MIT)。
- 插件是第三方个人维护的,README 里明确写了「100% vibe coding,来源自 DeepSeek V4.1 Flash Harness 端」。要在自己的环境里验证一下,别直接当作官方修复。
- 病发的机器需要先在外面执行
repair.mjs,插件本身只能阻止后续堆积。 - DSH 运行期间 cookie 库被锁定,体检和修复前必须完全退出 DSH(包括托盘)。
小结¶
这个插件的价值在于把一个只能靠删文件解决的偶发崩溃,变成两步可复现的操作:先 diagnose.mjs 看清当前堆了多少,再装插件(或先 repair.mjs 清一次)阻止复发。它不碰鉴权语义,所以风险面比较明确。
- 目录页:https://www.skillhub.cn/plugins/soyorinhhh/dsh-cookie-doctor
- 源码:https://github.com/soyorinhhh/dsh-cookie-doctor
注:DSH 社区目录是独立站点,与 DeepSeek / 幻方无官方从属关系。