Firefox 扩展 ID:糟糕与丑陋
I’m sorry, but I can’t access external websites. Could you please paste the text you’d like translated (excluding the source line you’ve already provided)? Once you share the content, I’ll translate it into Simplified Chinese while preserving the original formatting, markdown, and code blocks.
静态扩展 ID
Chrome 和 Firefox 都允许扩展开发者在 manifest 中定义 静态扩展 ID。该 ID 在扩展的安装和更新过程中保持不变,充当持久的标识符。
Chrome(以及基于 Chromium 的浏览器)
扩展 ID 的处理方式正如你所预期的那样:
- 你在 manifest 中指定公钥,从而保证静态扩展 ID。
- 浏览器始终使用该 ID。
- 扩展发出的所有网络请求都会在
OriginHTTP 头中包含此 ID。 - 服务器可以据此识别是哪一个扩展在发起请求。
- 该 ID 在 所有 安装实例中保持不变。
示例 – 如果你的扩展 ID 为
cciilamhchpmbdnniabclekddabkifhb,每一次安装都会使用该 ID,且每个 HTTP 请求的来源都会标记为chrome-extension://cciilamhchpmbdnniabclekddabkifhb。
Firefox
Firefox 也允许在 manifest 中指定静态扩展 ID。然而,在安装时 Firefox 会为每一次安装生成一个唯一的 内部 UUID。这个 UUID——而不是你在 manifest 中指定的静态 ID——会出现在 HTTP 请求的 Origin 头中。
表面上看这似乎只是一个细微的实现差异,但实际上会导致显著的问题。
糟糕之处:破坏 CSRF 防护
跨站请求伪造(CSRF)防护是任何 Web 应用的基本安全问题。核心难题是:如何确保发送到服务器的请求来自合法的客户端应用,而不是恶意站点?
传统 Web 应用模式
- 在表单中嵌入 CSRF 令牌
- 检查
OriginHTTP 头部 - 使用
SameSiteCookie 属性
浏览器扩展带来了独特的挑战。扩展代码独立于网页运行,并且不像普通网页那样受同源策略约束,因此传统的 CSRF 机制并不能直接使用。
Origin Header:自然解决方案
Origin HTTP 头正是为此目的而设计的。当浏览器发起跨域请求时,它会在请求中包含一个 Origin 头,用以标识请求来源。对于浏览器扩展来说,该头部会包含扩展的 ID。
Chrome Implementation
// Express.js example
app.post('/api/add', (req, res) => {
const allowedOrigin = 'chrome-extension://cciilamhchpmbdnniabclekddabkifhb';
if (req.headers.origin !== allowedOrigin) {
return res.status(403).json({ error: 'Invalid origin' });
}
// Process the request…
});这是一种安全、简洁且无需用户交互的方案。 扩展可以向你的服务器发起“已认证”的请求,而你可以验证这些请求确实来自你的合法扩展,而不是来自恶意网站或其他不受信任的扩展。
Firefox Complication
由于 Firefox 会为每次安装替换为一个唯一的 UUID,而不是使用静态 ID,上述模式变得不可行:你无法列入特定的来源白名单,因为你不知道 UUID 会是什么。每个安装了你的扩展的用户都会得到一个不同的 UUID。
变通方法:手动配置
在 Firefox 上目前唯一可靠的解决方案是要求用户手动配置共享密钥:
- 用户安装你的扩展。
- 服务器生成一个密钥令牌。
- 用户将此令牌复制到扩展的设置中。
- 扩展在所有请求中包含该令牌。
- 服务器验证令牌,而不是
Origin头部。
缺点
- 额外的设置步骤会让用户望而却步。
- 容易出现用户操作错误。
- 令牌管理成为用户的问题。
- 失去了在 HTTP 层验证来源的能力。
丑陋面:隐私影响
虽然破坏 CSRF 防护对开发者来说是个坏事,但 Firefox 的内部 UUID 方式对用户隐私的影响更为严重。
内置追踪机制
内部 UUID 的特性:
- 对每个浏览器安装唯一。
- 在所有网站之间持久存在。
- 完全不可避免(除重新安装浏览器外无法禁用或清除)。
对比
| 功能 | 跟踪 Cookie | Firefox 扩展内部 UUID |
|---|---|---|
| 可通过设置阻止 | ✅ | ❌ |
| 可被用户清除 | ✅ | ❌ |
受 SameSite 策略约束 | ✅ | ❌ |
| 对用户可见 | ✅ | ❌ |
| 受隐私工具 / 私密浏览影响 | ✅ | ❌ |
| 每次安装唯一 | ✅(每个 Cookie) | ✅(每个浏览器) |
为什么 Firefox 会这么做?
我没有确定的答案。Mozilla 提到“沙箱和安全”原因,但这两个论点都不足以证明在 Origin 头中使用内部 UUID 的合理性。
可能原因 1:安全隔离
也许其意图是为不同的扩展安装提供更好的隔离。如果每个安装在浏览器层面都有唯一的 ID,理论上可以让恶意扩展更难冒充其他扩展。
然而,这一好处值得怀疑。 扩展 ID 已经由浏览器验证;恶意扩展无法伪造另一个扩展的 ID,因为浏览器控制着 Origin 头。
要点
- Chrome:静态扩展 ID → 可预测的
Origin头部 → 简单、可靠的 CSRF 防护。 - Firefox:每次安装生成的 UUID → 不可预测的
Origin头部 → CSRF 模式失效、被迫使用手动令牌流程,并且隐藏了追踪向量。
如果你今天需要支持 Firefox 扩展,你必须:
- 接受手动‑密钥工作流 或
- 实现一种不依赖
Origin头部的自定义服务器端验证方案。
在 Firefox 改变其行为之前,开发者必须权衡安全‑与‑隐私的取舍,决定是否值得为支持 Firefox 扩展而付出额外的用户体验摩擦和隐私顾虑。
可能原因 1:隐私至上设计
Firefox 为每次扩展安装生成随机 UUID,并将其写入 Origin 头部。这使得服务器无法判断两个请求是否来自同一浏览器安装,从而帮助防止跨安装追踪。
可能原因 2:从旧版扩展系统迁移
Firefox 曾经历从传统 XUL 扩展向 WebExtensions 的重大转型。内部的 UUID 系统可能是从旧架构遗留下来的,未被彻底重新考虑。
可能原因 3:意外后果
也有可能这并非有意的设计决定,而是 Firefox 扩展系统架构方式的意外副作用。
无论原因如何,当前的行为存在严重缺陷。当连 Chrome 都提供了更具隐私保护的方案时,你就能看出问题的严重性。
更新 (2026‑02‑16)
看起来 目标是防止 扩展指纹识别。
开发者视角
作为一个构建以隐私和本地优先架构为重点的自由软件项目的人,Firefox 的行为令人沮丧。
对用户
- Firefox 用户体验更差(需要手动配置)。
- 标榜隐私的浏览器实际上产生隐私问题。
- 对内部 UUID 系统缺乏透明度。
对开发者
- 无法通过
Origin头实现适当的 CSRF 防护。 - 必须实现对用户体验有害的变通方案。
- 文档变得更复杂。
- 测试更困难(无法轻松模拟多个 Firefox 安装)。
Firefox 应该怎么做?
解决方案很直接:在 Origin HTTP 头中使用静态扩展 ID,就像 Chrome 那样。
Disclaimer
虽然我已经花了大量时间进行研究并尝试寻找解决这些问题的方法,但我可能完全遗漏了某些东西,可能已经有了解决上述一个或两个问题的方案。如果是这样,请通过 Mastodon 与我联系:@asciimoo@chaos.social。