From ed0c3c0ad5d2168757e13bfd48f0a20537a2a350 Mon Sep 17 00:00:00 2001 From: maogeigei Date: Mon, 14 Sep 2026 23:01:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(proxy):=20=E6=B2=BB=E6=9C=AC=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E9=99=88=E6=97=A7=20dsh-auth=20cookie=20=E5=9B=9E?= =?UTF-8?q?=E5=86=99=E6=B8=85=E7=90=86=EF=BC=88=E6=A1=A3=E6=A1=88=2098?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=20431=20=E2=86=92=20Failed=20to=20load=20plu?= =?UTF-8?q?gins=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因(2026-09-14 实测):dsh **每次实例 (重)启动都换** `dsh-auth-<随机后缀>` 的 **cookie 名**;平台此前 只在**转发给实例时**丢弃旧的(`mergeCookieHeader`),**从不回写浏览器** ⇒ 浏览器 cookie jar **只增不减** ⇒ `Cookie` 请求头越涨越长 ⇒ 实测 **12.8 KB 放行 / 19.1 KB 就被 431**(且 19 KB 时连 `/` 也 431) ⇒ 被 Cloudflare / 本机 nginx 直接拒掉,**请求根本到不了平台** ⇒ 那条 11 MB 合并脚本取不到 ⇒ 客户端报 `bundle script … failed to load` ⇒ 界面「Failed to load plugins」。 (旁证全对:平台日志无该请求 ✓/无痕正常=空 cookie 罐 ✓/服务器侧一切正常 ✓) 修法(只动 proxy.ts 一处):在响应头块里对**陈旧名字**回写 `Set-Cookie: =; Path=/; Max-Age=0`。 · **只在「本次响应确实下发了 dsh-auth-*」时才动手** —— 那时才确知当前有效名字,绝不误删在用的那个; · 其余情形什么都不做;不带 `Domain=`(dsh 下发的是 host-only cookie); · 即使判断有误,档案 51 的「401 → 取新 cookie 透明重放」也会自愈。 验证: · 本机 `npm run verify` EXIT=0;服务器 `ci.sh` CI OK(48 pass / 0 fail); · `scripts/verify-inject.cjs` 新增防回退断言「会清理陈旧 dsh-auth cookie」; · **端到端实测**:带 2 个假 `dsh-auth-*` 请求 `/?token=…` → 303 响应含 **3 条 Set-Cookie,其中 2 条 Max-Age=0** 正是那两个假名字 ✅ ⚠️ 它**救不了已经 431 的当下**(那时请求在 CF/nginx 就被拒、到不了平台)⇒ 用户需**先手动清一次** (只删 `dsh-auth-*`、保留 `sid`),此后由本机制维持小 jar。⚠️ 根因仍在(官方每重启换名)⇒ **少重启**仍是要点。 --- scripts/verify-inject.cjs | 9 +++++++++ src/supervisor/proxy.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/scripts/verify-inject.cjs b/scripts/verify-inject.cjs index 6cfb7c3..9a58493 100644 --- a/scripts/verify-inject.cjs +++ b/scripts/verify-inject.cjs @@ -84,6 +84,15 @@ if (fs.existsSync(PROXY_SRC)) { } else { console.log(' ✓ proxy.ts 有 /plugins/ ETag + 304 短路(省掉每次 11 MB 重下)') } + // 档案 98:陈旧 dsh-auth cookie 必须**回写浏览器删除**(否则 Cookie 头只增不减, + // 涨到 ~15 KB 就被 431 拒在 CF/nginx,请求到不了平台 —— 见档案 98 实测)。 + const hasStale = /const staleNames =/.test(src) && /Max-Age=0/.test(src) && /startsWith\('dsh-auth-'\)/.test(src) + if (!hasStale) { + console.log(' ✗ proxy.ts 缺「陈旧 dsh-auth cookie 回写清理」(Cookie 头会只增不减 ⇒ 431)') + bad++ + } else { + console.log(' ✓ proxy.ts 会清理陈旧 dsh-auth cookie(防 Cookie 头无限增长)') + } } console.log(bad ? '结论:' + bad + ' 项不合格 ❌ —— 别推上线' : '结论:全部合格 ✅') diff --git a/src/supervisor/proxy.ts b/src/supervisor/proxy.ts index 72dc897..f863a7e 100644 --- a/src/supervisor/proxy.ts +++ b/src/supervisor/proxy.ts @@ -376,6 +376,37 @@ function proxyHttp( return } // 重放成功后把新 cookie 交给浏览器:之后(含 SSE 自动重连)不再需要重放。 + // ── 档案 98(治本):把**陈旧的 `dsh-auth-*` cookie 从浏览器里清掉** ────────── + // 由来(2026-09-14 实测):dsh **每次实例 (重)启动都换** `dsh-auth-<随机后缀>` 的 cookie 名 + // (见本文件头部注释)。浏览器把每一个旧名都留着,而平台此前只在**转发给实例时**丢弃 + // 旧的(`mergeCookieHeader`),**从不回写浏览器** ⇒ jar **只增不减** ⇒ `Cookie` 请求头 + // 越涨越长 ⇒ 涨到 ~15 KB(实测 12.8 KB 放行 / 19.1 KB 已被 431)就被 Cloudflare / + // 本机 nginx 直接 **431** 拒掉 —— 请求**根本到不了平台** ⇒ 那条 11 MB 合并脚本取不到 + // ⇒ 客户端报 `bundle script … failed to load`(界面「Failed to load plugins」)。 + // ⚠️ 所以这一块**救不了已经 431 的当下**(那时请求进不来),它的作用是**防复发**: + // 用户手动清一次之后,jar 不会再长回去。 + // 为什么只在「本次响应确实下发了 dsh-auth」时才动手:只有这时我们**确知当前有效的名字**, + // 才能安全清掉其余旧名;否则(例如普通 API 响应不带该 cookie)宁可什么都不做。 + { + const scNow = headers['set-cookie'] + const scList = Array.isArray(scNow) ? scNow : scNow === undefined ? [] : [String(scNow)] + const freshNames = scList + .map((c) => /^\s*(dsh-auth-[^=;\s]*)\s*=/.exec(String(c))?.[1] ?? '') + .filter((n) => n !== '') + if (freshNames.length > 0) { + const staleNames = (request.headers.cookie ?? '') + .split(';') + .map((x) => (x.split('=')[0] ?? '').trim()) + .filter((n) => n.startsWith('dsh-auth-') && !freshNames.includes(n)) + if (staleNames.length > 0) { + headers['set-cookie'] = [ + ...scList, + ...staleNames.map((n) => `${n}=; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT`), + ] + } + } + } + if (authCookie !== undefined && replayCookies.length > 0) { const prev = headers['set-cookie'] headers['set-cookie'] = [