🐛 修复 GM_xmlhttpRequest 自定义 cookie 追加而非覆盖同名 cookie#1604
Open
cyfung1031 wants to merge 7 commits into
Open
Conversation
TM兼容: 脚本传入的 cookie 参数(非 anonymous)此前会与浏览器已存储的同名 cookie 直接拼接发送(如 data=1; data=2;),与 Tampermonkey 的覆盖语义不一致 (Tampermonkey/tampermonkey#2754)。改为按 cookie 名称合并:脚本自定义值覆盖 同名的已有 cookie,不同名的 cookie 全部保留,避免重蹈 TM 修复该问题时引入的 多 cookie 截断回归 (Tampermonkey/tampermonkey#2829)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cookie 名称在规范上允许因 domain/path 不同而以多值形式共存(例如同一请求 URL 同时匹配到两个不同 scope、但同名的已有 cookie)。此前的覆盖逻辑只要 脚本自定义了该名称就会丢弃全部已有同名值,会把这种合法的多值场景错误地 压缩成单一值。 改为:仅当已有同名 cookie 唯一存在一个时才视为可被脚本自定义值覆盖; 存在多个同名值时无法判断脚本想覆盖哪一个,改为全部保留、脚本自定义值 附加在前面,不丢弃任何一个已有值。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
改回更简单直接的规则:某个 cookie 名称只要被脚本的 cookie 参数指定, 就完全覆盖浏览器该名称下的全部已有值(无论原本是 1 个还是多个); 未被指定的名称则完全保留原样(无论是 0 个、1 个还是多个)。脚本指定 的值本身也可以是同名多值,原样保留、不做去重。 上一版按「已有同名 cookie 是否恰好唯一一个」区分覆盖/附加的做法过于 复杂,且与「脚本明确指定即完全接管该名称」的直觉不符。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
针对单一 cookie 名称,脚本指定状态(未指定/单一值/多个值)与浏览器已有 状态(无/单个/多个)各有 3 种,共 3×3=9 种组合,逐一断言覆盖/保留行为 符合预期规则,避免遗漏边界组合。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Collaborator
Author
|
manually check with the following scripts: // ==UserScript==
// @name GM_xmlhttpRequest cookie test - tampermonkey/tampermonkey#2754
// @namespace http://tampermonkey.net/
// @version 2026-04-15
// @description try to take over the world!
// @author AA
// @match https://httpbin.org/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
document.cookie = `data=1; max-age=86400;`;
document.body.onclick = () => {
GM_xmlhttpRequest({
url: 'https://httpbin.org/headers',
cookie: 'data=2',
responseType: 'json',
onload({ response }) {
const result = `${response?.headers?.Cookie}`;
console.log({ result, ok: result.includes("data=2") && !result.replace("data=2", "").match(/data=/) });
}
});
}and // ==UserScript==
// @name GM_xmlhttpRequest cookie test - tampermonkey/tampermonkey#2829
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description Test with httpbin
// @author AA
// @match https://httpbin.org/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
GM_xmlhttpRequest({
url: 'https://httpbin.org/headers',
cookie: 'data1=1; data2=2',
// anonymous: true,
responseType: 'json',
onload({ response }) {
const result = `${response?.headers?.Cookie}`;
console.log({ result, ok: result.includes("data1=1; data2=2") && !result.replace("data1=", "").replace("data2=", "").match(/data[12]=/) });
},
});
}()); |
沿用 example/tests/inject_content_test.js 的自动执行 + console.log 汇总 (总计/通过/失败)风格,支持机制化检查(docs/verification.md 描述的 in-page self-test pattern)。 覆盖: - Tampermonkey/tampermonkey#2754 的字面复现(data=1 被 cookie: 'data=2' 完全覆盖,而非拼接成 data=1; data=2) - Tampermonkey/tampermonkey#2829 的字面复现(cookie: 'data1=1; data2=2' 两个不同名 cookie 都应送出,不应被截断成只剩 data1) - 浏览器已有状态(无/单值/同名多值)× 脚本指定状态(未指定/单值/多值) 的完整 3x3 矩阵,通过 document.cookie 以不同 path 属性制造同名多值 cookie 来验证,并对照 gm_api.test.ts 中的 mergeCookieHeader 单元测试 Testing source references: scriptscat#1604 (comment) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist / 检查清单
cookieoverwrite semantics / 已修复 GM_xmlhttpRequest 自定义 cookie 覆盖语义背景
Tampermonkey tampermonkey/tampermonkey#2754 指出:
GM_xmlhttpRequest的cookie参数应当覆盖同名的已有 cookie,而不是与其拼接发送。ScriptCat 存在同样的问题:在非
anonymous模式下,src/app/service/service_worker/gm_api/gm_api.ts的buildDNRRule会先把脚本自定义的cookie字符串写入headers["cookie"],再无条件地把chrome.cookies.getAll()取到的浏览器已存储 cookie 追加在后面,即使名称相同也不会覆盖,导致请求头出现data=1; data=2这样的重复 cookie 名。Tampermonkey 后续为修复 #2754 引入了新的回归 tampermonkey/tampermonkey#2829:多个 cookie 被截断为只剩第一个。该回归目前 TM 尚未修复,但 ScriptCat 的实现不应重蹈覆辙。
另外,cookie 名称在规范上允许因 domain/path 不同而以多值形式共存于同一请求 URL(例如子网域与父网域各自设置了同名 cookie,两者都匹配同一个请求)。
本次改动
mergeCookieHeader(customCookie, storedCookies)(src/app/service/service_worker/gm_api/gm_api.ts),按 cookie 名称合并浏览器已有 cookie 与脚本自定义的cookie参数:buildDNRRule的非anonymous分支改为调用mergeCookieHeader,替换原本的字符串拼接逻辑。anonymous分支(使用 DNRmodifyHeaders直接set/remove整个 Cookie 头,不查询浏览器已有 cookie)未受影响,本身已符合覆盖语义。example/tests/gm_xhr_cookie_test.js:沿用inject_content_test.js的自动执行 + console.log 汇总风格(支持机制化检查),在真实浏览器中用document.cookie与真实GM_xmlhttpRequest复现 TM #2754 / #2829 的字面场景,并对照同一份 3×3 矩阵。实现考虑
cookie参数中」,不区分浏览器该名称下原本有几个值——只要脚本指定了,就是完全接管;没指定,就完全不动。这比按「已有同名 cookie 数量」区分覆盖/附加的做法更符合直觉,也更简单。chrome.cookiesmock 直接做单元测试。example/tests/gm_xhr_cookie_test.js借由同名 cookie 设置不同path(/与/headers)在同一浏览器内制造合法的「同名多值」cookie,两者都会匹配到https://httpbun.com/headers请求,藉此在真实 Chrome cookie jar 下验证矩阵中「浏览器已有 2 个同名值」的分支。已知限制
GM_xmlhttpRequest(buildDNRRule)路径;GM_cookie相关 API 不受影响。example/tests/gm_xhr_cookie_test.js是手动验证脚本(依docs/verification.md的 in-page self-test 规范撰写),不是 CI 中运行的 Playwright E2E 用例。建议审查重点
mergeCookieHeader对空值 / 空字符串 / 无浏览器 cookie / 同名多值(脚本端与浏览器端)等边界情况的处理,参见新增的 3×3 组合测试。buildDNRRule中该分支对headers["cookie"]的赋值时机是否影响后续的 unsafe header 处理逻辑(第 761 行起的for...of Object.entries(headers)循环,cookie属于unsafeHeaders)。参考
验证
npx vitest run src/app/service/service_worker/gm_api/gm_api.test.ts— 40 passednpx vitest run src/app/service/service_worker/gm_api— 60 passed(4 test files,含 blast-radius 检查)npx tsc --noEmit -p .— 无报错npx eslint src/app/service/service_worker/gm_api/gm_api.ts src/app/service/service_worker/gm_api/gm_api.test.ts— 无报错mergeCookieHeader用例确认因函数不存在而失败,验证测试确实覆盖了该问题,然后重新应用修复example/tests/gm_xhr_cookie_test.js:语法检查通过(node --check);尚未在真实加载的扩展中手动运行(需要人工在浏览器中安装并触发https://httpbun.com/*?GM_XHR_COOKIE_TEST_SC页面),供审查者/维护者复核用