1. 引言
开发者希望控制加载到其页面上下文中的资源,以及其页面可以向其发出请求的 端点。这种控制出于若干目的而必要, 包括限制用户数据可通过用户代理流动的方式(缓解 外泄攻击),以及确保对站点架构和依赖项的控制。
内容安全策略满足了其中一部分需求,但其方式比最关键用例所需的更细粒度, 并且语法和文法也因 CSP 用于部署的其他保护措施而变得复杂。 [CSP]
`Connection-Allowlist`
从 CSP 中退后一步,专注于单一用例:控制
页面可通过 Fetch 和其他 Web 平台 API(WebRTC、Web
Transport、FedCM、Web Payments、DNS Prefetch 等)发起的显式请求,
其方式旨在直观而全面。
NOTE: '\' line wrapping per RFC 8792
Connection-Allowlist: (response-origin "https://cdn.example" "https://*.example.:tld" \
"https://api.example:*"); report-to=ReportingAPIEndpoint
此标头随用户导航到的文档一起交付时,会将该
文档限制为仅允许向匹配列表中指定 URL 模式
[URLPATTERN]
的端点发出请求和连接:交付该文档的源、
https://cdn.example、倒数第二个 DNS 标签为
example 的任何主机的任何子域、
任意端口上的 https://api.example,等等。
尝试连接到不匹配允许列表的端点将被阻止,并通过
在 report-to 参数中指定的 Reporting API [REPORTING] 端点进行报告(并
通过单独的 Reporting-Endpoints 标头定义)。
1.1. 威胁模型
本提案有意保持小而聚焦,面向客户端攻击 和/或配置错误中一个具体但有用的细分领域:
-
文档和 worker 的策略将由服务器以 HTTP 响应标头的形式声明。这 意味着能够操纵响应标头的攻击者仍然不在范围内。
-
文档(或 worker)所声明的策略只管辖由 _该_ 上下文发起的请求。 如果被框架嵌入的文档声明了不同的策略,那就按其声明处理(但需注意,该策略 将应用于通过本地方案(
data:、about:等)创建的上下文), 类似于上下文的 策略容器中的其他 组件,这些组件由 HTML 在创建新的文档/worker 上下文时处理。 -
连接和/或请求是本提案旨在防御的威胁。要作为 外泄防御有效,我们必须在连接建立之前阻止它们。
-
优秀的 Web 平台 API 的意外效果可能带来大量侧信道。 本提案不试图解决它们,而是仅关注 用户代理代表页面显式发起的那些请求或连接。这 当然包括
fetch()和XMLHttpRequest这些明确案例, 以及通常的资源 请求。它还包括通过不那么显式称为“请求”的通道建立的网络连接: 通过 Web Install API 获取的清单、通过 WebRTC 连接到 TURN/STUN 服务器、Web Transport 通道、DNS 预取、导航,等等。 这些都在范围内,而内存或 CPU 消耗、套接字 耗尽以及一般意义上的 XSLeaks 等更冷门的通道则不在范围内。 -
本提案只处理通信通道。它不旨在防止(甚至不旨在 大幅缓解)内容注入或跨站脚本等威胁。它只能 约束此类攻击在 _那些特定页面_ 上的影响,前提是这些页面部署了该策略, 并且应仅被视为页面防御中的一层;它本身并不足够。
-
尝试防御一部分服务器端威胁(例如开放重定向)很有诱惑力。 但我们很难以有效方式做到这一点,因为离开客户端的数据 本质上已经处于服务器控制之下。也就是说,允许开发者 选择客户端是否直接配合服务器通过重定向响应创建 到其他服务器连接的决定,似乎是合理的。我们将提供两个选项, 如果未来需要更细粒度控制,也留有潜在扩展空间:默认情况下, 无论目标为何,重定向响应都将被阻止。开发者可以通过在标头上设置参数 来选择允许所有重定向响应。详见 § 5.5 重定向。
-
同样,WebRTC 连接也很难通过 URL 模式来约束,因为它们通常涉及 动态端点发现和点对点连接。我们将提供一个全局开关,允许 开发者选择完全允许或阻止 WebRTC 连接。详见 § 5.6 WebRTC。
1.2. 与内容安全策略的重叠
本提案与内容安全策略对给定上下文内资源使用施加限制的方式有很多共同点, 尤其是 fetch 指令。不过,出于以下几个原因,探索它似乎 是合理的:
-
CSP 的模型过于细粒度:希望缓解数据从敏感上下文流出的风险的开发者 需要一种保护措施,能够穷尽覆盖所有可能发出 请求或建立连接的方式。CSP 将请求分类为可被 单独控制的类型,这不是处理该问题的正确方式,因为数据通过 Web 字体请求泄漏与通过图片 或脚本请求泄漏一样糟糕。区分这些请求类型会以根本无关的问题 使设计合理防御的过程复杂化。
-
CSP 的语法又不够细粒度:CSP 支持的
host-source文法导致 响应中交付的标头非常冗长。一个独立策略提供了 转向 URLPattern 语法的机会;这种语法可以通过提供更现代、 更灵活且标准化的匹配语法,解决人们对 CSP 方式提出的一些抱怨。 -
CSP 的覆盖范围不完整:虽然 CSP 很好地覆盖了通过 Fetch 运行的 HTTP 请求,但它并没有穷尽覆盖 Web 平台 API 允许建立连接的无数方式。DNS 预取和 WebRTC 是很好的起点, 但还有许多其他机制一直难以明确它们究竟如何适配 CSP 的 威胁模型。通过创建一个聚焦范围较窄并向开发者作出明确承诺的新策略, 这些讨论将拥有可辩护的答案,并向规范作者提供清晰的授权。
2. 连接允许列表
连接 允许列表表示给定上下文 被允许连接到的一组 URL 模式。 它是一个包含以下 项的 结构体:
-
reporting endpoint,它要么是
null,要么是 Reporting API 端点。除非另有说明,否则它是null。 -
disposition,它要么是 enforce,要么是 report。
-
redirects,它要么是 allow,要么是 block。除非另有 说明,否则它是 block。
-
webrtc,它要么是 allow,要么是 block。除非另有说明,否则它是 block。
3. 连接允许列表标头
Connection-Allowlist 响应 标头包含 一组序列化的 URL 模式字符串,这些字符串定义上下文被允许 连接到的端点集合。该允许列表会针对给定上下文执行,阻止不匹配 所声明模式的出站连接。Connection-Allowlist-Report-Only 响应 标头是只报告变体,以相同方式解析,但只 发送违规 报告,而不阻止出站连接。
这些 Connection Allowlist 标头是 结构化标头,其值是一个由 列表组成的 内层 列表。服务器可以交付包含 任意数量项的列表,但只会使用第一个项。列表中的任何额外项 都会被忽略。
内层列表可以包含序列化为
字符串的 URL Patterns,或 token
response-origin,它表示
一个匹配
响应的 URL 的
源的模式。意外的值将被忽略。
-
report-to参数的值 将被解析为一个 token,表示 Reporting API 端点 [REPORTING]。 -
redirects参数的值 将被解析为一个 token。如果存在该参数,它将用于设置允许列表的 redirects。如果值是 tokenblock,它将被设置为 block,否则 设置为 allow。 -
webrtc参数的值将被 解析 为一个 token。如果存在该参数,它将用于设置允许列表的 webrtc。如果值是 tokenblock,它将被设置为 block,否则 设置为 allow。
所有其他参数都将被忽略。
3.1. 解析
-
令 allowlists 为空 列表。
-
令 header 为从 response 的 标头列表中, 以 列表形式 获取结构化字段值 名为 `
Connection-Allowlist` 的结果。 -
给定 header、response 的 URL,以及 enforce, 解析 Connection Allowlist 标头。如果 结果不是
null,则将其插入 allowlists。 -
令 header 为从 response 的 标头列表中, 以 列表形式 获取结构化字段值 名为 `
Connection-Allowlist-Report-Only` 的结果。 -
给定 header、response 的 URL,以及 report, 解析 Connection Allowlist 标头。如果 结果不是
null,则将其插入 allowlists。 -
返回 allowlists。
-
如果 list 的 大小为 0,返回
null。 -
如果 list[0] 不是 内层列表,返回
null。 -
令 allowlist 为一个 Connection Allowlist,其 disposition 为 disposition。
-
对 list[0] 中的每个 item 执行:
-
令 serialized pattern 为
null。 -
如果 item 是 token
response-origin: -
如果 item 是 字符串,则将 serialized pattern 设置为 item。
-
如果 serialized pattern 为
null,则继续。 -
令 URL pattern 为执行从 HTTP 结构化字段值构建 URL 模式的结果,给定 serialized pattern, 并以
null作为基准 URL。如果此步骤抛出错误,则继续。
-
-
对 list[0] 的 参数中的每个 key → value 执行:
-
如果 key 是
report-to且 value 是一个 token,则将 allowlist 的 reporting endpoint 设置为 value。
-
-
返回 allowlist。
注: 我们会在 解析算法中跳过任何无效输入。我们完全可以在解析上更加严厉, 但那可能会限制我们未来的灵活性。
3.2. 匹配
根据正在建立的连接类型,我们可能有一个 request 可用,
也可能只有一个 URL,甚至可能更少。例如
dns-prefetch
只能针对主机进行匹配。下面的算法说明了连接允许列表
检查在这些场景中的工作方式:
-
对 connection allowlist 的 allowlist 中的每个 pattern 执行:
-
令 input 为一个新的
URLPatternInit字典,其hostname被 设置为 pattern 的 hostname component。 -
令 host-only pattern 为给定 input、以
null作为基准 URL,以及以一个空 map 作为选项来创建 URL 模式的结果。 -
令 synthetic url 为将 "https://" 与 host 拼接后 作为 URL 解析的结果。
-
如果给定 host-only pattern 和 synthetic url 的 URL 模式匹配不 返回
null,则返回 success。
-
-
返回 failure。
注: 通过创建一个只包含 hostname component 的新模式,并为 host 合成一个 URL, 如果允许列表中的 _任何_ 模式可能允许使用任意协议、任意端口、任意路径等 向该主机发出请求,我们就能返回匹配。
-
令 allowlists 为 request 的 policy container 的 connection allowlists。
-
对 allowlists 中的每个 allowlist 执行:
-
如果 allowlist 的 disposition 是 enforce, 则返回 blocked。
-
返回 allowed。
-
对 connection allowlists 中的每个 connection allowlist 执行:
-
如果 host host-matches connection allowlist,则继续。
-
给定 host、environment 和 connection allowlist, 报告违规。
-
如果 connection allowlist 的 disposition 是 enforce, 则返回 blocked。
-
-
返回 allowed。
-
令 allowlists 为 environment 的 policy container 的 connection allowlists。
-
对 allowlists 中的每个 allowlist 执行:
-
返回 allowed。
3.3. 报告
与其他策略机制一样,Connection Allowlists 会将每个违规报告给 允许列表标头中指定的 Reporting API 端点。违规由以下字典 类型表示:
enum {ConnectionAllowlistDisposition ,"enforce" };"report" dictionary :ConnectionAllowlistViolationReport ReportBody {USVString ;url USVString ;connection sequence <DOMString >;allowlist ConnectionAllowlistDisposition ; };disposition
ConnectionAllowlistViolationReport
的
connection
是违反允许列表的连接的
序列化 URL。
ConnectionAllowlistViolationReport
的
allowlist
是被违反的
allowlist。
ConnectionAllowlistViolationReport
的
disposition
是该 allowlist
的
disposition。
webrtc" (resource URL)、一个 environment (environment),以及一个
connection
allowlist (allowlist):
-
如果 allowlist 的 reporting endpoint 是
null,则返回。 -
令 violation 为一个新的
ConnectionAllowlistViolationReport, 初始化如下:
url-
environment 的 creation URL,并剥离以供报告使用。
connection-
如果 resource URL 是一个 URL,则为 resource URL,并剥离以供报告使用。
否则,为 resource URL。
allowlist-
一个新列表,包含对 allowlist 的 allowlist 中每个模式进行序列化的结果
disposition-
allowlist 的 disposition。
-
给定 environment 作为上下文、"
connection-allowlist" 作为 类型、allowlist 的 reporting endpoint 作为 目标,并以 violation 作为数据,生成并排队一个报告。
4. 猴子补丁
4.1. 与 Fetch 集成
我们将通过在 Fetch § 4.1 Main fetch 中 与其他用于相同目的的检查并列添加一个阻塞检查,来处理 requests:
-
如果 should request be blocked due to a bad port、should fetching request be blocked as mixed content、should request be blocked by Content Security Policy、 should request be blocked by Connection Allowlists, 或 should request be blocked by Integrity Policy Policy 返回 blocked,则将 response 设置为网络错误。
Fetch 还在更低层级定义了一些算法,它们用于为 并非基于 requests 的 API 建立连接。我们将钩入 resolve an origin 和 obtain a connection,以处理 DNS 预取、Web Transport 等事项:
- 如果在 origin 的 host、environment 和 allowlists 上执行 should host be blocked by Connection Allowlists 返回 blocked, 则返回 failure。
- 如果在 url、environment 和 allowlists 上 执行 should url be blocked by Connection Allowlists 返回 blocked, 则返回 failure。
对 Fetch 的修改将 要求我们向低层级 算法的调用点传入额外信息,以标识应当使用的允许列表以及 用于报告的上下文。或许更好的做法是要求这些调用点自行执行检查。 我的感觉是,集中化逻辑会让我们更容易成功,但采用 分散处理方式可能更简单。
4.2. 与 HTML 集成
为了将上述内容集成到 HTML 中,我们会向 policy container 结构体添加一个新的 connection allowlists 项, 其中包含一个由 connection allowlists 组成的 列表。它将通过向 create a policy container from a fetch response 算法添加一个 步骤来填充:
-
使用 response 和 result 解析 Integrity-Policy 标头。
- 将 result 的 connection allowlists 设置为 给定 response 后 解析响应的 Connection Allowlists 的结果。
-
返回 result。
注: Early hint 集成不需要进一步 修改,因为在获取 early hint 链接时已经使用了 early response 的 policy container。
4.3. 与 WebRTC 集成
为了约束 WebRTC 连接,[webrtc] 可以在确定候选项是否 被管理性禁止时,调用 should WebRTC be blocked by Connection Allowlists 算法。
4.4. 与 Service Workers 集成
除了默认应用服务 worker 自身的 policy container 之外,
service workers 在使用 WindowClient
API 调用导航时,还需要应用自身的连接允许列表。
navigate()
和 openWindow(url)
算法都必须按如下方式打补丁:
-
如果在 url、this 的 relevant settings object,以及
this
的
relevant settings object 的 policy container 的 connection allowlists 上
执行
should url be
blocked by Connection Allowlists 返回 blocked,
则返回一个以
"SecurityError"DOMException拒绝的 promise。
navigate()
会将当前文档作为导航的 sourceDocument 传递。这看起来有点像一个
疏忽。参见 讨论。
5. 安全与隐私考量
5.1. 同源上下文
§ 1.1 威胁模型中描述的威胁模型有意保持狭窄,
开发者需要
仔细考虑如何将这里描述的允许列表机制分层纳入其防御中。
最显著的是,该机制是上下文特定的,而非源范围的。这给拥有脚本访问能力的攻击者
留下了广泛机会,使其可以通过寻找限制较低的同源
上下文来绕过某个上下文的允许列表。与 HTML 的 policy container 集成可以处理其中一些
可能性,但很可能还会存在其他可能性。例如,攻击者
可能能够沿着框架树向上到达限制较少的父级,或通过
window.open() 弹出一个保留 opener 关系的新窗口。因此,
允许列出文档的源(通过 response-origin 或
显式列出)本身
并不是完整解决方案。
在某些场景中,开发者可以通过
sandbox
属性或内容安全策略的
sandbox
指令,将已设置允许列表的上下文与其正常源隔离开来,从而避免这种风险。
在这些情况下,不会有文档同源,边界也更容易维持。
理想情况下,还应在一定程度上 让开发者能够控制其依赖项的允许列表。一个以类似 required document policy 或 [csp-embedded-enforcement]] 为根的选择加入机制 可能值得探索。[WICG/connection-allowlists Issue #1]
5.2. Service Workers
Service Workers 会像其他同源上下文一样, 使允许列表的故事变得复杂。 因为它们拥有一个与其管理的每个文档都不同的 policy container, 所以它们完全可能响应来自某些文档的消息或请求,而这些文档的允许列表 与服务 worker 的允许列表不同。本提案遵循其他策略的设计, 允许这些能力差异存在。
如果开发者希望约束 service workers 发出请求的能力,他们可以随 worker 脚本一起交付允许列表,但他们需要确保该允许列表是 它可能服务的任何文档的允许列表的超集。
5.3. DNS
Connection Allowlists 旨在缓解通过 DNS 查询进行外泄的风险,它修改 Fetch 的
resolve an origin 算法,以便在发送 DNS 请求之前检查允许列表。这将
同时处理因使用会发送请求的元素和 API 而产生的隐式查询
(img
标签、fetch()
等),也处理由
dns-prefetch
等机制触发的显式查询。
也就是说,允许列表依赖基于 URL 的匹配来确定给定端点是否 可接受。它不是对 DNS 操纵的防御,它不限制请求中涉及的 DNS 服务器 (包括 DNS 解析的递归性质可能与多个服务器 通信),也不限制 CNAME 等构造导致对允许列表之外域名的 额外查询。同样,允许列表也不能保证 DNS 指向的服务器就是你预期的服务器。
开发者应通过依赖经过身份验证的连接来缓解 DNS 劫持和/或重新绑定风险: 只允许列出安全协议会让控制 DNS 的攻击者更难 将流量转移到任意端点,因为 TLS 握手将要求其 拥有相关名称的证书。
我们是否应将 允许列表的模式限制为表示安全协议的模式?或者对于非安全源完全放弃 该标头?
5.4. postMessage(...)
本提案完全关注网络连接,这可能会让开发者感到意外,
因为他们可能期望通过
postMessage(message, options)、
MessageChannel、
BroadcastChannel
等显式通信通道进行的通信也
会被覆盖。将模型扩展为也包括这些机制可能是有意义的,因为它们都适配
可以与允许列表有意义比较的基于源的模型。
5.5. 重定向
默认情况下,Connection Allowlists 会阻止所有重定向。这是一种保守姿态, 旨在防止通过开放重定向或其他服务器端重定向机制进行数据外泄。 如果文档上强制执行允许列表,则任何导致重定向的请求都会被阻止, 除非允许列表明确选择允许它们。
redirects 参数允许
开发者控制
此行为。
如果设置为 block(默认值),任何
重定向链长度大于 1 的请求
都将被阻止。
如果设置为 allow,允许列表将
仅对初始请求强制执行。如果初始请求匹配允许列表,则无论其位置如何,任何后续重定向
都将被允许。此模式将数据安全的责任转移给服务器:一旦请求
被允许离开客户端,服务器就负责确保它不会将用户数据重定向到
不受信任的位置。
这种方式承认不同应用有不同的安全需求。 高敏感应用可以选择阻止所有重定向,而其他应用可以依赖其 受信任端点正确处理重定向。
Connection-Allowlist: ("https://api.example")
对 https://api.example/data 的请求如果返回 302 Found 重定向到
https://api.example/new-data,将被 阻止,因为重定向默认会被阻止。
如果标头改为:
Connection-Allowlist: ("https://api.example");redirects=allow
同一个对 https://api.example/data 的请求将被 允许,而后续重定向
到 https://api.example/new-data(甚至 https://attacker.com/)也将被
允许。
最后,为了向前兼容,未知 token 将被视为 allow:
Connection-Allowlist: ("https://api.example");redirects=some-future-policy
在这种情况下,redirects 参数存在,但其值
some-future-policy 未知。
用户代理会将其视为 allow,并允许该重定向。
这为我们未来引入额外行为提供了可能性,同时不会破坏现有
站点。
5.6. WebRTC
默认情况下,Connection Allowlists 会阻止所有 WebRTC 连接。这是一种保守姿态, 旨在缓解通过 WebRTC 独特网络 特性进行数据外泄的风险,因为单靠 URL 模式很难约束这些特性。
webrtc 参数允许开发者
控制
此行为。
如果设置为 block(默认值),任何
建立 WebRTC 连接的尝试
都将被阻止。
如果设置为 allow,WebRTC 连接
将被
允许。
Connection-Allowlist: ("https://api.example")
任何建立 WebRTC 连接的尝试都将被 阻止,因为 WebRTC 默认会被 阻止。
如果标头改为:
Connection-Allowlist: ("https://api.example"); webrtc=allow
WebRTC 连接将被 允许。
6. 实现考量
6.1. WebSockets
记住 WebSocket 连接由 http 和 https 模式覆盖,而不是
由指定 ws 或 wss 方案的模式覆盖,这一点很有帮助。当我们
建立 WebSocket 连接时,第 1 步
会将这些 websocket 专用方案分别重写为 http 和 https。这
意味着
包含类似 "ws://socket.example" 的模式的允许列表不会产生预期效果,
因为该模式永远不会被资源请求匹配。