浏览器应用负责人的内容安全策略指南
明确责任、报告证据、兼容性检查和回滚边界,规划并维护应用的 CSP。
BotBrowser Team
Content Security Policy(CSP,内容安全策略)是一种应用响应策略,用于告诉支持该策略的浏览器,文档可以加载或执行哪些类型的资源。它通过限制脚本、连接、框架、Worker 等资源,能够降低某些注入错误造成的影响,但不是完整的应用安全方案。服务器授权、输出编码、依赖审查、会话设计、传输安全和事件响应仍是不同的责任。W3C CSP Level 3 规范定义策略模型,MDN 的 CSP 指南说明实际部署选择。
应把 CSP 视为产品与部署决策。应用团队负责有意使用的资源,基础设施团队负责响应交付,各供应商负责各自服务。违规报告只是浏览器对某个请求和策略的观察;它不能证明攻击发生,也不能凭没有报告证明应用安全或业务操作成功。明确这些边界,才能避免把浏览器控制误当成普遍保证。
可据此规划策略设计、责任划分、Report-Only 推进、确定性浏览器检查、兼容性和回滚。示例使用合成页面及有界记录,不涉及为加载不可信内容而削弱策略、收集凭据、规避浏览器控制或改变非自有服务。其他响应头职责见自定义 HTTP 响应头指南,版本证据见浏览器版本验证指南。
明确策略目的与边界
从需要保护的用户流程和必需资源开始盘点。列出脚本、样式、字体、图片、媒体、框架、Worker、WebSocket、fetch 目标、表单和导航目标。也要包含标签管理器、支付、分析、支持组件和 Service Worker 引入的资源。清单应记录资源归属,而非复制浏览器日志。每项都注明业务用途、负责人、数据敏感性、环境和预期使用期限。
区分应用责任边界与浏览器执行边界。script-src 可以限制脚本请求,但不能判定 API 调用是否获准,也不会赋予脚本服务器权限。connect-src 限制浏览器连接,不能替代终端服务的访问控制。框架指令可以限制文档嵌入关系,却不会检查子文档的代码或策略。在确定指令前先记录这些差异。
选择符合自有应用契约的最窄策略。来源尽量明确、路径尽量稳定;有更小清单时不要使用宽泛通配符。注意 scheme、端口、重定向和子域:看似第一方的表达式可能涵盖额外主机。将每个第三方来源视为有负责人和复审机制的依赖。不清楚用途时,不要只为消除违规报告而添加来源。
为每个流程定义通过条件,例如“应用外壳已显示、获准模块已加载、合成的未批准资源已阻止”。这不等于“没有报告,所以应用安全”。记录预期的可见状态、最终文档收到的策略,以及确认应用操作完成所需的其他证据。不要留存秘密、客户内容、完整请求体或无关浏览器属性。
| 策略问题 | 应检查的证据 | 负责人及决策 |
|---|---|---|
| 哪些可执行代码是预期的? | 最终脚本 URL、内联代码清单、部署产物 | 应用负责人批准 nonce、哈希或明确来源 |
| 哪些网络目标不可缺少? | fetch、WebSocket、Worker 和表单目标 | 应用与服务负责人记录准确来源 |
| 哪些嵌入文档可信? | 框架 URL、重定向、子文档负责人 | 产品负责人批准关系与回退 |
| 哪些内容需要报告? | report-to 或 report-uri 配置与保留期 | 安全负责人制定隐私和运营边界 |
选择指令而不削弱信任
策略应表达明确的信任模型,而不是临时修补的集合。default-src 为没有专用指令的资源类型提供默认基线。script-src、style-src、img-src、font-src、connect-src、frame-src、worker-src、media-src、object-src、base-uri、form-action 和 frame-ancestors 分别控制不同的浏览器决策。不同浏览器版本的支持和回退行为可能不同,应在产品声明支持的浏览器中验证。
对可执行代码,应明确批准的内容。Nonce 是每个响应独有的值,由服务器放在获准的内联元素和策略中。哈希将已知内联代码块绑定到其摘要。外部脚本仍需允许来源并纳入依赖管理。不要把 nonce 当成密码,也不要跨不相关响应复用;它只授权当前响应中的元素,不授权 API、用户或供应商账户。
与应用团队共同审查内联脚本和样式授权。宽泛授权可能方便上线,但会降低保护。如果旧框架确实需要内联行为,应记录具体依赖、测试迁移,并把例外限制在必要范围和期限内。不要因为某条报告带来不便就加入 unsafe-eval、unsafe-inline、通配符或整个供应商域名。每项兼容性例外都应有理由、负责人、复审日期和可观察的回退。
其他指令保护不同边界。base-uri 防止意外的 base URL 改变相对地址解析;form-action 限制表单提交;frame-ancestors 限制谁能嵌入该文档,与 frame-src 的含义不同;不需要旧式插件时,object-src 'none' 是常见的加固选择。upgrade-insecure-requests 会改变 URL 处理方式,但不修复所有混合内容集成。启用前应明确每条指令的预期影响。
不要把 CSP 与其他响应头混为一谈。Permissions Policy 控制功能委派,COOP 和 COEP 调整浏览上下文与嵌入资源关系,传输安全响应头针对其他威胁。CSP 可以配合这些控制,却不提供它们的保证。安全上下文指南也说明浏览器安全状态与应用策略并不相同。每种控制都应有单独负责人和验收条件。
使用 Report-Only 收集证据
先在暂存环境或受控生产路由使用 Content-Security-Policy-Report-Only。支持该机制的浏览器会评估候选策略并上报违规,但不会阻止资源。这有助于发现遗漏依赖,却不是安全通过证明。没有报告可能是浏览器不支持、流程没有运行、报告丢失或资源由其他文档处理。应将其解释为“未观察到”,而非“安全”。
为每份候选策略指定版本与负责人,保存策略文本、发出它的路由或模板、目标浏览器、测试流程和复审日期。将报告缩减为文档来源、被阻止 URI 类别、有效指令、处置类型、浏览器系列和场景。删除查询参数、令牌、Cookie、页面文字和其他敏感字段。报告收集器必须是经批准的服务,具有访问控制和明确保留期限。
依据清单逐条分类。被阻止的 URI 可能是有意拒绝、缺失的自有资源、第三方依赖、意外重定向,或并非应用资源的浏览器生成值。应在获准环境中检查最终请求和响应,不要自动加入报告中的来源。确认其负责人、接收的数据、策略兼容性,以及不能加载时用户能看到什么。报告只证明浏览器评估了策略,不证明来源可信。
同时运行正向和负向案例。正向流程验证产品承诺的资源都能加载。负向 fixture 请求一个策略应阻止的合成资源,并检查有界、可见的替代状态。策略头和应用行为都要验证,避免因页面根本没有发出请求而误通过。使用团队控制的测试来源,只保留简短结果和清理状态。
| 阶段 | 浏览器响应 | 保留证据 | 晋级条件 |
|---|---|---|---|
| 盘点 | 尚无候选策略 | 资源负责人和流程 | 每个必要依赖均有负责人 |
| Report-Only | 上报违规但继续加载 | 有版本的策略、脱敏报告和结果 | 每项报告均已分类或明确作为例外接受 |
| 暂存强制 | 可能阻止资源 | 最终响应头、可见结果、兼容性矩阵 | 核心流程在支持的浏览器中通过 |
| 生产强制 | 阻止契约外资源 | 部署收据、监控和回滚产物 | 发布负责人接受剩余兼容性风险 |
运行确定性的资源阻止夹具
以下 fixture 使用自有测试页面和一张有意阻止的图片。它验证可见回退、不保存私人数据,并在断言失败时也移除临时节点。测试页面应经过与候选应用相同的响应头交付路径。预期结果要由测试用例声明,不能从观察结果反推。该 fixture 是浏览器观察,不能证明服务器拒绝请求、攻击被阻止或业务操作已完成。
async function checkCspFallback(page, expectedBlocked) {
const result = await page.evaluate(async expected => {
const host = document.createElement('section');
const image = document.createElement('img');
const status = document.createElement('output');
let violation = false;
host.dataset.test = 'owned-csp-blocked-image';
status.setAttribute('aria-live', 'polite');
status.textContent = '正在等待策略结果';
image.alt = '自有 fixture 资源';
const outcome = new Promise(resolve => {
document.addEventListener(
'securitypolicyviolation',
event => {
if (event.blockedURI.endsWith('/fixtures/csp-owned-image.png')) {
violation = true;
resolve('blocked');
}
},
{ once: true }
);
image.addEventListener('load', () => resolve('loaded'), { once: true });
image.addEventListener('error', () => resolve('error'), { once: true });
});
image.src = '/fixtures/csp-owned-image.png';
host.append(image, status);
document.body.append(host);
let timeoutId;
const timeout = new Promise(resolve => {
timeoutId = window.setTimeout(() => resolve('not-observed'), 5000);
});
const observed = await Promise.race([outcome, timeout]);
window.clearTimeout(timeoutId);
const blocked = violation;
status.textContent = blocked
? '回退:应用策略已阻止图片'
: observed === 'loaded'
? '对照:自有 fixture 已加载'
: '未出现策略违规的 fixture 失败';
const passed = expected
? observed !== 'error' && observed !== 'not-observed' && violation
: observed === 'loaded' && !violation;
return { marker: host.dataset.test, observed, blocked, passed };
}, expectedBlocked);
try {
if (!result.passed) throw new Error(`Unexpected CSP fixture result: ${result.observed}`);
return result;
} finally {
await page.evaluate(() => document.querySelector('[data-test="owned-csp-blocked-image"]')?.remove());
}
}
由应用自有测试 stub 提供 /fixtures/csp-owned-image.png,返回一个小型有效图片。对照策略允许 img-src 'self',图片应成功加载;候选策略使用 img-src 'none',浏览器会发出 securitypolicyviolation 并记录可见回退。如果没有产生预期事件,应记录“未观察到”,调查策略、浏览器支持范围和 fixture 设置。网络或策略错误不能算通过。确定性失败后要清理测试上下文和产物目录。
先用对照策略、再用候选策略运行同一 fixture,分别传入 false 和 true,并分开保存收据。“已阻止”只有在对照运行证明自有 stub 可达、候选运行报告策略违规时才有意义。记录最终 URL、策略版本、浏览器版本、标记、可见结果和清理状态。不要保存 Cookie、授权头、完整日志或复制的生产响应。
若设置、断言和清理都可能出错,应保留流程中的首个错误作为主要失败,并单独报告清理错误,在收尾处理中关闭页面和上下文。重试应创建新上下文及新收据。后续通过并不能消除第一次请求的不确定性,特别是 Report-Only 下资源可能已到达真实服务。负向案例应使用自有合成来源,避免产生远端变更。
验证兼容性和责任归属
在团队支持的浏览器、操作系统、文档模式和应用路由中验证策略。按实际功能覆盖重定向、缓存、Service Worker、模块脚本、Worker、框架、表单和动态导入。边缘配置文件中存在策略,不代表浏览器实际收到了策略。检查重定向后的最终响应,确认策略由哪个文档持有。嵌套文档可能拥有自己的策略和资源决策。
明确应用与基础设施契约。应用负责人定义必需资源和用户回退体验;托管或 CDN 负责人确认所有规范路由均返回响应头且缓存没有覆盖;安全负责人定义报告、保留和事件边界;供应商负责人确认来源、重定向和数据处理;发布负责人解决分歧并记录通过的策略版本。依赖为可选时记录用户可见回退;若不可或缺,则在契约兼容前不要晋级。
检查策略与缓存、模板的交互。每响应 nonce 不得跨响应复用,缓存的 HTML 必须携带与自身策略匹配的 nonce。报告收集器不得暴露查询中的敏感值。Service Worker 可能提供旧文档,因此应用使用它时应测试干净上下文和回访上下文。浏览器状态隔离有助于重复验证,但不会删除服务器记录,也无法修复陈旧部署。
像审查代码和基础设施一样审查策略变更。比较指令、来源、nonce 或哈希生成、响应路由、报告目标和收据格式。每个新增来源都要有负责人。临时例外需要复审日期。只有正向流程确认依赖已不再使用,且获准部署已移除它后,才能删掉来源。没有实际运行流程时报告流量平静,不能证明依赖已经退役。
| 兼容性案例 | 检查项 | 失败时的处理 |
|---|---|---|
| 内联启动代码 | Nonce 或哈希与响应匹配 | 修正生成逻辑或改用自有外部模块 |
| 模块或 Worker 导入 | 最终 URL 与适用指令 | 添加准确的自有来源,或使用受支持回退 |
| 框架或表单流程 | 子来源、重定向、frame-src、frame-ancestors、form-action | 与负责人协调,不添加全局通配符 |
| 缓存或 Service Worker 响应 | 策略与资源版本一致 | 按批准流程清理或等待受控过期 |
监控、回滚与持续维护
先在有限路由或发布范围内启用强制策略。按策略版本、路由、浏览器系列和依赖类型监控资源阻止情况。聚合到足够诊断但不留存用户内容的程度。数量上升可能代表依赖变化、缓存错配、重定向变化、浏览器行为变化或收集器故障。修改策略前先与已知正向流程比较。报告数量不是通用安全或可用性指标。
启用强制策略前准备回滚,保留上一版响应策略、应用版本和测试收据。如果必要流程损坏,同时恢复已知正常策略与应用路径,然后重新运行相同的正向和负向 fixture。若代码依赖新增资源,应通过运行时回退保护,或发布兼容的应用版本。回滚不应移除传输安全、授权检查或诊断失败所需日志。
回滚后保留候选策略报告并标记为历史数据。不要悄悄删除证据,也不要把 Report-Only 结果称为强制策略效果。针对资源、策略或发布负责人创建范围明确的后续任务。下一候选版本只改变一个已知条件,并重复同一 fixture,以区分来源遗漏、nonce 错误、重定向、缓存或应用回退问题。
维护策略记录:当前响应头和路由范围、版本、来源负责人、报告目标、保留要求、支持的浏览器范围、已知例外及复审日期。框架升级、CDN 规则变化、供应商集成变化、新增框架或 Worker、规范主机变化后都要复核。CSP 是部署后应用契约的一部分,过时文档本身也是维护风险。
BotBrowser 可以验证什么
BotBrowser 受控浏览器上下文可使用干净或已声明的浏览器状态,运行获授权的应用流程,观察浏览器是否阻止合成资源,并对比可见回退。 BotBrowser 多账户隔离文档介绍隔离的 BrowserContext 及浏览器管理状态的边界。这适用于验证 CSP 的浏览器可见部分:最终 URL、测试获准查看的策略元数据、资源加载或阻止结果以及清理收据。
BotBrowser 不会编写或部署响应头,不会为应用选择安全的来源清单,也不验证服务器授权、依赖供应链或证明不存在注入漏洞。它无法让非自有第三方资源变可信、替供应商授予例外,也不能保证所有浏览器以相同方式支持指令。它也不能证明应用记录已创建、支付已接受、服务器会话已撤销或安全事件没有发生。这些事实需要应用、服务、基础设施和安全负责方的证据。
BotBrowser 收据应保持精简:场景、策略版本、最终文档 URL、浏览器版本、可见结果、合成路由分类与清理状态。不要放入客户内容、凭据、Cookie 或完整违规报告载荷。结果应标注为“浏览器策略观察”;需要业务结论时链接到另外的应用或服务证据。干净 BrowserContext 是执行条件,不是服务器数据删除机制,也不是安全认证。
发布前,负责人应确认四项不承诺:违规报告不证明攻击发生;没有报告不证明安全;资源被阻止不证明服务器拒绝或删除了任何数据;BotBrowser 观察不能替代应用授权与依赖审查。随后记录已接受策略、兼容性结果、回退方案、监控负责人和回滚产物。如此才能持续提升保护,而非为加载未知依赖而削弱策略。
策略或自有依赖发生变化后,应重新运行相同用户流程。带版本的有界比较有助于区分变化来自浏览器决策、应用回退,还是独立服务结果。
BotBrowser 团队