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'] = [