这篇博文介绍了如何借助最近推出的问题标签页,实现开发者工具对调试内容安全政策 (CSP) 问题的支持。
实现工作是在两次实习期间完成的: 1. 在第一阶段,我们构建了通用报告框架,并针对 3 个 CSP 违规问题设计了问题消息。 2. 在第二阶段,我们添加了可信类型问题,以及一些专门用于可信类型调试的开发者工具功能。
什么是内容安全政策?
内容安全政策 (CSP) 允许限制网站中的某些行为,以提高安全性。例如,CSP 可用于禁止内嵌脚本或禁止 eval,这两者都可以减少跨站脚本攻击 (XSS) 的攻击面。如需详细了解 CSP,请点击此处。
可信类型(TT) 政策是一种特别新颖的 CSP,可实现动态分析,从而系统性地防范网站上的大量注入攻击。为此,TT 支持网站监管其 JavaScript 代码,仅允许将特定类型的内容分配给 innerHTML 等 DOM 接收器。
网站可以通过添加特定的 HTTP 标头来启用内容安全政策。例如,标头 content-security-policy: require-trusted-types-for 'script'; trusted-types default 会为页面激活 TT 政策。
每项政策都可以在以下任一模式下运行:
- 实施模式 - 每次违反政策都会导致错误,
- 仅报告模式 - 将出错提示报告为警告,但不会导致网页出现故障。
问题标签页中的“实施内容安全政策”问题
这项工作的目标是改善 CSP 问题的调试体验。在考虑新问题时,开发者工具团队大致遵循以下流程:
- 定义用户故事。在 DevTools 前端中确定一组用户故事,这些故事涵盖了 Web 开发者需要如何调查问题。
- 前端实现。根据用户故事,确定在前端调查问题时需要哪些信息(例如相关请求、Cookie 的名称、脚本或 HTML 文件中的某一行等)。
- 问题检测。确定浏览器中可在 Chrome 中检测到问题的位置,并对该位置进行插桩,以报告问题,包括第 (2) 步中的相关信息。
- 保存并显示问题。将问题存储在适当的位置,并在开发者工具打开后将其提供给开发者工具
- 设计问题文本。想出一段解释性文字,帮助 Web 开发者理解并更重要的是解决问题
第 1 步:为 CSP 问题定义用户故事
在开始实施工作之前,我们创建了一份包含用户故事的设计文档,以便更好地了解我们需要做什么。例如,我们写下了以下用户故事:
作为一名开发者,我刚刚意识到我网站的某些部分被屏蔽了,我想: - …了解 CSP 是否是我网站上的 iframe / 图片被屏蔽的原因 - …了解哪个 CSP 指令导致了特定资源被屏蔽 - …了解如何更改我网站的 CSP,以允许显示当前被屏蔽的资源 / 执行当前被屏蔽的 js。
为了探索这个用户故事,我们创建了一些简单的示例网页,这些网页展示了我们感兴趣的 CSP 违规行为,并探索了这些示例网页,以便我们自己熟悉该过程。 以下是一些示例网页(打开演示页面,并打开问题标签页):
通过此流程,我们了解到,来源位置是调试 CSP 问题时最重要的信息。我们还发现,在资源被屏蔽时,快速找到关联的 iframe 和请求非常有用,并且直接链接到 DevTools 的 Elements 面板中的 HTML 元素也很有用。
第 2 步:前端实现
我们将这一洞见转化为我们希望通过 Chrome DevTools Protocol (CDP) 向开发者工具提供的信息的初稿:
以下是 third_party/blink/public/devtools_protocol/browser_protocol.pdl 中的摘录
type ContentSecurityPolicyIssueDetails extends object
properties
# The url not included in allowed sources.
optional string blockedURL
# Specific directive that is violated, causing the CSP issue.
string violatedDirective
boolean isReportOnly
ContentSecurityPolicyViolationType contentSecurityPolicyViolationType
optional AffectedFrame frameAncestor
optional SourceCodeLocation sourceCodeLocation
optional DOM.BackendNodeId violatingNodeId
上述定义本质上是对 JSON 数据结构进行编码。它采用一种名为 PDL(协议数据语言)的简单语言编写。PDL 有两个用途。首先,我们使用 PDL 生成 DevTools 前端所依赖的 TypeScript 定义。例如,上述 PDL 定义会生成以下 TypeScript 接口:
export interface ContentSecurityPolicyIssueDetails {
/**
* The url not included in allowed sources.
*/
blockedURL?: string;
/**
* Specific directive that is violated, causing the CSP issue.
*/
violatedDirective: string;
isReportOnly: boolean;
contentSecurityPolicyViolationType: ContentSecurityPolicyViolationType;
frameAncestor?: AffectedFrame;
sourceCodeLocation?: SourceCodeLocation;
violatingNodeId?: DOM.BackendNodeId;
}
其次,可能更重要的是,我们根据定义生成一个 C++ 库,该库负责生成这些数据结构并将其从 C++ Chromium 后端发送到 DevTools 前端。使用该库,您可以通过以下 C++ 代码创建 ContentSecurityPolicyIssueDetails 对象:
protocol::Audits::ContentSecurityPolicyIssueDetails::create()
.setViolatedDirective(d->violated_directive)
.setIsReportOnly(d->is_report_only)
.setContentSecurityPolicyViolationType(BuildViolationType(
d->content_security_policy_violation_type)))
.build();
确定要提供哪些信息后,我们需要探索如何从 Chromium 获取这些信息。
第 3 步:问题检测
为了以最后一节中所述的格式向 Chrome 开发者工具协议 (CDP) 提供这些信息,我们需要找到这些信息在后端中的实际位置。幸运的是,CSP 代码已经有一个用于“仅报告”模式的瓶颈,我们可以利用它:ContentSecurityPolicy::ReportViolation 会向可在 CSP HTTP 标头中配置的(可选)报告端点报告问题。我们想要报告的大部分信息都已存在,因此无需对后端进行重大更改,我们的插桩即可正常运行。
第 4 步:保存并显示问题
一个稍微复杂的问题是,我们还希望报告在开发者工具打开之前发生的问题,这与处理控制台消息的方式类似。这意味着,我们不会直接向前端报告问题,而是使用一个存储空间,无论开发者工具是否打开,该存储空间都会填充问题。打开开发者工具(或附加任何其他 CDP 客户端)后,可以从存储空间中重放之前记录的所有问题。
至此,后端工作已完成,我们现在需要专注于如何在前端显示问题。
第 5 步:设计问题文本
设计问题文本的过程涉及我们团队之外的多个团队,例如,我们经常依赖于实现功能的团队(在本例中为 CSP 团队)以及 DevRel 团队的洞见,后者负责设计 Web 开发者应如何处理特定类型的问题。问题文本通常会经过一些优化,直到完成。
通常,开发者工具团队会先根据他们的设想绘制一份草图:
## Header
Content Security Policy: include all sources of your resources in content security policy header to improve the functioning of your site
## General information
Even though some sources are included in the content security policy header, some resources accessed by your site like images, stylesheets or scripts originate from sources not included in content security policy directives.
Usage of content from not included sources is restricted to strengthen the security of your entire site.
## Specific information
### VIOLATED DIRECTIVES
`img-src 'self'`
### BLOCKED URLs
https://imgur.com/JuXCo1p.jpg
## Specific information
https://web.dev/strict-csp/
经过迭代后,我们最终确定了以下方案:

如您所见,让功能团队和 DevRel 参与进来,可以使说明更加清晰、准确!
您还可以在专门用于显示 CSP 违规问题的标签页中发现网页上的 CSP 问题。
调试可信类型问题
如果没有合适的开发者工具,大规模使用 TT 可能会面临挑战。
改进了控制台打印
在处理可信对象时,我们希望至少显示与不可信对象相同数量的信息。遗憾的是,目前在显示受信任对象时,不会显示有关封装对象的信息。
这是因为控制台中显示的值默认取自对对象调用 .valueOf() 的结果。不过,对于可信类型,返回的值用处不大。相反,我们希望获得与调用 .toString() 时类似的结果。为此,我们需要修改 V8 和 Blink,以便为可信类型对象引入特殊处理。
虽然由于历史原因,这种自定义处理是在 V8 中完成的,但这种方法存在重大缺点。有许多对象需要自定义显示,但其类型在 JS 级别是相同的。由于 V8 是纯 JS,因此无法区分与 Web API(例如 Trusted Type)对应的概念。因此,V8 必须向其嵌入器 (Blink) 寻求帮助,以区分它们。
因此,将这部分代码移至 Blink 或任何嵌入器似乎是合乎逻辑的选择。除了暴露问题之外,还有许多其他好处:
- 每个嵌入器都可以有自己的说明生成
- 通过 Blink API 生成说明要容易得多
- Blink 有权访问对象的原始定义。因此,如果我们使用
.toString()生成说明,则不存在.toString()可能被重新定义的风险。
违规时中断(在仅报告模式下)
目前,调试 TT 违规行为的唯一方法是在 JS 异常上设置断点。由于强制执行的 TT 违规行为会触发异常,因此此功能在某种程度上很有用。不过,在实际场景中,您需要更精细地控制 TT 违规行为。具体而言,我们希望仅在发生 TT 违规(而非其他异常)时中断,在仅报告模式下也中断,并区分不同类型的 TT 违规。
开发者工具已经支持各种断点,因此该架构具有很强的可扩展性。添加新的断点类型需要更改后端 (Blink)、CDP 和前端。
我们应该引入一个新的 CDP 命令,我们将其命名为 setBreakOnTTViolation。前端将使用此命令来告知后端应在何种 TT 违规行为上中断。后端(特别是 InspectorDOMDebuggerAgent)将提供一个“探测器”onTTViolation(),每次发生 TT 违规时都会调用该探测器。然后,InspectorDOMDebuggerAgent 会检查该违规行为是否应触发断点,如果是,则会向前端发送消息以暂停执行。
已完成的工作和后续计划
自本文所述问题出现以来,问题标签页发生了很大变化:
- 它与开发者工具中其他面板的互联性得到了改进。
- 许多其他问题的报告已移至问题标签页:低对比度、Trusted Web Activity、Quirks 模式、Attribution Reporting API 和与 CORS 相关的问题等。
- 引入了隐藏问题的机会
今后,我们计划使用问题标签页来显示更多问题,从长远来看,这将有助于减轻控制台在处理难以理解的错误消息流方面的负担。
下载预览渠道
考虑将 Chrome Canary、开发者版或 Beta 版用作默认开发浏览器。通过这些预览渠道,您可以抢先体验最新的开发者工具功能,测试前沿的 Web 平台 API,并在用户发现问题之前找出网站上的问题!
与 Chrome 开发者工具团队联系
您可以通过以下方式讨论新功能、更新或与开发者工具有关的任何其他内容。
- 请通过 crbug.com 向我们提交反馈和功能请求。
- 在开发者工具中,依次点击 更多选项 > 帮助 > 报告开发者工具问题,即可报告开发者工具问题。
- 在 Twitter 上发帖并提及 @ChromeDevTools。
- 在 YouTube 视频“开发者工具的新变化”或 YouTube 视频“开发者工具提示”中发表评论。