从 Report-Only 到强制执行:一次 CSP 迁移的完整记录
迁移背景:
一个中型 SaaS 前端,长期没有内容安全策略,XSS 防护依赖框架的自动转义和代码评审。安全团队要求上线 CSP,但直接上强制执行会大面积破坏页面,于是走了一条从 Report-Only 到 enforce 的渐进路径,前后约六周。
第一周,只开 Report-Only,收集真实违规。
响应头设 Content-Security-Policy-Report-Only,策略从最严格的白名单开始写,同时配置 report-uri 收集违规报告。第一天的报告量很大,但大部分是噪音:浏览器扩展注入的脚本、第三方统计工具的动态加载、内联样式。关键不是立刻修,而是先分类——哪些是自有代码的问题,哪些是第三方依赖,哪些是浏览器扩展(可以忽略)。
第二到三周,处理自有代码的违规。
最大的一类问题是内联脚本和内联事件处理器。onclick="..." 这类写法在框架里本应被禁止,但历史代码里仍有残留。处理方式是逐步替换成事件监听,同时给确实需要内联的少量脚本加 nonce。nonce 需要每次请求生成并注入到 HTML 和 CSP 头里,缓存策略要跟着调整,否则 CDN 缓存会导致 nonce 不匹配。另一个高频问题是动态 eval 和 new Function,来自某个老旧的模板库,最终通过升级依赖解决。
第四周,处理第三方依赖。
第三方脚本是 CSP 迁移里最不可控的部分。统计、客服、A/B 测试工具经常动态加载更多脚本,白名单很难穷举。做法是给每个第三方单独评估:能自托管的自托管,能异步加载的加 async,确实需要动态加载的,把域名加入白名单并定期审查。一个实用原则是先收紧 script-src,再收紧 style-src,因为脚本的风险远高于样式,而样式的内联在现有前端体系里更难消除。
第五周,灰度 enforce。
选择流量较小的一个租户先切 enforce,观察错误监控和用户反馈。CSP 违规在浏览器里通常表现为资源被阻止,页面功能异常但不一定报错,所以需要在前端加一个全局错误监听,把 securitypolicyviolation 事件上报。灰度期间发现一个被忽略的问题:某个支付 SDK 在 iframe 里加载,frame-src 没配,导致支付流程中断。补上之后继续观察。
第六周,全量 enforce,保留 Report-Only 作为监控。
全量切换后,Report-Only 不关闭,继续收集违规作为预警。新功能上线时,如果引入了不在白名单里的资源,Report-Only 会先报警,而不是等用户遇到问题。
复盘:
CSP 迁移的难点不在写策略,在把“哪些资源是必要的”这件事从隐性知识变成显性清单。Report-Only 阶段最大的价值不是收集违规,是逼团队盘点所有外部依赖。强制执行只是最后一步,前面五周的工作才是主体。如果跳过 Report-Only 直接 enforce,大概率会在上线当天回滚
- 点赞
- 收藏
- 关注作者
评论(0)