✨ 为 GM.cookie / GM_cookie 添加 Firefox First-Party Isolation: firstPartyDomain (TM/VM) - #1565
✨ 为 GM.cookie / GM_cookie 添加 Firefox First-Party Isolation: firstPartyDomain (TM/VM)#1565cyfung1031 wants to merge 12 commits into
Conversation
Chrome 不支持该参数(chrome.cookies 会拒绝未知属性),因此仅在 Firefox 下透传给 chrome.cookies.getAll/set/remove,其余浏览器直接忽略该字段。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7a20d26 to
48248a5
Compare
- list 分支误判 cookieAction === "getAll"(实际值为 "list"),导致 Firefox 下 firstPartyDomain 从未真正传给 chrome.cookies.getAll;改为区分「是否提供」而非真值判断,未提供时按 MDN 语义传 null(getAll)或省略(set/remove),提供空字符串时如实保留(代表 FPI 关闭时创建的 cookie)。 - 补齐 GMTypes.Cookie.firstPartyDomain(英/中文类型声明)及内置编辑器默认类型定义 src/template/scriptcat.d.tpl 的 CookieDetails/Cookie,避免与独立声明文件不同步。 - 新增空字符串/纯空白、list 未提供时传 null、set/delete 未提供时省略字段的单元测试。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
chrome.cookies.getAll({firstPartyDomain: null}) 会跨越所有 first-party 分区查询,等同绕过 Firefox 的 First-Party Isolation。
GM_cookie 的权限校验只验证目标 hostname,未考虑调用脚本所在的 first-party 上下文,之前默认给未提供的 firstPartyDomain 补 null,
会让脚本读取到与自己运行上下文无关的第三方分区下的同名 cookie(见 violentmonkey/violentmonkey#1467)。
现在未提供该字段时一律省略(不再区分 list 与 set/delete),只有脚本显式提供时才透传给 chrome.cookies.getAll/set/remove;
相应移除 chrome.cookies.GetAllDetails.firstPartyDomain 的 null 联合类型标注与失效测试。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
上一版为规避 null 跨分区查询的隐患,把未提供时一律省略该字段,但这重新引入了 violentmonkey/violentmonkey#746 描述的问题:FPI(含 Tor Browser 的默认设置)开启时该参数为必填,缺省会导致 chrome.cookies.getAll/remove 的 promise 被拒绝,即 GM_cookie 的 list/delete 在这些环境下报错。 对齐 Violentmonkey 的实际实现(其 checkCookieOpts 对 CookieList/CookieDelete 均补 null,仅 CookieSet 不补): list/delete 未提供时补 null,set 未提供时仍省略(无法用 null 表达新 cookie 的归属)。 "按调用标签页的 first-party 上下文收紧 firstPartyDomain"(而非放宽为 null)目前没有可靠算法可从 WebExtension API 计算(需感知 privacy.firstparty.isolate.use_site 等无法读取的偏好),Violentmonkey 自身对此有长期开放但 未实现的讨论(violentmonkey/violentmonkey#1467),本次不在此 PR 范围内引入。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
上一版把 list 与 delete 一并按 Violentmonkey 的写法补 null,但直接查阅 Firefox 源码
(toolkit/components/extensions/parent/ext-cookies.js,而非 MDN 文档)后确认两者处理并不对称:
- getAll():`if (!("firstPartyDomain" in details)) validateFirstPartyDomain(details)` ——
只有「完全没有这个 key」才会在 FPI 开启时报错;key 存在但值为 null/undefined 会跳过校验,
且 oaFromDetails 在 allowPattern=true(仅 getAll)时会因此丢弃 firstPartyDomain 过滤条件。
因此 list 必须显式传字面量 null(而非省略整个字段,stripUndefined 只会剔除 undefined)。
- set() / remove():`validateFirstPartyDomain(details)` 无条件执行,且该函数用 `!= null`
判断,对 null 与 undefined 一视同仁;oaFromDetails 在 allowPattern=false(set/remove)时
用 `details.firstPartyDomain ?? ""`,null 与未提供得到完全相同的结果。也就是说给
set/remove 补 null 是无效动作:既不能避免 FPI 开启时的报错,也没有「跨分区」的含义。
因此改为:
- list 未提供时补字面量 null(对 getAll 才有效,避免 violentmonkey/violentmonkey#746 描述的
FPI/Tor Browser 报错)。
- set/delete 未提供时直接省略该字段;FPI 开启且脚本未提供时,让 Firefox 自身抛出
"... required 'firstPartyDomain' attribute was not set." 并原样传播给调用方,不去伪造
一个值掩盖这个限制。
「list 的 null 会跨越所有 first-party 分区、GM_cookie 权限校验只验证 hostname 未限定调用脚本
自身分区」这一取舍依旧存在且注释里已说明:正确收紧到调用标签页当前分区需要复刻 Firefox 内部
eTLD+1 算法并感知无法从 WebExtension API 读取的偏好,Violentmonkey 自身对此有长期开放但未
实现的讨论(violentmonkey/violentmonkey#1467),不在本 PR 范围内引入。
同步修正 chrome.cookies.CookieDetails(remove 使用)类型标注,去掉不再需要的 null 联合;
补充 GMTypes.CookieDetails.firstPartyDomain 的文档说明 set/delete 在 FPI 下必须显式提供;
测试更新为验证 list 补 null、delete 省略字段、以及 set/delete 在 FPI 拒绝时原样传播错误。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
👀 还没处理/没准备好处理的,不用merge得这么频繁吧 |
现在不merge 一下之后merge不了 反正测试都要用最新代码去测 这个 draft PR 是因为未测试好 |
|
按 PickInvariant 的 DELTA_AUDIT,对 base 结论:适配层隔离和声明大体一致,但有一项需要明确的隐私边界,以及一项确定的输入校验缺口:
已检查:Firefox 与非 Firefox 的底层字段隔离、set/delete 与 list 的缺省差异、空字符串/trim、按脚本 UUID 的警告去重、模板/英文/中文声明。未验证:Firefox 实际 FPI 运行时与项目对跨分区读取的最终安全契约;上述第一项因此标为需要明确决策,而非单方面认定实现必错。 |
Checklist / 检查清单
背景
Firefox 的 First-Party Isolation(FPI,
privacy.firstparty.isolate)会使用firstPartyDomain区分 Cookie 所属的第一方分区。Tampermonkey 和 Violentmonkey 的
GM_cookie/GM.cookie接口支持传入该字段,但 ScriptCat 目前既未公开对应类型,也不会将其传递给 Firefox Cookies API。这会导致启用 FPI 的 Firefox 环境中,用户脚本无法准确列出、创建或删除指定第一方分区内的 Cookie;其中
set/delete在缺少该字段时还可能直接被 Firefox 拒绝。同时,该字段属于 Firefox 特有扩展。如果在其他浏览器中直接传递,可能因为 Cookies API 不认识该参数而导致整个调用失败,因此需要在浏览器适配层明确处理能力差异。
本次改动
为
GM_cookie/GM.cookie参数增加可选的firstPartyDomain字符串支持。内容脚本侧将
firstPartyDomain纳入参数类型校验,非字符串值会返回Invalid Argument Type。Service Worker 侧仅在 Firefox 环境向
chrome.cookies.getAll、set、remove传递该字段;其他浏览器会从底层调用参数中完全省略,避免未知字段导致 Cookies API 拒绝请求。用户脚本显式使用
firstPartyDomain时输出浏览器兼容性警告:警告按照用户脚本 UUID 去重,同一运行时内每个脚本只提示一次,不因重复调用刷屏;不同脚本分别提示。
对传入值执行
trim(),同时保留显式空字符串语义。按 Firefox Cookies API 的实际行为分别处理不同操作:
list:未提供时显式传入字面量null;set/delete:未提供时省略字段,由 Firefox 在 FPI 开启时按原生规则报错。扩展
chrome.cookies.GetAllDetails、SetDetails、CookieDetails、Cookie的环境类型声明。更新
GMTypes.CookieDetails与GMTypes.Cookie,并同步模板、英文及简体中文用户脚本类型声明。更新开发文档中的浏览器特有 API 处理原则:能力差异应在适配层处理,并通过可操作、按脚本去重的 DevTools 诊断明确告知用户,而不是静默伪装成等价行为。
新增内容脚本和 Service Worker 测试,覆盖参数校验、浏览器差异、警告内容和去重边界、空白裁剪、空字符串、各操作的缺省行为及错误传播。
实现考虑
1. 只在 Firefox 中传递
firstPartyDomainfirstPartyDomain是 Firefox Cookies API 为 First-Party Isolation 提供的扩展字段。Chrome 等其他浏览器不支持该参数,并可能因为出现未知属性而拒绝整个 Cookies API 调用。
因此实现使用
isFirefox()作为运行时开关:firstPartyDomain;这样可以提供 Firefox 兼容能力,同时不破坏其他浏览器现有的 Cookie 操作。
2. 浏览器能力差异必须提供明确诊断
虽然非 Firefox 环境不能把
firstPartyDomain传递给底层 API,但完全静默忽略会让用户脚本误以为 Cookie 操作已经按照指定分区执行。因此,脚本首次显式使用该字段时会输出 DevTools 警告:
警告包含脚本 UUID、脚本名称和
GM_cookie组件信息,便于开发者定位来源。去重边界是用户脚本 UUID,而不是整个扩展全局:同一脚本重复调用不会刷屏,不同脚本仍会分别收到诊断。
3. 区分
list与set/delete的缺省语义Firefox 对
cookies.getAll()与cookies.set()/cookies.remove()的处理并不对称:getAll()会区分“完全没有firstPartyDomainkey”和“key 存在但值为null”;set()/remove()对null与未提供的处理等价;FPI 开启时仍会因为缺少有效值而拒绝请求。因此:
list在未提供字段时补字面量null;set/delete不补默认值;4. 区分“未提供”和“显式空字符串”
Firefox 使用空字符串表示 Cookie 创建时 First-Party Isolation 处于关闭状态,因此
""是合法值,不能与未提供字段混为一谈。实现先判断原始值是否为字符串,再执行
trim():""或仅包含空白的字符串:保留为"";5. 不自动推导当前
firstPartyDomainWebExtension API 无法可靠读取
privacy.firstparty.isolate、privacy.firstparty.isolate.use_site等内部偏好,也没有官方接口可以直接取得与 Firefox 内部算法完全一致的当前第一方域。从标签页 URL 自行推导 eTLD+1 也无法完整覆盖:
use_site配置;因此本 PR 不尝试猜测默认值。需要精确操作分区时,由用户脚本显式提供
firstPartyDomain。已知限制
1. 仅 Firefox 支持实际分区操作
Chrome 及其他不支持该 Cookies API 扩展字段的浏览器不会向底层 API 传递
firstPartyDomain。参数会被忽略,但脚本首次使用时会收到明确的 DevTools 警告,避免把实际行为误认为跨浏览器等价。
2. 警告仅在当前运行时内去重
去重集合由当前 Service Worker 的
GMApi实例维护。同一运行时内每个用户脚本只会警告一次;Service Worker 或扩展运行时重启后,脚本再次使用该字段时可能重新出现警告。
3.
list缺省时会跨第一方分区查询Firefox 下调用
GM_cookie("list", ...)/GM.cookie.list(...)而未提供firstPartyDomain时,实现会传入null,从而绕过 FPI 必填校验并匹配所有 first-party domains。这与现有兼容实现一致,但查询范围不会自动限制在调用脚本所在标签页的当前第一方分区。
4.
set/delete在 FPI 开启时需要显式提供Firefox 开启 FPI 后,若
set/delete未提供有效的firstPartyDomain,浏览器的原生拒绝会直接传播给调用方。实现不会吞掉错误,也不会伪造默认值。
5. 兼容目标
本实现依据 Firefox Cookies API 的实际参数语义,并提供与常见用户脚本管理器相近的
firstPartyDomain接口。不在类型声明或文档中承诺与某个特定版本的 Tampermonkey 或 Violentmonkey 在所有边缘行为上完全一致。
建议审查重点
list补null、set/delete省略字段的差异是否符合 Firefox 实际行为;参考
https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/cookies#storage_partitioning
violentmonkey/violentmonkey#1467
violentmonkey/violentmonkey#746