dsh-cookie-doctor: Clean up accumulated dsh-auth-cookies on DSH Desktop Edition to prevent recurring Web authentication failures

Foreword

The philosophy of DSH is “everything is a plugin”; plugins run with the current dsh process’s permissions and can modify things inside the host. For desktop users, a particularly annoying type of failure is: after restarting a few times during use, DSH will inevitably fail to start, popping up a window saying desktop welcome: Web authentication failed, suggesting reinstalling or disabling plugins. Manually deleting the cookie database can save it, but restarting dozens of times will bring the same problem back. soyorinhhh/dsh-cookie-doctor is the plugin targeting this failure: it cleans up accumulated dsh-auth-* cookies and prevents them from accumulating again at the root. It is maintained by soyorinhhh, categorized as admin-security, and has 1 star on GitHub.

The following introduces it in the order of “What it is, Why it’s useful, How to install, How to use, and What to watch out for.”

What is it

One-sentence positioning: DSH Desktop “Web authentication failed” crash governance plugin. Its goal is not to help you bypass the problem, but to find the root cause and stop it.

Item Content
Plugin Name dsh-cookie-doctor
Maintainer soyorinhhh
Category admin-security
Version 0.1.0 (see package.json)
License MIT
Requirements Node >= 22.13.0
Source https://github.com/soyorinhhh/dsh-cookie-doctor
Directory Page https://www.skillhub.cn/plugins/soyorinhhh/dsh-cookie-doctor

The plugin mounts into the host process and takes over the http.Server created by dsh-host-webserver. It does not modify DSH’s authentication logic, nor does it change ports or touch the host header; cookie names are hashed as usual based on host:port, the plugin simply invalidates historical cookies that the session doesn’t use one by one.

Root Cause

The name of the sessions cookie includes the port, but cookies themselves are not isolated by port. In @deepseek-ai/dsh-client-connection, this is how cookie names are calculated:

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());
}

And authority comes from host in the request header:

function requestAuthority(headers) {
    const host = header(headers, "host");          // "127.0.0.1:54321"
    ...
    return new URL(`http://${host}`).host;
}

The desktop version changes ports randomly every time it starts, so:

  1. A new cookie name is calculated every time it starts (dsh-auth- + hash of host:port);
  2. Chromium stores cookies without looking at the port, so all these cookies are piled up under the same domain 127.0.0.1;
  3. The shell’s welcome backend will concatenate all cookies under that domain in the database into a Cookie header to send out:
   async () => (await session.defaultSession.cookies.get({ url: ready.url }))
       .map((cookie) => `${cookie.name}=${cookie.value}`).join("; ")
  1. Each such cookie is about 220~260 bytes; when accumulated to over 70, it hits Node’s default maxHeaderSize (16384 bytes);
  2. Node’s HTTP parser returns 431 before entering business code, which Electron interprets as an authentication failure.

The practical test given in the README (Scenario A in test/simulate.mjs) is: on the 73rd startup, the request header is 16423 bytes and returns 431. The README also mentions that as of 0.2.1-rc.3, the official version has not yet fixed it.

What the Plugin Does

Three actions:

Action Effect
server.maxHeaderSize = 256KB Lifts the ceiling to about 1000 cookies, allowing machines that have already accumulated 70+ to start immediately
Every request calculates the “cookie names that should exist in this round” and invalidates “other” dsh-auth-* cookies with Set-Cookie: …=; Max-Age=0 Chromium cleans this up, so only 1 remains in the database, no longer accumulating
Statistics are written to ~/.dsh/cookie-doctor/report.json You can directly see if it’s working

The expiration command goes through the same Chromium network stack, so the channel used to write them is completely consistent with the original. Test results from the README:

Scenario Result
A. No plugin installed 431 crash on the 73rd startup
B. Plugin installed, starting from scratch for 90 startups Cookie header is constantly 223 bytes / 1 cookie
C. Plugin installed, machine already has 76 cookies (request header 17098 bytes, exceeding default limit) No crash, self-heals to 1 cookie on the first request

Additionally, the README records that this plugin has run on a real DSH host: after dsh --profile ctest --port 18099 started, sending requests with a 20000-byte Cookie header returned 401 instead of 431, and the response included expiration directives.

The plugin wraps the whole apply() in a try/catch: even if initialization fails, it only logs a warning and won’t cause the host startup to fail.

Installation

The installation method in the README is not dsh plugin add, but the installation script provided by the repository:

node scripts/install.mjs            # Install to ~/.dsh/profiles/desktop
node scripts/install.mjs --dry-run  # See what it will do first

What the script does (no internet connection, no pnpm required):

  1. ~/.dsh/plugins/dsh-cookie-doctor → points to this repository (Windows uses junction, no admin privileges needed)
  2. <profile>/node_modules/dsh-cookie-doctor → points to the directory above, allowing Node to resolve to it
  3. Adds dependencies and dsh.profile.bundles to the profile’s package.json (automatically backed up before modification)

After installing, restart DeepSeek Harness. The console will print a line:

[cookie-doctor] 已接管 host HTTP 通道:maxHeaderSize 16384 → 262144,过期策略 开启

Uninstall:

node scripts/install.mjs --uninstall

According to the README, the plugin’s bundle patch path is ./cordis.patch.yml, and configuration items are written in this file (or overridden at the profile layer).

What to do if it won’t start

The plugin can only run after the host starts; when the request header has already exceeded 16KB, the host can’t receive the request at all, so machines that have already fallen ill need to clear it once externally:

# 1. Completely exit DeepSeek Harness (including the tray)
node scripts/diagnose.mjs --dry-run   # First checkup: you'll see dozens of dsh-auth-* cookies
node scripts/repair.mjs --dry-run     # See how many will be deleted
node scripts/repair.mjs               # Actually delete, auto-backup original database
# 2. Restart DSH

repair.mjs only deletes the dsh-auth-* lines, leaving other cookies untouched. Before deleting, it automatically generates Cookies.bak-<timestamp>.

If DSH won’t start at all and you just want to do it manually, the equivalent method given in the README is to delete:

%APPDATA%\@deepseek-ai\dsh-desktop\Network\Cookies

And Cookies-journal in the same directory. The effect is equivalent, but it will also clear other cookies.

Diagnostic Script

node scripts/diagnose.mjs          # See how much is piled up currently, will it explode?
node scripts/diagnose.mjs --json   # Machine-readable

Example output from the 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 字节

Note: While DSH is running, the cookie database is exclusively locked by Chromium. Both diagnosis and repair require completely exiting DSH first. If you just want to take a look, you can first copy the database file and then analyze it with --db <copy>.

Configuration

Modify config in cordis.patch.yml (or override at the profile layer):

Key Default Description
maxHeaderBytes 262144 Lift to how much. 0 = don’t touch default value (not recommended, default value is the trap)
purgeStale true Whether to expire historical cookies
maxPurgePerResponse 200 Max number of expiration directives in one response
warnAuthCookies 8 Warn once if exceeded this many
reportPath $DSH_HOME/cookie-doctor/report.json Statistics disk location
quiet false Turn off logging

Testing

The README provides two tests you can run yourself:

node test/simulate.mjs      # Reproduce the failure + verify the fix (including self-healing of sick machines)
node test/repair.test.mjs   # Diagnostic/repair scripts, running with Chromium structurally identical cookie database

Actual output from test/simulate.mjs:

### A. No plugin installed (Reproduce online failure)
  First time: Request header 223 bytes, 1 cookie in database
  Last time: Request header 16423 bytes, 73 cookies in database
  ❌ 73rd startup failed: auth=303 welcome=431

### B. Plugin installed (Starting from scratch)
  Peak request header 223 bytes / 1 cookie in database, normal throughout 90 startups

### C. Plugin installed + Already has 76 cookies (Sick machine)
  First time: Request header 17098 bytes → self-heals to 1 cookie, normal throughout

Several Trade-offs

  • Does lifting maxHeaderSize have side effects? The README’s explanation is: the host only listens to the loopback address, lifting the limit just prevents “abnormally large headers” from being cut off by the parser; true access control is still handled by /api loopback + Origin fences. If you don’t want this, set maxHeaderBytes: 0.
  • Why not simply change the cookie name to not include the port? Doing so would lose port binding for authority (isAuthenticated checks payload.authority === authority), effectively loosening the authentication semantics; also, this changes DSH’s own code, which the plugin cannot do or should not overstep. So the plugin chooses to “preserve semantics, only clean up garbage.”

Applicable Scenarios and Notes

Suitable for:

  • Users who have encountered desktop welcome: Web authentication failed after restarting on DSH Desktop and who can be saved by manually deleting the Cookie database but have it recur.
  • People who want to see exactly how many dsh-auth-* cookies have piled up on their machine before deciding whether to change configuration.
  • People who don’t want to reinstall DSH or disable the plugin.

Notes:

  • The plugin runs with the current dsh process permissions and takes over the HTTP server in the host process. Check the source code and license (MIT) before installing.
  • The plugin is maintained by a third-party individual; the README explicitly states “100% vibe coding, source from DeepSeek V4.1 Flash Harness”. Verify it in your own environment; don’t treat it as an official fix directly.
  • Sick machines need to execute repair.mjs externally first; the plugin itself can only prevent subsequent accumulation.
  • The cookie database is locked while DSH is running; diagnosis and repair must completely exit DSH (including the tray) before proceeding.

Summary

The value of this plugin lies in turning an occasional crash that could only be solved by deleting files into a two-step reproducible operation: first diagnose.mjs to see how much is piled up, then install the plugin (or first repair.mjs to clear once) to prevent recurrence. It doesn’t touch authentication semantics, so the risk surface is clear.

Note: The DSH community directory is an independent site and has no official affiliation with DeepSeek / Fenxi.

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

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

Xiaoye