Copyright © 2025 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本文档定义了一种机制,使开发者能够为 Web 应用声明网络错误报告策略。用户代理可以使用此策略报告 所遇到的、导致其无法成功获取所请求资源的网络错误。
本节描述本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 https://www.w3.org/TR/ 的 W3C 标准和草案 索引中找到。
本文档由 Web 性能工作 组使用 推荐标准轨道发布为 工作草案。
作为工作草案发布并不 意味着获得 W3C 及其成员的认可。
本文档是一份草案,可能随时由其他 文档更新、取代或废弃。除作为进行中的工作外, 不应引用本文档。
本文档由一个 根据 W3C 专利 政策运作的工作组制定。 W3C 维护着一份 所有专利披露的公开 列表, 这些披露与该工作组的交付成果有关; 该页面还包含 专利披露说明。任何实际 知晓某项专利,并认为该专利包含 必要权利要求 的个人,都必须依照 W3C 专利政策第 6 节披露相关信息。
本文档受 2023 年 11 月 3 日 W3C 流程文档管辖。
准确测量 Web 应用的性能特征,是帮助网站 开发者了解如何改进其 Web 应用的一个重要方面。最糟糕的情况是由于网络错误导致 应用或某个特定资源加载失败,而为了处理此类故障, 开发者需要用户代理提供协助,以确定此类故障发生的时间、位置和原因。
目前,应用开发者无法从最终用户处获得实时的 Web 应用可用性数据。 例如,如果用户由于网络错误(如 DNS 查找失败、 连接超时、连接被重置或其他原因)而无法加载页面,网站开发者就无法检测并处理 此问题。请注意,这类网络错误无法仅在服务器端检测到,因为按照 定义,客户端可能根本无法成功与服务器建立连接。
现有方法(如合成监控)通过在 预先确定的地理位置部署监控节点来提供部分解决方案,但需要额外的基础设施投资,而且无法为 真实最终用户提供真正全球性的近实时可用性数据。
网络错误日志记录(NEL)通过定义一种机制来满足这一需求,使 Web 应用能够声明
一个报告策略,用户代理可使用该策略报告给定源的网络错误。Web
应用通过提供一个 NEL HTTP 响应标头字段来选择使用 NEL,该字段描述所需的 NEL
策略。该策略指示用户代理记录有关向该源发出的请求的信息,并
尝试将这些信息交付到先前使用 Reporting
API 配置的一组端点。顾名思义,NEL 报告主要用于描述错误。
不过,为了确定不同客户端群体中的错误率,我们还必须
知道发生了多少成功请求;这些成功请求也可以通过
NEL 机制进行报告。
例如,如果用户代理由于
TCP 连接中止而无法从 https://www.example.com 获取资源,则用户代理会通过 Reporting API 将以下报告排入队列:
"network-error"report_to 字段配置的端点组{
"referrer": "https://referrer.com/",
"sampling_fraction": 1.0,
"server_ip": "192.0.2.42",
"protocol": "http/1.1",
"elapsed_time": 321,
"phase": "connection",
"type": "tcp.aborted"
}
有关 所传递字段和报告格式的说明,请参阅 5.4 生成网络错误报告;有关 NEL 注册和 报告过程的更多实际示例,请参阅 7. 示例。
除标记为非规范性的章节外,本 规范中的所有编写指南、图表、示例和注释均为非规范性的。本规范中的其他所有内容均为规范性的。
本文档中的关键词 MAY、MUST、MUST NOT、OPTIONAL、REQUIRED 和 SHOULD 仅当它们像此处所示以 全部大写形式出现时,才应按照 BCP 14 [RFC2119] [RFC8174] 中的描述进行解释。
作为算法一部分以祈使句表述的要求(例如“去除所有前导空格字符”或 “返回 false 并中止这些步骤”),应按照引入该算法时所使用的关键词(“must”、 “should”、“may”等)的含义进行解释。
某些一致性要求表述为对属性、方法或对象的要求。此类 要求应解释为对用户代理的要求。
以算法或特定步骤形式表述的一致性要求可以采用任何方式实现,只要 最终结果等效即可。(特别是,本规范中定义的算法旨在 易于理解,而并非旨在具有高性能。)
当 用户代理尝试通过网络针对给定执行 HTTP 网络获取 某个资源的请求时,就会发生一个 网络请求。
如果已知用户
代理处于离线状态(即 navigator.
onLine 返回
false),则请求不得产生网络请求。
如果请求因混合内容或 CORS 失败而被阻止,则该请求不得产生网络请求。任何 CORS 预检请求必须产生其自身的网络请求。
无论使用哪种获取算法以及哪种底层应用和 传输协议,处理一个网络请求都由 以下阶段组成:
唯一强制的阶段是请求和 响应的传输;其他阶段可能并非每个 网络请求都需要。例如,DNS 结果可以在用户代理中本地缓存, 从而省去以后向相同域发出请求时的DNS 解析。 类似地,HTTP 持久连接 允许多个对相同 网络分区键的请求共享已打开的连接。不过,如果发生多个阶段, 它们将按照上述顺序发生。
如果用户代理能够 从服务器接收到有效的 HTTP 响应,并且该响应没有 4xx 或 5xx 状态码,则一个网络请求是成功的。
每个网络错误都有一个类型,它是 一个字符串。
dnsconnectionapplication6. 预定义网络 错误类型中定义了若干预定义的网络错误类型。
网络错误报告是一种描述 网络错误的 Reporting API 报告。
网络错误报告不会对
ReportingObserver 可见。
网络错误报告不会对
ReportingObserver 可见,因为它们仅旨在
对接收这些请求的服务器的管理员或所有者
可见。如果它们对
ReportingObserver 可见,
则这些报告也会对请求的发起方
可见。对于跨源请求,这可能会将有关服务器
网络配置的信息泄露给其控制范围之外的各方。
NEL 策略 指示用户代理是否收集有关向某个源发出的 网络请求的报告,以及在收集时将其发送到何处。 NEL 策略通过 HTTP 响应标头交付给用户代理。
每个NEL 策略都有一个接收 IP 地址,即用户代理从中接收该 NEL 策略的服务器的 IP 地址。
每个NEL 策略都有一个源。
每个NEL 策略都有一个子域标志,
其值为
include 或 exclude。
每个NEL 策略都有一个请求标头列表和一个响应 标头列表,其中每个列表都是标头 名称的列表。
每个NEL 策略都有一个报告组,它是此策略的报告将 被发送到的 Reporting 端点组的名称。
每个NEL 策略都有一个ttl,表示该策略保持有效的 秒数。
每个NEL 策略都有一个创建时间,即用户代理接收到该策略时的 时间戳。
如果从 创建时间到墙上 时钟的不安全当前时间的持续时间 大于 172800 秒(48 小时),则一个NEL 策略是陈旧的。
预计会处理大量流量的源可能 没有能力接收针对向该源发出的每个网络请求的 NEL 报告。 该源可以定义采样 率,以限制每个用户代理 提交的 NEL 报告数量。由于成功请求通常应远远 多于失败请求,因此该源可以分别为二者指定不同的 采样率。
每个NEL 策略都有一个成功采样率,它是 一个介于 0.0 和 1.0 之间(含两端)的数字。
每个NEL 策略都有一个失败采样率,它是一个 介于 0.0 和 1.0 之间(含两端)的数字。
一致的用户代理必须提供一个策略缓存,它是一种 存储机制,用于维护一组NEL 策略,并以 (网络分区键、源)元组作为键。
此存储机制是不透明的、供应商特定的,并且不会暴露给 Web,但它必须提供以下方法,本文档定义的 算法将使用这些方法:
服务器可以通过
NEL
HTTP 响应标头,为其控制的源定义一个NEL
策略。
NEL 响应标头用于
将源的NEL 策略传达给用户代理。NEL
标头的 ABNF(扩充巴科斯-诺尔形式)[RFC5234] 语法
如下:
NEL = json-field-value
标头的值被解释为 JSON 对象数组,如 json-field-value 中所定义。数组中的每个对象都为该源定义一个 NEL 策略。用户代理必须处理数组中的第一个有效 策略,并忽略数组中的所有其他策略。
用户代理必须忽略任何不符合本规范中所定义语法的未知或无效字段或值。一个有效的NEL 标头字段必须至少包含一个对象,并包含本规范中定义的所有“必需”字段。
用户代理必须忽略通过
meta 元素指定的NEL 标头,以缓解通过脚本攻击劫持错误报告的问题。NEL 策略必须通过
NEL
响应标头交付。
对 meta 元素的限制与 [CSP] 规范一致,该规范出于相同原因将
报告注册限制为只能通过 HTTP 标头字段进行。
report_to 成员指定此NEL 策略的报告将被发送到的端点
组。
注册NEL 策略时,report_to 成员是必需的;
如果目的是移除先前的注册,则它是可选的——参见
max_age。如果存在,其值必须是字符串;任何其他类型
都会导致解析错误。
必需的 max_age 成员指定此NEL 策略的
生命周期,以非负整数秒数表示。其值必须是非负整数;任何其他类型都会
导致解析错误。
可选的 include_subdomains 成员是一个
布尔值,它为此
源的所有子域(对子域深度不设限制)启用此NEL 策略。如果对象中不存在名为
include_subdomains 的成员,或者其值
不是 true,则不会为子域启用该NEL 策略。
为确保子域的 NEL 报告能够交付,应用应
确保 Reporting 端点组也启用了
include_subdomains。如果 Reporting 策略没有
启用它,并且某个给定子域也没有单独的 Reporting 策略,
则即使 NEL
策略包含该子域,也不会交付该子域的 NEL 报告。
可选的 success_fraction 成员定义
应应用于有关此源的
成功网络请求报告的
采样率。
如果存在,
其值必须是介于 0.0 和
1.0 之间(含两端)的数字;任何其他值都会导致解析
错误。如果不存在此成员,则用户代理将不会
收集有关此源的成功网络请求的 NEL 报告。
可选的 failure_fraction 成员定义
应应用于有关此源的
失败网络请求报告的
采样率。如果存在,其
值必须是介于 0.0 和 1.0 之间
(含两端)的数字;任何其他值都会导致解析错误。如果不存在此成员,
用户代理将收集有关此源的
所有失败网络请求的 NEL 报告。
可选的 request_headers 成员定义
一个请求标头列表,这些标头的名称和值将包含在有关此源的网络错误报告中。如果存在,其值必须
是一个字符串
列表。
可选的 response_headers 成员定义
一个响应标头列表,这些标头的
名称和值将包含在有关此源的网络错误报告中。如果存在,其值必须
是一个字符串
列表。
给定一个网络请求(request)及其对应的 响应(response),此算法为 request 的NEL 策略提取其源,并相应地更新 策略缓存。
NEL 的响应标头的值。
max_age 的成员,或者该
成员的值不是数字,则中止这些步骤。
max_age 成员的值为
0,则从策略
缓存中移除其源为
origin 的任何NEL 策略,并跳过剩余步骤。
report_to 的成员,或者该
成员的值不是字符串,则中止这些步骤。
success_fraction 的成员,
且其
值不是 0.0 到 1.0(含两端)范围内的数字,则中止这些
步骤。
failure_fraction 的成员,
且其
值不是 0.0 到 1.0(含两端)范围内的数字,则中止这些
步骤。
request_headers 的成员,且其
值不是列表,或者该列表中的任何元素不是字符串,
则中止这些步骤。
response_headers 的成员,
且其
值不是列表,或者该列表中的任何元素不是字符串,
则中止这些步骤。
令 policy 为一个新的NEL 策略,其属性 设置如下:
在 [FETCH] 中更明确地传递此信息。
include_subdomains 且值为
true 的成员,则为 include,
否则为 exclude
request_headers
成员的值
response_headers 成员的值
report_to 成员的值max_age 成员的值success_fraction 成员的值;
否则为 0.0
failure_fraction 成员的值;
否则为 1.0
给定一个网络请求(request)及其对应的 响应(response),如果任何匹配的NEL 策略指示这样做,则此算法生成一份关于 request 的报告, 并返回该报告和 NEL 策略。否则,此 算法返回 null。
Potentially Trustworthy,则返回
null。
no policy,则返回 null。
phase 属性不是
dns,则将以下属性追加到 report
body:
phase 属性不是
dns 或 connection,则将以下
属性追加到 report body:
referrermethodrequest_headersresponse_headersstatus_code0。
include,并且 report
body 的 phase 属性不是 dns,
则返回 null。
phase 属性不是
dns,且 report body 的 server_ip
属性非空并且不等于 policy 的接收
IP 地址:
phase 设置为
dns。
type 设置为
dns.address_changed。
request_headers、
response_headers、status_code 和
elapsed_time 属性。
如果服务器与策略的 IP 地址不匹配, 此步骤会“降级”NEL 报告。 这是一种隐私保护措施,可确保 NEL 报告只发送 给报告所描述服务的所有者。如果 IP 地址不匹配,则用户代理只能验证 NEL 策略是由源的 域名所有者发送的;它无法验证 该策略是否由此域名解析到的服务器的所有者发送。因此,我们 将报告降级为仅包含有关 DNS 解析的信息。有关更多详细信息,请参阅9. 隐私 注意事项和 7.5 具有多个 IP 地址的源。
给定一个 ECMAScript 对象(report body,通常由 生成网络错误 报告返回,然后由调用规范扩充)、与之 匹配的NEL 策略(policy)以及网络请求 (request),此算法将报告排入交付队列。
用户代理可以使用自定义网络错误
类型扩展此列表——例如,
为了适应新协议,或者提供现有协议的更详细错误
描述。这样做时,用户代理应该
对类型名称遵循以点分隔的模式
([group].[optional-subgroup].[error-name]),以便
简单且一致地处理错误报告——例如,收集器可以按类别
和/或一个或多个子组进行聚合。
本节中的所有网络错误都发生在DNS
解析期间,因此其阶段
为 dns。
dns.unreachabledns.name_not_resolveddns.faileddns.address_changed
本节中的所有网络错误都发生在安全
连接建立期间,因此其
阶段为 connection。
tcp.timed_outtcp.closedtcp.resettcp.refusedtcp.abortedtcp.address_invalidtcp.address_unreachabletcp.failedtls.version_or_cipher_mismatchtls.bad_client_auth_certtls.cert.name_invalidtls.cert.date_invalidtls.cert.authority_invalidtls.cert.invalidtls.cert.revokedtls.cert.pinned_key_not_in_cert_chaintls.protocol.errortls.failed
本节中的所有网络错误都发生在
请求和响应的传输期间,
因此其阶段为 application。
http.errorhttp.protocol.errorhttp.response.invalidhttp.response.redirect_loophttp.failedabandonedunknown> GET / HTTP/1.1
> Host: example.com
< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
"endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000}
此 NEL 标头定义了一个NEL 策略,指示
用户代理将有关 example.com 的网络错误报告
给名为 network-errors 的端点组。该
策略适用 2592000 秒(30 天)。
请注意,只有当响应来自可能可信的源时,上述注册才会成功。
> GET / HTTP/1.1
> Host: example.com
< HTTP/1.1 200 OK
< ...
< NEL: {"max_age": 0}
此 NEL 标头指示用户代理移除
example.com 的任何现有NEL 策略。
本节包含一些网络错误报告示例,
当具有已注册NEL 策略的源遇到网络错误时,用户代理可能会将这些报告排入队列。这里展示了
[REPORTING] API 在
上传报告时创建的完整报告载荷;该载荷的 body 字段包含
网络错误报告正文。
{
"age": 0,
"type": "network-error",
"url": "https://www.example.com/",
"body": {
"sampling_fraction": 0.5,
"referrer": "http://example.com/",
"server_ip": "2001:DB8:0:0:0:0:0:42",
"protocol": "h2",
"method": "GET",
"request_headers": {},
"response_headers": {},
"status_code": 200,
"elapsed_time": 823,
"phase": "application",
"type": "http.protocol.error"
}
}
此报告表示用户代理尝试从
example.com 导航到 www.example.com,后者
成功解析为 2001:DB8::42。但是,虽然
用户代理通过 HTTP/2(h2)协议从服务器接收到200 响应,
但在交换过程中遇到协议错误,并被迫放弃
导航。用户代理在导航开始 823 毫秒后
中止了导航。最后,用户代理在遇到网络错误后立即发送了此报告
——即报告的 age 为 0。
{
"age": 0,
"type": "network-error",
"url": "https://widget.com/thing.js",
"body": {
"sampling_fraction": 1.0,
"referrer": "https://www.example.com/",
"server_ip": "",
"protocol": "",
"method": "GET",
"request_headers": {},
"response_headers": {},
"status_code": 0,
"elapsed_time": 143,
"phase": "dns",
"type": "dns.name_not_resolved"
}
}
上述报告表示用户代理尝试从
https://www.example.com/ 获取
https://widget.com/thing.js。但是,用户代理
无法解析 DNS 名称(widget.com),并且请求
在 143 毫秒后被用户代理中止。由于先前
对 widget.com 的请求交付了有效的NEL
策略,用户代理为此请求生成一个网络错误
报告。该报告在遇到网络错误后立即上传——即报告的 age 为
0。
> GET / HTTP/1.1
> Host: example.com
< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
"endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000, "include_subdomains": true}
此 NEL 标头使
example.com 的所有者能够检测其
DNS 服务器何时配置错误——例如,当他们忘记添加一条新的
资源记录,将 new-subdomain.example.com 解析为
IP 地址时。如果用户代理尝试向
new-subdomain.example.com 发出请求,它可能会生成以下
报告:
{
"age": 0,
"type": "network-error",
"url": "https://new-subdomain.example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {},
"response_headers": {},
"status_code": 0,
"elapsed_time": 48,
"phase": "dns",
"type": "dns.name_not_resolved"
}
}
> GET / HTTP/1.1
> Host: example.com
< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
"endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000, "success_fraction": 1.0,
"request_headers": ["If-None-Match"], "response_headers": ["ETag"]}
< ETag: 01234abcd
在此示例中,example.com 的所有者使用
ETag 响应
标头来标识托管在服务器上的不同资源版本。
用户代理随后可以使用
If-None-Match
请求标头来告知服务器客户端当前缓存的是资源的哪个版本,
从而在客户端已有副本仍为最新时,使服务器无需生成
和发送该资源的内容。
通过在此域的 NEL 标头中包含request_headers 和response_headers 字段,
浏览器将在它
为该请求创建的任何 NEL 报告中包含 请求标头
和
If-None-Match 响应标头的副本,
从而使网站所有者能够跟踪其缓存策略的
有效性。
ETag
基于上述内容,请考虑以下事件序列:
用户代理向 example.com 发送一个请求,
并从服务器接收到成功的响应,
其中包含一个指示资源版本的 标头。
用户代理将生成以下 NEL
报告:
ETag
{
"age": 0,
"type": "network-error",
"url": "https://example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "192.0.2.1",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {},
"response_headers": {
"ETag": ["01234abcd"]
},
"status_code": 200,
"elapsed_time": 1392,
"phase": "application",
"type": "ok"
}
}
一段时间后,用户代理再次向
example.com 发送请求。用户代理的本地缓存中仍有
原始资源的副本,并在
请求
标头中包含其版本。服务器检查
该版本,发现其仍为当前版本,并发送
If-None-Match304 响应,告知用户代理其缓存的
资源副本仍然有效。用户代理将生成
以下报告:
{
"age": 0,
"type": "network-error",
"url": "https://example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "192.0.2.1",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {
"If-None-Match": ["01234abcd"]
},
"response_headers": {
"ETag": ["01234abcd"]
},
"status_code": 304,
"elapsed_time": 45,
"phase": "application",
"type": "ok"
}
}
再过一段时间,用户代理又向
example.com 发送了一个请求。用户代理的本地缓存中仍有同一个
资源副本,并像上一个
示例一样,在 请求
标头中包含其版本。不过,这一次服务器发现有新的
资源版本可用。它生成此
资源的内容并将其发送给客户端,同时将新版本编码
到新的If-None-Match 响应标头值中。用户
代理将生成以下报告:
ETag
{
"age": 0,
"type": "network-error",
"url": "https://example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "192.0.2.1",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {
"If-None-Match": ["01234abcd"]
},
"response_headers": {
"ETag": ["56789ef01"]
},
"status_code": 200,
"elapsed_time": 935,
"phase": "application",
"type": "ok"
}
}
对于其域名解析为多个 IP 地址的源,NEL 有时会“降级”错误报告,提供 较少的错误原因信息,因为它无法验证 源的所有者是否与处理请求的 服务器所有者相同。
例如,假设 example.com 由三台
服务器处理,每台服务器具有不同的 IP 地址。服务所有者
配置 DNS,将 example.com 解析为
192.0.2.1、192.0.2.2 和
192.0.2.3,并依赖用户代理在
这三个 IP 地址之间均衡其请求。服务所有者交付
以下NEL 策略:
> GET / HTTP/1.1
> Host: example.com
< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
"endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000,
"success_fraction": 1.0, "failure_fraction": 1.0}
基于上述内容,请考虑以下事件序列:
用户代理向 192.0.2.1 发送一个请求,
并从服务器接收到成功的响应。
此响应包含上述NEL 策略,用户
代理将该策略的接收 IP 地址设置为
192.0.2.1。由于接收 IP 地址
与服务器的 IP 地址匹配(对于任何
成功请求都必须如此),因此它生成以下 NEL 报告:
{
"age": 0,
"type": "network-error",
"url": "https://example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "192.0.2.1",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {},
"response_headers": {},
"status_code": 200,
"elapsed_time": 57,
"phase": "application",
"type": "ok"
}
}
用户代理向
192.0.2.2 发送一个新的请求,并接收到另一个成功的
响应。此响应也包含该NEL 策略,
用户代理将该策略的接收 IP 地址
更新为 192.0.2.2。由于接收 IP 地址
与服务器的 IP 地址匹配(对于任何
成功请求都必须如此),因此它生成以下 NEL 报告:
{
"age": 0,
"type": "network-error",
"url": "https://example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "192.0.2.2",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {},
"response_headers": {},
"status_code": 200,
"elapsed_time": 34,
"phase": "application",
"type": "ok"
}
}
随后,用户代理尝试向
192.0.2.3 发送一个请求,但无法与
服务器建立连接。用户代理的策略缓存中仍有该NEL 策略,理想情况下会使用该策略
为这个失败的
网络请求生成 tcp.timed_out 报告。但是,由于策略的接收
IP 地址(192.0.2.2)与此请求所发送到的 IP
地址不匹配,因此用户代理无法
验证位于 192.0.2.3 的服务器实际上是否归
example.com 的所有者所有。因此,用户代理必须
将报告降级为 dns.address_changed:
{
"age": 0,
"type": "network-error",
"url": "https://example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "192.0.2.3",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {},
"response_headers": {},
"status_code": 0,
"elapsed_time": 0,
"phase": "dns",
"type": "dns.address_changed"
}
}
随后,用户代理又尝试向
192.0.2.1 发送一个请求,但再次无法与
服务器建立连接。尽管用户代理过去某个时候曾从 192.0.2.1 接收到
NEL 策略,
但策略的接收 IP 地址只记录它
最近一次从何处接收——在此情况下为
192.0.2.2。因此,用户代理必须将
报告降级为 dns.address_changed:
{
"age": 0,
"type": "network-error",
"url": "https://example.com/",
"body": {
"sampling_fraction": 1.0,
"server_ip": "192.0.2.1",
"protocol": "http/1.1",
"method": "GET",
"request_headers": {},
"response_headers": {},
"status_code": 0,
"elapsed_time": 0,
"phase": "dns",
"type": "dns.address_changed"
}
}
典型应用需要数十个资源,这些资源的获取通常通过
HTML、CSS 或 JavaScript 发起。请求这些资源的应用可以观察到大多数此类
获取的失败(例如通过 onerror 回调),但它无法访问有关失败原因的详细网络
错误报告——例如 DNS 失败、TCP 错误、TLS 协议违规等。
为解决此问题,应用可以针对获取子资源所来自的 第一方主机,向用户代理注册相关的NEL 策略。然后,如果存在此类策略,并且从具有已注册源的资源遇到网络错误,而该源具有已注册的NEL 策略,则用户 代理将报告详细的网络错误报告,使应用开发者能够调查 该错误。
当资源由第三方嵌入时,资源提供者通常无法
检测和观察失败。例如,如果 example.com 在其网站上嵌入
widget.com/thing.js 资源,而访问 example.com 的用户
因网络错误而无法获取该资源,则 widget.com 主机既不知道
该失败,也无法检测到它。
为解决此问题,widget.com 可以为其主机注册 NEL 策略。然后,如果存在此类策略,
并且在获取资源时遇到网络错误——无论该资源是从
第一方还是第三方源请求的——只要该资源来自具有已注册源的NEL 策略,用户
代理就会报告网络错误,使提供者能够调查该错误。
NEL 提供的网络错误报告可能会暴露有关 用户网络配置的新信息。例如,攻击者可能滥用 NEL 报告来探测用户的网络配置,或扫描 用户内部网络中的服务器。此外,与 HSTS、HPKP 和 固定的 CSP 策略类似,存储的NEL 策略可被用作 “超级 Cookie”,方法是设置一个不同的策略,并使用自定义的(每用户) 报告 URI 充当标识符,与 HTTP Cookie 结合使用(或替代 HTTP Cookie)。
为缓解上述部分风险,NEL 注册仅限于 可能可信的源,网络错误 报告的交付同样仅限于可能可信的源。 这可防止临时 HTTP MITM 轻易滥用 NEL 作为 持久跟踪器。
此外,NEL 策略缓存使用 网络分区键进行分区,因此为某个 网站在一种嵌入上下文中存储的NEL 策略不会在不同上下文中使用 (例如,被不同顶级网站嵌入时)。
NEL 旨在增强现有的服务器端监控。NEL 报告 应仅发送给所请求服务的所有者。对于 发生在DNS 解析期间的错误,仅当NEL 策略是从包含策略源的 域名命名空间树所有者处接收到时,才会生成 NEL 报告。对于 发生在安全连接 建立或 请求和响应的传输期间的错误,仅当 NEL 策略是从服务器的所有者处接收到时,才会生成 NEL 报告,而请求正是 发送到该服务器的。
此理由解释了NEL 策略的接收 IP 地址 和子域标志的处理方式。通过检查 策略的接收 IP 地址是否与 服务器的 IP 地址匹配,NEL 将策略的信任边界扩展为不仅 包括策略的源,还包括 用户代理正在与之通信的特定服务器。这有助于 防止(例如)DNS 重绑定攻击,在这种攻击中,攻击者从其拥有的服务器交付一个 长期有效的NEL 策略,然后更改 其名称服务器,使策略源解析到一个 其无法控制的服务器。如果没有接收 IP 地址验证,这 将导致用户代理把有关第二台服务器的报告发送给 攻击者。
类似地,子域NEL 策略受到 限制,并且只能用于在策略源的子域的DNS 解析阶段期间生成 请求的报告。在此阶段,不存在需要验证所有权的服务器, 而策略是从请求的源的超域接收到这一事实,足以确定 错误的所有权。这使特定部分 域名命名空间树的所有者能够使用 NEL 检测7.3 DNS 配置错误 ,同时防止他们 使用恶意 DNS 条目收集有关其 无法控制的服务器的信息。
为防止信息泄露,有关请求的 NEL 报告
不会包含
服务器在
处理请求时不可见的任何信息。对于DNS 解析期间的错误,
NEL 报告仅包含 DNS 本身可提供的信息。这
可防止服务器滥用 NEL 来收集超出其已有访问权限的更多
用户信息。请注意,NEL 报告将在报告
正文的
server_ip 字段中包含网站的公共 IP 地址,而生成 NEL 标头的服务
可能并不总是知道该地址,例如当其位于负载
均衡器或其他透明 MitM 代理之后时。
除上述限制外,用户代理还必须:
部署 NEL 时,开发者应该考虑交付给指定收集器的 NEL 报告的隐私影响。 例如,报告可能包含带有敏感数据的 URL(例如 “能力 URL”),这些 URL 可能需要采取特殊预防措施(参见 [CAPABILITY-URLS]), 并且可能要求开发者运行自己的 NEL 收集器,以防止将此类 URL 报告给第三 方。
永久消息标头字段注册表应使用以下注册项进行更新([RFC3864]):
NELNEL 响应标头)request)
request)
response)
request)
report)
url)
url)
url)
本文档复用了 [CSP] 和 [RFC6797] 规范中的文本,这是这些 规范的许可证所允许的。此外,衷心感谢 Julia Tuttle、Chris Bentzel、Todd Reifsteck、Aaron Heady 和 Mark Nottingham 对本工作的有益意见和贡献。
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: