Repository navigation
Replies: 1 comment
|
Your mechanism analysis is accurate for the release you measured, but the fix is already in the current release line — the packaged rc.1 build predates it. Verified against the rc.3 source:
|
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
中文版
交叉引用:原报告 #9924(@Mooling0602,至今未修)——我上月在该帖验证了同一溢出同样会打断账号 WebSocket 通道(评论链接)。同类报告:#11160、#11379、#11489、#11758、#11759、#12186、#12377、#12502、#12698、#12995。
1. 问题推测,及对官方迟迟不修复的推测
机制(在 0.2.2-rc.1 上复核,实测数据见第 2 节):桌面端每次启动都会向 Electron 的 Cookie 库写入一条新的
dsh-auth-<随机串>(域127.0.0.1)。Cookie 名与其签名载荷都绑定当次启动的随机回环端口,因此名字全局唯一、永不互相覆盖,每次启动给Cookie请求头增加约 230 B。约 70 次启动后,请求头越过 Host 的 16 KiBmaxHeaderSize上限 → 欢迎请求被 431 拒绝 →connectDesktopWelcome抛出desktop welcome: Web authentication failed。以下六点解释它为何能跨版本存活。这一节是推测,证据已内联:
开发模式复现不出来。 dev 走固定端口 → authority 恒定 → Cookie 同名 → 每次启动互相覆盖,永不累积;只有打包版的随机端口才堆叠。(依据
lib/main.js中的developmentHostInspectPort(development)分支推断,未实测 dev 行为。)这个 bug 自带半衰期。 凭据有效期实测全部恰好 30 天,库的稳态规模收敛于 启动频率 × 30 天——按每天 2–3 次启动,正好压在 16 KiB 悬崖线上。停用一个月,库里旧凭据自动过期,"自愈"→ 复现失败 → 工单关闭。
报错名目错位。 自家 Host 回的 431 被包装成"认证失败",triage 稳定落在认证模块,而认证代码是对的;且失败发生在渲染器起来之前,crash log 里
no error-level renderer console output——诊断信号为零。一行能修,但要先证明一个否定。
WITHHELD_RESPONSE_HEADERS注释明写set-cookie要被摘掉、凭据"由壳持有";可 welcome 握手用的net.fetch(url, { credentials: "include" })恰恰把它写进了 jar。删掉这个参数得先证明 jar 对其它链路(service worker、账号 WebSocket、上传)不是承重墙——安全敏感路径上,这种改动最容易被搁置。责任落在无主接缝,且存在多实例竞态。 Host 铸币、壳消费、Chromium 持 jar,没有任何一方拥有"回收"这件事;而粗暴清扫会误删并发运行的第二个实例的有效凭据(同一 userData → 同一 jar),连"显而易见的修复"都需要先做安全设计。
排序算术 + 测试盲区。 打包桌面端是小众入口;故障频率是"每约 70 次启动一次";没有任何 CI 测试会连续启动 71 次应用,因此没有回归测试持续逼着修。
预测:不会有补丁——真正的修法是重做回环凭据下发(一次性 token 或固定端口),要同时改 Host、壳和 preload。在那之前,用户侧自行清扫是唯一缓解手段。
2. 问题复现(本机实测)
官方 Windows 打包版,0.2.2-rc.1,Electron 44.7.0 / Node 24.21.0。启动即弹错误框,
crash-2026-11-13T07-30-04-952Z-host.log内为熟悉的connectDesktopWelcome栈。清理前对 Cookie 库的实测:
| 测量项 | 数值 |
|---|---|
|
dsh-auth-*条数 | 71,全部host_key = 127.0.0.1、is_httponly|| 载荷解码出的端口 | 71 / 71 全不重复——零复用 |
| 载荷有效期(
expiresAt − issuedAt) | 2,592,000,000 ms = 30 天,71 条全部如此 || 签发窗口 | 24.6 天内 71 次启动(≈ 2.89 次/天) |
|
Cookie请求头合计 | 16,188 B,上限 16,384 B(差 196 B ≈ 一条 Cookie) |恢复验证:关闭桌面端后,先备份再删除全部
dsh-auth-*行,下一次启动即恢复正常。3. 我们的解决插件(临时方案)
https://github.com/Rad-Weed/dsh-auth-cookie-janitor(MIT、零依赖、无客户端注入)
宿主每次启动时测量 Cookie 头占用;低于 60% 静默
达到 80%:若桌面壳未持有数据库(如在终端运行
dsh)→ 备份 + 删除 + VACUUM;若壳正在运行 → 只警告不写入——扫地工绝不能弄坏启动附带独立 Python 脚本,可挂计划任务(14 天自判,动正在运行的壳之前先询问)
这是临时方案,不是修复:如第 1 节及我此前在 [Bug] Desktop "Web authentication failed": stale dsh-auth-* cookies overflow the Host header limit (431) #9924 的评论所述,
connectDesktopWelcome与账号 WebSocket 的cookies()两处都要上游收窄;正式修复落地之日,就是本插件退役之时溯源 / 参与说明
产出方式:本帖由 AI 助手——WorkBuddy(模型 GLM-5.3,2026-11-13 会话)——在受影响机器上、于账号持有人指导与审核下协助完成。助手从
app.asar中抽取了打补丁版与原版两份lib/main.js做比对、解码 71 条 Cookie 载荷、执行备份 + 清理 + 完整性校验并起草本文;账号持有人审核了全部发布内容。实测 vs 推断:71 条 Cookie、71 个不同端口、30 天有效期、16,188 B 请求头、crash log 与恢复结果均为本机当日实测;第 1 节中除机制外的全部内容(六条原因与预测)属代码阅读推断,不掌握任何内部信息,请按推测对待。
未包含:无任何 Cookie 值、令牌、凭据或账号标识。71 条 Cookie 取自本机 Cookie 库备份,正文未复制任何值。
建议权重:单机、脚本辅助的分析;复现数据扎实,"为何不修"一节是结构化推测。
English version
Cross-references: the original report #9924 (by @Mooling0602, still open) — I verified there last month that the same overflow also breaks the account WebSocket channel (comment). Related reports: #11160, #11379, #11489, #11758, #11759, #12186, #12377, #12502, #12698, #12995.
1. The problem, and speculation on why it stays unfixed
Mechanism (re-confirmed on 0.2.2-rc.1; measurements in §2): every desktop launch stores a new
dsh-auth-<random>cookie for127.0.0.1in the Electron jar. The cookie name and its signed payload bind to that launch's random loopback port, so names are globally unique and never overwrite each other (~230 B of request header per launch). Around 70 launches later theCookieheader crosses the host's 16 KiBmaxHeaderSize→ the welcome request is rejected with 431 →connectDesktopWelcomethrowsdesktop welcome: Web authentication failed.Why it survives release after release — this section is speculation, evidence inline:
Dev mode does not reproduce it. Dev runs a fixed port → constant authority → same cookie name → each launch overwrites the previous one; only packaged builds accumulate. (Inferred from the
developmentHostInspectPort(development)branch inlib/main.js; dev behavior not measured.)The bug has a half-life. Measured token lifetime is exactly 30 days for all tokens, so the jar converges to launch rate × 30 days — at 2–3 launches/day that lands right at the 16 KiB cliff. Stop launching for a month and the jar "heals" by expiry → reproductions rot → tickets close as cannot-reproduce.
The error name points at the wrong subsystem. A 431 from our own host is reported as an authentication failure; triage keeps landing on auth code, which is correct. The failure is fatal-before-render, so crash logs carry
no error-level renderer console output— zero diagnostic signal.A one-line fix that requires proving a negative.
WITHHELD_RESPONSE_HEADERSstripsset-cookiefrom the renderer because the cookie is one "the shell owns instead" (comment inlib/main.js) — yet the welcome handshake usesnet.fetch(url, { credentials: "include" }), which persists it into the jar. Removing that flag requires proving the jar is not load-bearing for other paths (service workers, the account WebSocket, uploads) — on a security-sensitive route, exactly the kind of change that stalls.An orphaned seam, plus a multi-instance race. The Host mints the cookie, the shell consumes it, Chromium holds the jar — nobody owns its lifecycle. A naive sweep would delete the valid cookie of a concurrently running second instance (same userData → same jar), so even the obvious cleanup needs a safety design first.
Triage math and a test blind spot. The packaged desktop is a side entrance; the failure is once per ~70 launches; no CI test launches the app 71 times, so nothing forces the fix.
Prediction: no patch — the real fix is a redesign of loopback credential delivery (one-shot token, or fixed port) touching Host, shell and preload together. Until then, user-side sweeping is the only mitigation.
2. Reproduction (measured on the affected machine)
Official Windows build, 0.2.2-rc.1, Electron 44.7.0 / Node 24.21.0. Startup dies with the familiar dialog;
crash-2026-11-13T07-30-04-952Z-host.logcontains theconnectDesktopWelcomestack.The jar, measured before cleanup:
| measurement | value |
|---|---|
|
dsh-auth-*cookies | 71, allhost_key = 127.0.0.1,is_httponly|| distinct ports in decoded payloads | 71 / 71 — zero reuse |
| payload lifetime (
expiresAt − issuedAt) | 2,592,000,000 ms = 30 days, all 71 || issuance window | 71 launches in 24.6 days (≈ 2.89/day) |
|
Cookieheader total | 16,188 B vs the 16,384 B limit (196 B ≈ one cookie short of passing) |Recovery verified: with the desktop app closed, backing up and deleting the
dsh-auth-*rows makes the next launch boot normally.3. Our workaround plugin (temporary)
https://github.com/Rad-Weed/dsh-auth-cookie-janitor (MIT, zero dependencies, no client injection)
measures the jar at every host start; silent below 60% of the limit
at 80%: if the desktop shell is not holding the database (e.g.
dshstarted from a terminal), it backs up, deletes the stale rows and VACUUMs; if the shell is running it warns instead of writing — a janitor must never break startupships a standalone Python script for scheduled cleanup (14-day self-gating, asks before touching a running shell)
this is a temporary workaround, not a fix: as argued in §1 and in my earlier [Bug] Desktop "Web authentication failed": stale dsh-auth-* cookies overflow the Host header limit (431) #9924 comment, both call sites (
connectDesktopWelcomeand the account WebSocket'scookies()) need narrowing upstream; the plugin retires when that landsProvenance
How it was produced. Prepared with an AI assistant — WorkBuddy, model GLM-5.3, 2026-11-13 session — on the affected machine, under the direction and review of the account holder. The assistant extracted both patched and stock
lib/main.jsfromapp.asar, decoded the 71 cookie payloads, performed the backup + cleanup + integrity check, and drafted this text; the human operator approved what is published.Measured vs. inferred. The 71-cookie jar, 71 distinct ports, 30-day lifetimes, the 16,188 B header, the crash log line and the successful recovery were measured on this machine today. Everything in §1 beyond the mechanism (the six reasons, the prediction) is inference from code reading, not insider knowledge.
Not included. No cookie values, tokens, credentials or account identifiers. The 71 cookies came from a local backup of this machine's own jar; none are reproduced in this text.
Suggested weight. Single-machine, script-assisted analysis; the reproduction numbers are solid, the "why unfixed" section is structured speculation.
All reactions