用户代理客户端提示

社区组报告草案

本版本:
https://wicg.github.io/ua-client-hints/
编辑:
(Google LLC)
(Google LLC)
前编辑:
(Google LLC)
参与:
提交议题开放议题

摘要

本文档定义了一组客户端提示,旨在为开发者在必要时提供基于代理的内容协商能力,同时避免历史上由传统 User-Agent 头暴露的包袱和 被动指纹识别面。

本文件状态

本规范由 Web Platform Incubator Community Group 发布。 它不是 W3C 标准,也不属于 W3C 标准轨道。 请注意,根据 W3C Community Contributor License Agreement (CLA) 有有限的选择退出权以及其它相关条件。 了解更多 W3C Community and Business Groups 信息。

1. 介绍

本节为非规范性内容

目前,用户代理通常通过在每个请求中发送 User-Agent HTTP 请求头字段来向服务器标识自己(详见 [rfc9110] 的第5.5.3节)。理想情况下,这个头字段可以让服务器进行内容协商,针对特定用户代理精确地发送最能代表所请求资源的数据,从而优化带宽和用户体验。但实际上,这个头字段的值一方面作为默认设置暴露了过多关于用户设备的信息,另一方面又故意掩盖真实的用户代理,以规避服务器端的错误判定。

例如,最新版 iOS 上的 Chrome 标识为:

User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X)
            AppleWebKit/605.1.15 (KHTML, like Gecko)
            CriOS/69.0.3497.105 Mobile/15E148 Safari/605.1

而最新版的 Edge 标识为:

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
            AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.2704.79
            Safari/537.36 Edge/18.014

这些字符串里包含了大量信息(也夹杂着不少虚假内容)。版本号、平台细节、型号信息等都会在每次请求时被广播,并成为各种指纹识别方案的基础。各浏览器厂商尝试过修改自己的用户代理字符串,但在历史实践中遇到了以下几类开发者反馈,阻碍了进一步改进:

  1. 品牌和版本信息(如 "Chrome 69")让网站能够绕过特定版本已知的 bug,这些 bug 无法通过其他方式检测。例如,不同厂商对内容安全策略的实现差异很大,难以在不知道具体浏览器的情况下确定应该在 HTTP 响应中发送什么策略。

  2. 开发者常常会根据用户代理和平台协商发送何种内容。例如,某些应用框架会在 iOS 和 Android 上采用不同样式,以匹配各自平台的美学和设计规范。

  3. 类似于第1点,操作系统版本和架构可能导致特定 bug,可以通过网站代码进行规避,同时也方便如下载合适的可执行文件(32位/64位,ARM/Intel等)。

  4. 高级开发者会利用型号/厂商信息来定制网站以适配设备能力(如 [FacebookYearClass]),并定位某些只在特定型号/厂商出现的性能问题和回归。

本文件提出了一种机制,允许用户代理更积极地减少 User-Agent 字符串中的熵,让真正需要客户端细节的服务器可以主动选择接收相关信息。它引入了多个新的客户端提示([RFC8942]),可提供客户端的品牌和版本信息、底层操作系统的品牌和主版本,以及底层设备的细节。用户代理不再向所有人一直广播这些数据,而是可以针对特定站点的需求,合理决定是否提供更细粒度的数据,从而减少网络暴露的 被动指纹识别面(参见 最佳实践1[FINGERPRINTING-GUIDANCE])。

1.1. 示例

本节为非规范性内容

用户首次使用最新版 "Examplary Browser" 访问 https://example.com/。他们的用户代理在 HTTP 请求中发送如下请求头:

Sec-CH-UA: "Examplary Browser"; v="73", ";Not?A.Brand"; v="27"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Windows"

服务器希望根据用户的平台版本渲染内容,因此通过在初始响应中发送 Accept-CH 头([RFC8942] 第2.2.1节)请求更多信息:

Accept-CH: Sec-CH-UA-Platform-Version

随后,用户代理在下一个请求中加入了平台版本信息:

Sec-CH-UA: "Examplary Browser"; v="73", ";Not?A.Brand"; v="27"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Windows"
Sec-CH-UA-Platform-Version: "14.0.0"

1.2. 使用场景

本节为非规范性内容

本节尝试记录当前 User-Agent 字符串的用途,以及如何通过 User-Agent 客户端提示(UA-CH)实现类似功能。

1.2.1. 差异化服务

1.2.1.1. 基于浏览器特性
此场景可让 polyfill.io 等服务针对用户定制 polyfill,而无需让现代浏览器用户的体验变得臃肿。

同理,在为用户提供 Javascript 时,可以避免为支持最新 ES 特性的浏览器进行转译(否则会造成代码臃肿和低效)。在图片服务方面,有些浏览器不会更新 Accept 请求头,而 MIME 类型也可能不足以区分同一格式的不同变体(如 WebP)。此时,了解浏览器及其版本对于提供正确的图片变体至关重要。

要实现上述用例,服务器需要知道浏览器及其有效版本,并将其映射到可用功能列表,从而决定提供哪种 polyfill 或代码变体。

希望通过 UA-CH 实现此功能的服务需要检查每个请求中默认发送的 Sec-CH-UA 头,并据此调整响应。

1.2.1.2. 浏览器缺陷兼容处理
某些浏览器版本存在已知 bug,内容需要针对性地规避这些问题。触发这些 bug 会导致浏览器崩溃、内容损坏等问题,而这些 bug 本质上无法通过特性检测发现,因此内容需针对受影响的浏览器版本进行规避。

在此用例中,服务器需知晓浏览器及其有效版本,了解相关 bug,并在当前浏览器版本受影响时应用兼容处理。

希望通过 UA-CH 实现此功能的服务需要检查每个请求中默认发送的 Sec-CH-UA 头,并据此调整响应。

1.2.2. 市场份额分析

浏览器的市场份额极其重要。了解某浏览器的使用情况可以激励开发者进行兼容性测试,减少用户遇到的问题。同时,浏览器的市场份额也直接影响浏览器厂商的商业目标,推动浏览器未来发展。

要进行市场份额分析,服务器需了解以下一项或多项信息:用户代理名称及其有效版本、操作系统及其版本、设备型号。服务器可注册这些信息并统计其相对市场份额。

希望通过 UA-CH 提供市场份额分析的网站需检查每个请求中默认发送的 Sec-CH-UA 头,并进行记录。根据具体场景,还可请求其他 UA 客户端提示(如移动设备型号分析)。

设计上,品牌列表中的单项难以区分不太流行浏览器的真实品牌名和更流行浏览器的虚构品牌(GREASE)。由于不太流行的浏览器可能会为兼容性目的包含多个流行品牌名,因此若采用此方法,其用户很可能被归类为流行浏览器用户,导致市场份额统计偏向流行浏览器,而不太流行的浏览器则难以获得可见度。

因此,分析时最好将品牌列表作为一个整体,与各(浏览器,版本)组合发送的已知品牌列表进行比较。需要定期更新已知品牌列表,否则所有浏览器都将被归类为未知浏览器。但由于这不会影响用户访问网站,在此场景下未知浏览器统计归零是可以接受的。

可由中心化机构维护品牌列表(如 caniuse 和 MDN 维护浏览器特性支持,供众多站长使用)。

规范建议浏览器每个版本发送固定品牌列表,简化统计(也利于缓存),这样已知品牌列表只需映射到(浏览器,版本)组合。

1.2.3. 内容适配

内容适配即确保用户获得针对其需求定制的内容。内容适配不仅限于 UA 字符串,还涉及 多种维度视口尺寸设备内存用户偏好等。本小节主要讨论依赖当前 User-Agent 字符串信息的内容适配需求。
1.2.3.1. 基于浏览器的适配
有些网站会针对不同浏览器提供略有不同的内容,原因多种多样。有些理由是合理的(如根据功能支持提供不同体验),有些理由不太合理(如提醒用户未在某浏览器测试过),还有些理由是明显错误的(如只因浏览器类型而屏蔽用户访问)。

作为浏览器,我们希望支持前者,抑制后者。

1.2.3.2. 移动端专用网站
许多网站会区分移动端和桌面端内容。响应式设计已让单一代码适配多种设备成为可能,但在某些场景下,移动端专版可能更具针对性。

在这些场景下,向移动设备用户提供移动端专版网站是有帮助的。为实现此功能,服务器需在 HTML 响应阶段知晓用户是否为移动设备。

希望通过 UA-CH 实现移动端专版的网站可利用每个请求默认发送的 Sec-CH-UA-Mobile 头。

1.2.3.3. 低性能设备
有些网站会针对低性能设备(难以处理高负载任务、大视频和大图片等)提供不同内容。此类适配通常依赖 User-Agent 字符串里的设备型号信息,并结合服务器端数据库将型号转换为内存、CPU 等类别进行内容划分。

若划分维度为内存,可用 Device-Memory 客户端提示。使用 UA-CH 时,网站可通过 Sec-CH-UA-Model 获取设备型号。

这两种提示均非默认发送,因此需要额外配置。

顶级域需在响应中发送 Accept-CH: Device-Memory, Sec-CH-UA-Model 头以接收相关提示。若需每次导航都适配,可在缺少提示时通过重定向解决,或用 Critical-CH 让客户端处理额外请求响应流程。

第三方域若需适配,需顶级域进行 跨域提示授权。顶级域需配置 Accept-CH 并通过 Permissions-Policy 头授权相关提示给第三方。

1.2.3.4. 操作系统专属样式
一些网站希望界面风格能匹配用户的操作系统。渐进增强通常是更优选择(如用脚本应用不同按钮样式),但有时会希望基于平台和平台版本提供定制的内联样式。

这类场景与“低性能设备”问题类似,只是用的是 Sec-CH-UA-PlatformSec-CH-UA-Platform-Version 提示。

1.2.3.5. 操作系统集成
同样,有些网站希望将链接换为操作系统专用(如 Android intent 链接)。渐进增强可以用脚本修改这些链接,但部分网站偏好服务器端适配。

同“操作系统专属样式”,需用 Sec-CH-UA-PlatformSec-CH-UA-Platform-Version 实现。

1.2.3.6. 浏览器和操作系统专属实验
有些服务器可能只希望在特定浏览器、特定平台或其版本上进行多变体实验。若仅限浏览器及版本,网站可用默认的 Sec-CH-UA。如需平台及其版本,可用默认的 Sec-CH-UA-Platform,但需请求 Sec-CH-UA-Platform-Version,或用客户端脚本控制实验。

1.2.4. 用户登录通知

很多网站,尤其是安全敏感类网站,都会在新设备登录时通知用户。这样用户能及时知晓并在发现异常时采取措施。

为使通知有意义,网站需识别并告知用户所用浏览器的商业品牌信息。通常还会包括平台及其版本,以确保用户明确是哪台设备。

此类通知无需服务器端适配,因此建议使用 userAgentData.getHighEntropyValues() 方法获取所需信息。

1.2.5. 下载合适的二进制可执行文件

部分网站用于下载本地应用的二进制可执行文件,需能默认向用户推荐合适的可执行文件。合适的文件需考虑多个因素:操作系统、版本、位宽及 CPU 架构等。

为满足该用例,下载类网站可主动接收 Sec-CH-UA-PlatformSec-CH-UA-Platform-VersionSec-CH-UA-ArchSec-CH-UA-Bitness 提示(或通过 API 查询),以确保默认推荐合适的文件。

1.2.6. 转化建模

某些机器学习模型会用 User-Agent 字符串中的细节来估算用户属性。使用客户端提示也能实现类似建模,但需明确选择收集相关信息。

1.2.7. 漏洞过滤

某些环境下,代理服务器会校验用户是否使用存在安全漏洞的过时设备。虽然 Sec-CH-UA 能提供部分信息,但浏览器和操作系统的完整版本信息对于该类分析尤为重要。

此类代理需增加一次重定向,或用两种 客户端提示可靠性机制来主动获取浏览器完整版本和平台版本,以保证能持续获取相关提示。

1.2.8. 日志与调试

许多服务目前会记录 User-Agent 字符串,并在分析流量或排查服务错误时加以利用。这些服务需用 Sec-CH-UA 提供的低熵值进行日志记录,或主动接收高熵提示。后者仅在特定问题时建议使用。遇到特殊问题时可主动获取更多用户代理细节,或用 userAgentData.getHighEntropyValues() API 实现。

1.2.9. 指纹识别

用户指纹识别是指收集用户的多项信息并组合形成唯一签名,从而即使用户清理浏览器状态(如删除 cookie)也能再次识别其身份。

此类场景下,源站会收集尽可能多的熵,因此可能会请求所有可用提示。

1.2.9.1. 垃圾过滤和机器人检测
这是指纹识别的一种非恶意用途,因此我们希望予以保留。通过 UA-CH,初期可主动收集各类提示实现此用例。

我们希望未来有替代方法或 API 能解决垃圾过滤和机器人检测场景,因为浏览器可能会介入限制用户身份熵收集(如 隐私预算提案)。

1.2.9.2. 持久化用户追踪
这是本提案明确试图加大难度的指纹识别场景。和“垃圾过滤”类似,仍可主动收集所有提示作为熵值。但与上述用例不同,隐私预算等提案旨在阻止持久化追踪,而不会为持久化追踪提供替代方案。
1.2.9.3. 拦截已知机器人和爬虫
当前,User-Agent 字符串常被用作阻止已知机器人和爬虫的简单手段。有担忧认为默认减少熵暴露后,机器人更容易隐藏于“人群”之中。虽然有一定道理,但不应因此让普通用户更易被识别。

和垃圾过滤场景类似,希望未来有替代方法能替代 User-Agent 匹配实现此用例。

2. 基础设施

本规范依赖客户端提示基础设施、HTTP 客户端提示、Infra 标准和权限策略。[CLIENT-HINTS-INFRASTRUCTURE] [RFC8942] [INFRA] [permissions-policy-1]

部分术语定义参见 用于 HTTP 的结构化字段值[rfc9651]

3. 用户代理提示

以下章节定义了多个 HTTP 请求头字段,用于暴露特定用户代理的详细信息,服务器可通过 [RFC8942] 中定义的客户端提示基础设施主动选择接收这些信息。下面的定义假设每个用户代理为自身定义了一些属性:

用户代理应保持这些字符串简短直接,但服务器必须接受任意值,因为这些值均由用户代理自由构造。

用户代理必须将高熵的平台架构值映射到以下桶:

其他 CPU 架构可视情况映射到上述值之一,或者映射为空字符串。

用户代理应返回平台架构或平台位宽的空字符串或虚构值,除非用户平台同时满足以下两个条件:

用户代理移动性为 false 时,必须为型号返回空字符串。即使移动性为 true,型号也必须返回空字符串,除非该平台通常会暴露型号信息。

用户代理可为类型为 sf-string 的提示返回空字符串,为类型为 sf-boolean 的提示返回 false,或者为以下提示返回任意虚构值(为隐私、兼容性或其它原因):完整版本平台架构平台位宽wow64 属性型号

3.1. 'Sec-CH-UA' 头字段

Sec-CH-UA 请求头字段为服务器提供用户代理品牌信息重要版本信息。它是一个结构化头,其值必须为列表 [rfc9651]。 列表项必须为字符串。每项的值应包含 "v" 参数,表示用户代理的版本。

该头的 ABNF 如下:

Sec-CH-UA = sf-list

返回请求的 Sec-CH-UA,执行以下步骤:

  1. brands 为用 "重要版本" 运行 创建品牌的结果。

  2. list 为用 brands 和 "重要版本" 运行创建品牌-版本列表的结果。

  3. 返回用 list 运行序列化列表的输出。

注意: 与多数客户端提示不同,由于其包含在低熵提示表中,Sec-CH-UA 头会默认发送,无论服务器是否通过 Accept-CH 头主动选择接收(但仍可由策略控制的客户端提示功能控制)。它被认为是低熵提示,因为只包含用户代理的品牌信息和重要版本号(这些信息可通过分析其它头结构和测试特性支持轻易推断 [Janc2014])。

注意: Sec-CH-UA 会揭示品牌列表中每个品牌的主版本。如需获取完整版本的用例,请参考 Sec-CH-UA-Full-Version-List

3.2. 'Sec-CH-UA-Arch' 头字段

Sec-CH-UA-Arch 请求头字段为服务器提供特定用户代理所运行平台的架构信息。它是一个结构化头,其值必须为字符串 [rfc9651]

该头的 ABNF 如下:

Sec-CH-UA-Arch = sf-string

3.3. 'Sec-CH-UA-Bitness' 头字段

Sec-CH-UA-Bitness 请求头字段为服务器提供特定平台位宽的信息,即特定用户代理所运行平台架构的位宽。它是一个结构化头,其值必须为字符串 [rfc9651]

该头的 ABNF 如下:

Sec-CH-UA-Bitness = sf-string

3.4. 'Sec-CH-UA-Form-Factors' 头字段

Sec-CH-UA-Form-Factors 请求头字段为服务器提供用户代理形态因子信息。它是一个结构化头,其值必须为列表 [rfc9651]。为避免增加指纹熵,头值必须按字典顺序排列,且值区分大小写。

该头应使用下列通用形态因子值之一或多个描述设备形态因子:"Desktop"、"Automotive"、"Mobile"、"Tablet"、"XR"、"EInk" 或 "Watch"。所有适用的形态因子值都应包含。

注意:

用户代理的形态因子描述用户与用户代理的交互方式。允许值的含义如下:

如有用户以全新方式交互的新形态因子,且网站有充分理由希望对该设备采用不同交互方式,且现有提示无法可靠识别该形态,应提议并加入新值。

该头的 ABNF 如下:

Sec-CH-UA-Form-Factors = sf-list

3.5. 'Sec-CH-UA-Full-Version' 头字段

Sec-CH-UA-Full-Version 已废弃,未来将被移除。开发者请使用 Sec-CH-UA-Full-Version-List 代替。

Sec-CH-UA-Full-Version 请求头字段为服务器提供用户代理的完整版本信息。它是一个结构化头,其值必须为字符串 [rfc9651]

该头的 ABNF 如下:

Sec-CH-UA-Full-Version = sf-string

3.6. 'Sec-CH-UA-Full-Version-List' 头字段

Sec-CH-UA-Full-Version-List 请求头字段为服务器提供品牌列表中每个品牌的完整版本信息。它是一个结构化头,其值必须为列表 [rfc9651]

该头的 ABNF 如下:

Sec-CH-UA-Full-Version-List = sf-list

返回请求的 Sec-CH-UA-Full-Version-List,执行以下步骤:

  1. brands 为用 "完整版本" 运行 创建品牌的结果。

  2. list 为用 brands 和 "完整版本" 运行创建品牌-版本列表的结果。

  3. 返回用 list 运行序列化列表的输出。

3.7. 'Sec-CH-UA-Mobile' 头字段

Sec-CH-UA-Mobile 请求头字段为服务器提供特定用户代理是否偏好“移动”体验的信息。它是一个结构化头,其值必须为布尔值 [rfc9651]

该头的 ABNF 如下:

Sec-CH-UA-Mobile = sf-boolean

注意: 类似上面的 Sec-CH-UA,由于其包含在低熵提示表中,Sec-CH-UA-Mobile 头会默认发送,无论服务器是否通过 Accept-CH 主动选择接收(但仍可由策略控制的客户端提示功能控制)。它被视为低熵提示,因为它只是一位信息且可由用户直接控制。

3.8. 'Sec-CH-UA-Model' 头字段

Sec-CH-UA-Model 请求头字段为服务器提供特定用户代理所运行设备的信息。它是一个结构化头,其值必须为字符串 [rfc9651]

该头的 ABNF 如下:

Sec-CH-UA-Model = sf-string

3.9. 'Sec-CH-UA-Platform' 头字段

Sec-CH-UA-Platform 请求头字段为服务器提供特定用户代理所运行平台的信息。它是一个结构化头,其值必须为字符串 [rfc9651]。其值应匹配下列通用平台之一:"Android"、"Chrome OS"、"Fuchsia"、"iOS"、"Linux"、"macOS"、"Windows" 或 "Unknown"。

该头的 ABNF 如下:

Sec-CH-UA-Platform = sf-string

注意: 类似上面的 Sec-CH-UA,由于其包含在低熵提示表中,Sec-CH-UA-Platform 头会默认发送,无论服务器是否通过 Accept-CH 主动选择接收(但仍可由策略控制的客户端提示功能控制)。

3.10. 'Sec-CH-UA-Platform-Version' 头字段

Sec-CH-UA-Platform-Version 请求头字段为服务器提供特定平台版本的信息,即特定用户代理所运行的平台版本。它是一个结构化头,其值必须为字符串[rfc9651]。 其值为用平台品牌运行获取平台版本的结果。

获取平台版本,给定字符串 platform,执行以下步骤:

  1. 如果 platform 为 "Linux":

    1. 返回空字符串。

  2. 如果 platform 为 "Android":

    1. platformReturnedVersionString 为查询操作系统 android.os.Build.VERSION.RELEASE 字符串的结果。

    2. 返回用 platformReturnedVersionString 运行创建统一平台版本字符串的结果。

  3. 如果 platform 为 "iOS":

    1. platformReturnedVersionString 为通过 currentDevice 获取 UIDevice 对象并读取其 systemVersion 的结果。

    2. 返回用 platformReturnedVersionString 运行创建统一平台版本字符串的结果。

  4. 如果 platform 为 "Windows":

    1. 如可用(即 Windows 10 或更高版本),令 platformReturnedVersionString 为查询 Windows.Foundation.UniversalApiContract 整数版本并转换为字符串的结果。否则,令 platformReturnedVersionString获取旧版 Windows 版本号的结果。

    2. 返回用 platformReturnedVersionString 运行创建统一平台版本字符串的结果。

  5. platformVersionComponentList 为一个列表

  6. 如果 platform 为 "macOS":

    1. macOSVersion 为通过获取 processInfo 信息代理获得 NSProcessInfo 对象的 operatingSystemVersion 属性。

    2. 追加 macOSVersionmajorVersionminorVersionpatchVersion 组件(按顺序)到 platformVersionComponentList

  7. 如果 platform 为其它值:

    1. 追加一到三个版本部分(根据最有可能实现与在 platform 上运行其它浏览器互操作性的格式)到 platformVersionComponentList

    2. platformVersionComponentList 长度小于 3 时,追加 "0" 到 platformVersionComponentList

  8. 返回用 U+002E 句点(.)分隔 platformVersionComponentList连接结果。

获取旧版 Windows 版本号,执行以下步骤:

  1. major 为 Win32 GetVersionEx API 返回的 OSVERSIONINFOdwMajorVersion 成员值。

  2. minor 为 Win32 GetVersionEx API 返回的 OSVERSIONINFOdwMinorVersion 成员值。

  3. 如果 major6minor3(即 Windows 8.1),返回 "0.3"。

  4. 如果 major6minor2(即 Windows 8),返回 "0.2"。

  5. 如果 major6minor1(即 Windows 7),返回 "0.1"。

  6. 否则,返回 "0"。

创建统一平台版本字符串,给定字符串 input,执行以下步骤:

  1. platformVersionComponentList 为一个列表index 为 0。

  2. platformVersionUnprocessedTokenList 为用 U+002E 句点字符(.严格分割 input 得到的列表

  3. index 小于 3 时:

    1. 如果 index 小于 platformVersionUnprocessedTokenList 的长度:

      1. 如果 platformVersionUnprocessedTokenList[index] 是无符号整数,则转换为字符串并追加platformVersionComponentList

      2. 否则,追加 "0" 到 platformVersionComponentList

    2. 否则,如果 index 大于等于 platformVersionUnprocessedTokenList 的长度:

      1. 追加 "0" 到 platformVersionComponentList

    3. index 增加 1。

  4. 返回用 U+002E 句点(.)分隔 platformVersionComponentList连接结果。

该头的 ABNF 如下:

Sec-CH-UA-Platform-Version = sf-string

3.11. 'Sec-CH-UA-WoW64' 头字段

Sec-CH-UA-WoW64 请求头字段为服务器提供特定用户代理二进制文件是否在 64 位 Windows 的 32 位模式下运行的信息。它是一个结构化头,其值必须为布尔值 [rfc9651]

该头的 ABNF 如下:

Sec-CH-UA-WoW64 = sf-boolean

注意: 这些客户端提示可通过以下客户端提示令牌激活:Sec-CH-UASec-CH-UA-ArchSec-CH-UA-BitnessSec-CH-UA-Form-FactorsSec-CH-UA-Full-VersionSec-CH-UA-Full-Version-ListSec-CH-UA-MobileSec-CH-UA-ModelSec-CH-UA-PlatformSec-CH-UA-Platform-VersionSec-CH-UA-WoW64

4. 接口

dictionary NavigatorUABrandVersion {
  DOMString brand;
  DOMString version;
};

dictionary UADataValues {
  DOMString architecture;
  DOMString bitness;
  sequence<NavigatorUABrandVersion> brands;
  sequence<DOMString> formFactors;
  sequence<NavigatorUABrandVersion> fullVersionList;
  DOMString model;
  boolean mobile;
  DOMString platform;
  DOMString platformVersion;
  DOMString uaFullVersion; // deprecated in favor of fullVersionList
  boolean wow64;
};

dictionary UALowEntropyJSON {
  sequence<NavigatorUABrandVersion> brands;
  boolean mobile;
  DOMString platform;
};

[Exposed=(Window,Worker)]
interface NavigatorUAData {
  readonly attribute FrozenArray<NavigatorUABrandVersion> brands;
  readonly attribute boolean mobile;
  readonly attribute DOMString platform;
  Promise<UADataValues> getHighEntropyValues(sequence<DOMString> hints);
  UALowEntropyJSON toJSON();
};

interface mixin NavigatorUA {
  [SecureContext] readonly attribute NavigatorUAData userAgentData;
};

Navigator includes NavigatorUA;
WorkerNavigator includes NavigatorUA;

注意: 用户代理的高熵部分信息通过 Promise 检索,以便让用户代理有机会在暴露前执行可能耗时的检查(例如请求用户授权)。

4.1. 处理模型

4.1.1. WindowOrWorkerGlobalScope

每个用户代理都关联一个品牌列表,该列表通过用重要版本运行创建品牌获得,是一个列表

每个 WindowOrWorkerGlobalScope 对象都关联一个品牌冻结数组,即 FrozenArray<NavigatorUABrandVersion>。 初始值为用用户代理品牌列表运行创建冻结数组的结果。

此外,每个 WindowOrWorkerGlobalScope 对象还关联一个完整版本列表冻结数组,即 FrozenArray<NavigatorUABrandVersion>。 其值为用完整版本运行创建品牌 创建冻结数组的结果。

4.1.2. 创建品牌

要求用 version type 创建品牌时,执行以下步骤:

  1. list 为一个列表

  2. 断言 version type 必须为 "完整版本" 或 "重要版本"。

  3. 对于每个表示用户代理(或等价类)的品牌,作为 brand

    1. version 为一个字符串,按如下初始化:

      1. 如果 version type 为 "完整版本",则 version 设为对应完整版本的字符串。

      2. 如果 version type 为 "重要版本",则 version 设为对应重要版本的字符串。

    2. dict 为新建的 NavigatorUABrandVersion 字典,brand 设为 brandversion 设为 version

    3. 追加 dictlist

  4. 用户代理应执行以下步骤:

    1. 追加一个额外的list,该项为一个 NavigatorUABrandVersion 字典,brand 设为任意品牌version 设为用 version type 运行创建任意版本的结果。

    2. 随机化 list的顺序。

    注意: 为最小化生成这些随机组件时的缓存差异,可在构建时确定其值,并在用户代理的整个重要版本生命周期内保持不变。

    注意: 详见 § 7.2 类 GREASE UA 品牌列表,了解何时及为何应采用这些随机化步骤。

  5. 返回 list

等价类表示一组被认为彼此兼容的浏览器。例如,共享渲染引擎可以形成一个等价类

4.1.3. 创建任意品牌和版本值

创建任意品牌用户代理必须执行以下步骤:

  1. arbitraryBrand 为一个仅由ASCII 字母和 0x20 (空格) 组成的字符串arbitraryBrand 必须包含一个或多个 0x20 (空格) 字节,且长度不超过 20 个ASCII 字节,且不能以 0x20 (空格) 开头或结尾。

  2. arbitraryBrandList 为用ASCII 空白分割arbitraryBrand后得到的列表。

  3. greaseyStack 为一个

  4. greaseyChars 为如下列表ASCII 字节 « 0x20 (空格)、0x28 (左括号)、0x29 (右括号)、0x2D (-)、0x2E (.)、0x2F (/)、0x3A (:)、0x3B (;)、0x3D (=)、0x3F (?)、0x5F (_) »。

  5. index 为 0。

  6. index 小于 arbitraryBrandList长度减一时:

    1. 推入一个随机选取的greaseyChars中的greaseyStack

    2. index加1。

  7. greaseyBrandList 为一个列表,同时index设为0。

  8. greaseyStack非空时:

    1. 追加 arbitraryBrandList[index] 到 greaseyBrandList

    2. item弹出greaseyStack的结果。

    3. 追加 itemgreaseyBrandList

    4. index加1。

  9. 追加 arbitraryBrandList[index] 到 greaseyBrandList

  10. 返回用去除首尾 ASCII 空白后的greaseyBrandList(无分隔符)连接结果。

该算法应生成不带首尾greaseyChars的任意品牌,因为实现经验表明这些字符与网页兼容性不佳。

此外,虽然结构化头允许字符串中使用转义的 0x22 (\") 和 0x5C (\\),但这些字符也会导致防火墙兼容性问题。

创建任意版本,给定version type,执行以下步骤:

  1. 断言 version type为"完整版本"或"重要版本"。

  2. arbitrary version字符串,按如下初始化:

    1. version type 为"完整版本",则 arbitrary version 设为与完整版本格式相同但值不同的字符串。

    2. version type 为"重要版本",则 arbitrary version 设为与重要版本格式相同但值不同的字符串。

  3. 返回 arbitrary version

注意: 用户代理可以选择发送任意低版本号以确保版本检测正常,并应定期变化。

4.1.4. 创建品牌-版本列表

创建品牌-版本列表,给定brandsversion type,执行以下步骤:

  1. list 为一个初始为空的列表

  2. 断言 version type为"完整版本"或"重要版本"。

  3. 遍历brands中的每个brand

    1. version 为字符串,按如下初始化:

      1. version type 为"完整版本",则 version 设为对应完整版本的字符串。

      2. version type 为"重要版本",则 version 设为对应重要版本的字符串。

    2. parameter 为一个初始为空的字典

    3. 设置 parameter["param_key"] 为 "v"。

    4. 设置 parameter["param_value"] 为 version

    5. pair 为由 brandbrandparameter组成的元组。

    6. 追加 pairlist

  4. 返回 list

4.1.5. Getter方法

获取brands属性时,必须返回this相关全局对象品牌冻结数组

获取mobile属性时,必须返回用户代理移动性

获取platform属性时,必须返回用户代理平台品牌

4.1.6. getHighEntropyValues方法

getHighEntropyValues(hints) 方法必须执行以下步骤:

  1. p 为在当前环境下创建的新Promise

  2. uaData 为新建的UADataValues

  3. 设置 uaData["brands"] 为 this相关全局对象品牌冻结数组

  4. 设置 uaData["mobile"] 为 用户代理移动性

  5. 设置 uaData["platform"] 为 用户代理平台品牌

  6. 如果this相关全局对象关联文档不被允许使用ch-ua-high-entropy-values功能,则用uaData解析p

  7. 否则,以下步骤并行执行:

    1. 如果 hints 包含 "architecture",则设置 uaData["architecture"] 为用户代理平台架构

    2. 如果 hints 包含 "bitness",则设置 uaData["bitness"] 为用户代理平台位宽

    3. 如果 hints 包含 "formFactors",则设置 uaData["formFactors"] 为用户代理形态因子

    4. 如果 hints 包含 "fullVersionList",则设置 uaData["fullVersionList"] 为 this相关全局对象完整版本列表冻结数组

    5. 如果 hints 包含 "model",则设置 uaData["model"] 为用户代理型号

    6. 如果 hints 包含 "platformVersion",则设置 uaData["platformVersion"] 为用平台品牌运行获取平台版本的结果。

    7. 如果 hints 包含 "uaFullVersion",则设置 uaData["uaFullVersion"]。

    8. 如果 hints 包含 "wow64",则设置 uaData["wow64"] 为用户代理的wow64属性

    9. 在权限任务源队列解析p,值为uaData

  8. 返回 p

4.1.7. toJSON方法

toJSON()方法必须执行以下步骤:

  1. uaLowEntropyData 为新建的UALowEntropyJSON

  2. 设置 uaLowEntropyData["brands"] 为 this相关全局对象品牌冻结数组

  3. 设置 uaLowEntropyData["mobile"] 为 用户代理移动性

  4. 设置 uaLowEntropyData["platform"] 为 用户代理平台品牌

  5. 返回 uaLowEntropyData

5. 权限策略集成

本规范定义了由字符串"ch-ua-high-entropy-values"标识的策略控制功能,其默认允许列表'*'。这决定了某文档是否允许通过getHighEntropyValues()API返回高熵客户端提示值。

注意: 若某文档不允许使用"ch-ua-high-entropy-values"功能,则getHighEntropyValues()API仍会为方便起见返回低熵值。

6. 安全与隐私注意事项

6.1. 安全传输

客户端提示不会发送给非安全端点(详见[RFC8942]第2.2.1节中的安全传输要求)。这意味着用户代理信息不会通过明文通道泄露,从而减少了网络攻击者长期构建指定代理行为画像的机会。

6.2. 委托

客户端提示将通过权限策略([permissions-policy-1])从顶级页面委托。这减少了用户代理信息随子资源请求一同发送的概率,从而降低了被动指纹识别的可能性。

此委托由追加客户端提示到请求定义。

6.3. 指纹识别

User Agent Client Hints 的主要目标是减少通过 User-Agent 头字段默认暴露给整个 Web 的熵量,这些信息可用于被动指纹识别

用户代理可以自主决定提供哪些提示,原则上不应提供超出未裁剪 User-Agent 头字段包含的信息。用户代理可为不想提供的值返回空字符串,或直接拒绝返回某提示。

但仍可能有部分或全部提示被请求并用于第一方或被委托第三方的主动指纹识别。如§ 6.4 访问限制所述,用户代理应考虑针对已知会主动指纹识别用户的方针实施访问限制。

用户代理应注意不要通过 GREASE 类品牌列表引入仅针对单个或极少用户独特的指纹向量。而应采用一种让任意品牌在众多用户间共享的策略(如:在所有用户的主版本间保持稳定)。

6.4. 访问限制

上述客户端提示揭示了大量关于用户代理及其运行设备的信息。用户代理应在授予访问这些信息前谨慎评估,并可在安全传输和委托要求之外施加额外限制。例如,用户代理可选择仅在打算下载时暴露平台架构平台位宽,以便服务器能提供合适的二进制文件。同样,也可让用户自行控制暴露给服务器的信息,或经由权限提示或设置界面进行访问授权。

7. 实现注意事项

7.1. 'User-Agent' 头字段

用户代理应通过减少User-Agent头的信息粒度,弃用其使用,转而采用本文档描述的客户端提示模型。该头短期内不太可能完全移除,因为现有网站的内容协商代码仍需其存在(参见[Rossi2015],了解新浏览器在此领域的最新挑战)。

一种可取的做法是让每个用户代理锁定其User-Agent头值,通过长期保留“like Gecko”和“AppleWebKit/537.36”等声明确保向后兼容。此策略可逐步推进,先冻结版本号,再将平台和型号信息转为更通用内容,以减少该头带来的指纹。

7.2. 类似 GREASE 的 UA 品牌列表

历史表明,用户代理存在通过谎报品牌来应对网站嗅探脚本的动力,以防止用户因 UA 白/黑名单而被屏蔽。

重置预期在短期内有助于防止品牌列表被滥用,但长期来看效果有限。网络协议领域引入了 GREASE(Generate Random Extensions And Sustain Extensibility)概念 [I-D.ietf-tls-grease],我们可借鉴其应对方案。

用户代理品牌包含多于一个条目时,可促进品牌列表的标准化处理。通过随机添加额外的、故意错误的、逗号分隔的条目并任意排序,可降低我们对少数必需字符串的依赖。

下面举几个例子:

用户代理必须在品牌中包含多于一个值,其中一个条目必须为任意值。

品牌中的值顺序必须随时间变化,以防接收方依赖固定位置的特定值。

选择 GREASE 策略时,用户代理应关注缓存差异和分析用例,最大限度减少同版本用户代理的差异。

注意: 最小化缓存和分析差异的一种方法是,在构建时确定 UA 集的 GREASE 部分,并在整个主版本生命周期内保持一致。

7.3. 'Sec-CH-' 前缀

限制用户态 JavaScript 代码影响和修改 UA-CH 头具有多种安全优势。同时,目前看不到任何需要这种用户态重写的实际用例

因此,基于与 TAG 的讨论,合理做法是禁止通过 JavaScript(如 fetch 或 Service Worker)写入这些头,将其标记为浏览器控制的客户端提示,以便文档化和在请求中包含而不会触发 CORS 预检。

故本规范定义的请求头均包含 Sec-CH- 前缀。

8. IANA 注意事项

本文档旨在定义 Sec-CH-UASec-CH-UA-ArchSec-CH-UA-BitnessSec-CH-UA-Form-FactorsSec-CH-UA-Full-VersionSec-CH-UA-MobileSec-CH-UA-ModelSec-CH-UA-PlatformSec-CH-UA-Platform-VersionSec-CH-UA-WoW64 以及 Sec-CH-UA-Full-Version-List HTTP 请求头字段,并将它们注册到永久消息头字段注册表([RFC3864])。

同时还打算弃用 User-Agent 头字段的使用。

8.1. 'Sec-CH-UA' 头字段

头字段名称:Sec-CH-UA

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.1 'Sec-CH-UA' 头字段

8.2. 'Sec-CH-UA-Arch' 头字段

头字段名称:Sec-CH-UA-Arch

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.2 'Sec-CH-UA-Arch' 头字段

8.3. 'Sec-CH-UA-Bitness' 头字段

头字段名称:Sec-CH-UA-Bitness

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.3 'Sec-CH-UA-Bitness' 头字段

8.4. 'Sec-CH-UA-Form-Factors' 头字段

头字段名称:Sec-CH-UA-Form-Factors

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.4 'Sec-CH-UA-Form-Factors' 头字段

8.5. 'Sec-CH-UA-Full-Version' 头字段

头字段名称:Sec-CH-UA-Full-Version

适用协议:http

状态:已废弃

作者/变更控制方:IETF

规范文档:本规范(§ 3.5 'Sec-CH-UA-Full-Version' 头字段

8.6. 'Sec-CH-UA-Full-Version-List' 头字段

头字段名称:Sec-CH-UA-Full-Version-List

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.6 'Sec-CH-UA-Full-Version-List' 头字段

8.7. 'Sec-CH-UA-Mobile' 头字段

头字段名称:Sec-CH-UA-Mobile

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.7 'Sec-CH-UA-Mobile' 头字段

8.8. 'Sec-CH-UA-Model' 头字段

头字段名称:Sec-CH-UA-Model

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.8 'Sec-CH-UA-Model' 头字段

8.9. 'Sec-CH-UA-Platform' 头字段

头字段名称:Sec-CH-UA-Platform

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.9 'Sec-CH-UA-Platform' 头字段

8.10. 'Sec-CH-UA-Platform-Version' 头字段

头字段名称:Sec-CH-UA-Platform-Version

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.10 'Sec-CH-UA-Platform-Version' 头字段

8.11. 'Sec-CH-UA-WoW64' 头字段

头字段名称:Sec-CH-UA-WoW64

适用协议:http

状态:标准

作者/变更控制方:IETF

规范文档:本规范(§ 3.11 'Sec-CH-UA-WoW64' 头字段

8.12. 'User-Agent' 头字段

头字段名称:User-Agent

适用协议:http

状态:已废弃

作者/变更控制方:IETF

规范文档:本规范(§ 7.1 'User-Agent' 头字段),以及 [rfc9110] 第 5.5.3 节

9. 致谢

感谢 Aaron Tagliaboschi、Ali Beyad、ArkUmbra、Dustin Mitchell、Erik Anderson、jasonwee、Luke Williams、Mike West、Martin Thomson 和 Toru Kobayashi 对本规范的宝贵反馈和贡献。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语组合表达。规范正文中的关键词 “MUST”、 “MUST NOT”、 “REQUIRED”、 “SHALL”、 “SHALL NOT”、 “SHOULD”、 “SHOULD NOT”、 “RECOMMENDED”、 “MAY” 和 “OPTIONAL” 应按 RFC 2119 的描述进行解释。不过,为了便于阅读,本规范未将这些词全部大写。

除明确标注为非规范性、示例和备注的章节外,本文档的所有文字均为规范性内容。[RFC2119]

规范中的示例通过 “for example” 引入,或通过 class="example" 与规范性文本区分,如下所示:

这是一个说明性示例。

说明性备注以 “Note” 开头,并通过 class="note" 与规范性文本区分,如下所示:

注意,这是一个说明性备注。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[CLIENT-HINTS-INFRASTRUCTURE]
Client Hints Infrastructure. Draft Community Group Report. URL: https://wicg.github.io/client-hints-infrastructure/
[COMPAT]
Mike Taylor. Compatibility Standard. Living Standard. URL: https://compat.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC8942]
I. Grigorik; Y. Weiss. HTTP Client Hints. February 2021. Experimental. URL: https://www.rfc-editor.org/rfc/rfc8942
[RFC9651]
M. Nottingham; P-H. Kamp. Structured Field Values for HTTP. September 2024. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc9651
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

说明性引用

[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[FacebookYearClass]
Chris Marra; Daniel Weaver. Year class: A classification system for Android. URL: https://engineering.fb.com/android/year-class-a-classification-system-for-android/
[FINGERPRINTING-GUIDANCE]
Nick Doty; Tom Ritter. Mitigating Browser Fingerprinting in Web Specifications. URL: https://w3c.github.io/fingerprinting-guidance/
[I-D.ietf-tls-grease]
David Benjamin. Applying GREASE to TLS Extensibility. ID. URL: https://tools.ietf.org/html/draft-ietf-tls-grease
[Janc2014]
Artur Janc; Michal Zalweski. Technical analysis of client identification mechanisms. URL: https://dev.chromium.org/Home/chromium-security/client-identification-mechanisms#TOC-Browser-level-fingerprints
[RFC3864]
G. Klyne; M. Nottingham; J. Mogul. Registration Procedures for Message Header Fields. September 2004. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc3864
[RFC9110]
R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed.. HTTP Semantics. June 2022. Internet Standard. URL: https://httpwg.org/specs/rfc9110.html
[Rossi2015]
The Microsoft Edge Rendering Engine that makes the Web just work. URL: https://channel9.msdn.com/Events/WebPlatformSummit/2015/The-Microsoft-Edge-Rendering-Engine-that-makes-the-Web-just-work#time=9m45s

IDL 索引

dictionary NavigatorUABrandVersion {
  DOMString brand;
  DOMString version;
};

dictionary UADataValues {
  DOMString architecture;
  DOMString bitness;
  sequence<NavigatorUABrandVersion> brands;
  sequence<DOMString> formFactors;
  sequence<NavigatorUABrandVersion> fullVersionList;
  DOMString model;
  boolean mobile;
  DOMString platform;
  DOMString platformVersion;
  DOMString uaFullVersion; // deprecated in favor of fullVersionList
  boolean wow64;
};

dictionary UALowEntropyJSON {
  sequence<NavigatorUABrandVersion> brands;
  boolean mobile;
  DOMString platform;
};

[Exposed=(Window,Worker)]
interface NavigatorUAData {
  readonly attribute FrozenArray<NavigatorUABrandVersion> brands;
  readonly attribute boolean mobile;
  readonly attribute DOMString platform;
  Promise<UADataValues> getHighEntropyValues(sequence<DOMString> hints);
  UALowEntropyJSON toJSON();
};

interface mixin NavigatorUA {
  [SecureContext] readonly attribute NavigatorUAData userAgentData;
};

Navigator includes NavigatorUA;
WorkerNavigator includes NavigatorUA;