Fetch Metadata 请求头:浏览器上下文、CSRF 策略与隐私边界
使用 Sec-Fetch-* 请求上下文帮助服务器制定有限策略,但不要把浏览器元数据当作身份、认证或隐私保证。
BotBrowser Team
BotBrowser 可以在声明浏览器版本、配置文件、路由和断言后,重放获授权的合成请求旅程。它不能把 Sec-Fetch-* 变成身份凭证,不能替代认证,不能推断用户意图,也不能证明所有浏览器和中间层都会发送相同元数据。稳妥做法是把 Fetch Metadata 当作请求上下文的有限服务器信号,并把 CSRF token、Cookie、授权和隐私审查分开。
TL;DR
- Fetch Metadata 描述站点关系、目标、模式和导航等上下文,不是凭证。
- 服务器可以用
Sec-Fetch-Site等字段作为 CSRF 纵深防御,拒绝意外的跨站状态变更。 - 缺失或不支持字段时必须有兼容分支;不能假设所有客户端都会发送。
- 这些字段可能向服务器透露粗粒度的请求关系和导航信息。应减少日志,不要用于指纹或账户身份。
- 用同源、同站、跨站、顶层导航和无头字段客户端组成小型矩阵,记录预期决策和原因。
Contents
Fetch Metadata 描述什么
W3C Fetch Metadata 规范定义浏览器发起 fetch 时可供服务器观察的请求头。Sec-Fetch-Site、Sec-Fetch-Mode、Sec-Fetch-User 和 Sec-Fetch-Dest 可以说明请求来自同源、同站还是跨站,以及它是导航还是子资源。它们不会说明谁在使用浏览器、用户是否批准了动作或请求是否来自受信账户。上下文与身份必须分开。请求可能同源但页面已被入侵,也可能跨站但属于公开图片或明确的回调。原生客户端、旧浏览器、扩展、服务工作线程或代理也可能不发送这些头。服务器仍须用认证和授权防御任意 HTTP 客户端。
Sec-Fetch-Site 的值包括 same-origin、same-site、cross-site 和 none。同站不等于同源;子域、端口和协议仍可能不同。Sec-Fetch-Mode 和 Sec-Fetch-Dest 可帮助区分 JSON mutation 与图片或脚本加载。Sec-Fetch-User: ?1 只是一类用户激活导航提示,不能当作支付批准或登录捷径。请同时阅读 MDN Fetch metadata 术语 和 Sec-Fetch-Site 文档,并记录实际浏览器版本。
每个请求头的含义
先根据路由和方法定义请求的业务含义。状态变更仍需要会话、授权和合适的 CSRF 防护。Fetch Metadata 可作为提前的上下文检查:意外跨站 mutation 可以拒绝或挑战,公开的跨站图片可以继续。缺失字段应进入需要 token 或正常授权的兼容分支,而不是默认放行。不要写“同站即可信”的宽泛规则,尤其当不同子域由不同团队管理时。
构建 CSRF 策略
写中间件前先写决策表:合法会话和 CSRF token 的同源 POST 可继续;跨站 mutation 拒绝或挑战;只读公开资源可继续;缺失头进入兼容策略。对 OAuth、支付返回等例外使用明确 allowlist、nonce 或签名 state。日志只保留决策类别,不保存完整 URL、Cookie 或请求体。高风险路由可以对缺失上下文采取更严格措施,但公共资源和正式非浏览器 API 应使用各自的认证契约。
处理缺失字段和特殊客户端
移动应用、命令行、WebView、测试夹具和旧浏览器可能没有这些头。它们应使用 token、签名请求、mTLS 或其他记录在案的认证,而不是伪造浏览器头。网关可能删除或修改未知头;应在边缘和源站分别观察,并记录哪个信任边界负责决定。重定向、预取、重试、服务工作线程和扩展也会产生少见组合。把它们标为已观察或未知,提供不依赖请求头的安全回退。
隐私与可观测性边界
请求头可以帮助服务器阻止意外跨站请求,但也会透露粗粒度关系、目标类型和可能的激活提示。减少留存,聚合指标,不要把它们连接到账户来做跨站跟踪。它们不是稳定指纹;浏览器版本、重定向、扩展和代理都会改变组合。BotBrowser 能在授权的合成路由中重复观察这些头,却不能证明第三方留存、所有网络中间层或生产日志的隐私影响。测试值应为合成值,并参考自定义 HTTP 请求头文档。
可复制的策略夹具
下面的伪代码只让 Fetch Metadata 选择额外的上下文分支,仍强制认证和 CSRF 校验:
function contextDecision(req) {
const changing = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method);
const site = req.headers['sec-fetch-site'];
const mode = req.headers['sec-fetch-mode'];
const dest = req.headers['sec-fetch-dest'];
if (!changing) return { action: 'continue', reason: 'read-only' };
if (!req.user || !validCsrfToken(req)) return { action: 'reject', reason: 'authentication-or-csrf' };
if (site === 'cross-site') return { action: 'reject', reason: 'unexpected-cross-site' };
if (site === undefined) return { action: 'challenge', reason: 'missing-context' };
if (mode === 'no-cors' || dest === 'image' || dest === 'script')
return { action: 'reject', reason: 'subresource-shape' };
return { action: 'continue', reason: 'declared-context' };
}
预期结果是:同源且 token 有效的 mutation 继续,跨站 mutation 拒绝,缺失字段挑战,子资源形状拒绝。服务器仍需检查请求体、授权、重放和业务不变量。
使用 BotBrowser 测试
建立只返回脱敏决策的合成端点,固定浏览器版本、配置文件、顶层源、目标源、方法、目标类型和预期状态。至少运行同源导航、同站子域、跨站 mutation、跨站公开图片、顶层导航和不发送 Fetch Metadata 的客户端,再加入 token 无效的负例。记录声明输入、字段是否存在、服务器决策、状态码和限制。不要收集真实账户、凭据、生产私有 URL 或宽泛设备清单。升级浏览器或修改策略后保持夹具不变再比较。
实践结论
把 Fetch Metadata 当作有限的浏览器请求上下文描述,与认证、授权、CSRF token、Origin 检查、路由语义和回调契约组合。先定义缺失字段和特殊客户端的行为,再启用强制策略。日志只保存必要的原因码并设置留存期限。下一步应是建立一份合成决策矩阵,让服务负责人批准每个例外。不要声称 Sec-Fetch-* 证明用户、可信浏览器、同意或隐私。
FAQ
Sec-Fetch-Site 能替代 CSRF token 吗?
不能。它可能缺失,也不是服务器验证过的秘密。Cookie 会话的状态变更仍需 CSRF token 或等价防护。
same-origin 是否表示请求已认证?
不是。任何客户端都能发送相同文本;必须独立验证会话、授权、token 和业务规则。
是否应拒绝所有没有 Fetch Metadata 的请求?
通常不应。公开资源、旧浏览器和非浏览器客户端可能缺失。按路由选择兼容策略,高风险 mutation 可要求更强 token。
这些头是指纹吗?
不是稳定身份。组合会随版本、导航、扩展和代理变化,应减少日志并避免跨站跟踪。
BotBrowser 如何验证策略?
使用授权的合成路由和固定浏览器/配置文件观察请求及决策。它不能证明第三方留存或所有浏览器行为。