严格 csp 必须同时满足策略指令无 unsafe-*、nonce 每次请求唯一且端到端匹配、report-only 阶段跑满 3 天以上;否则无法防御 xss,易致功能异常或绕过。

直接上结论:严格 CSP 不是加一串字符串就行,它必须同时满足「策略指令无 unsafe-*」、「nonce 每次请求唯一且端到端匹配」、「report-only 阶段跑满 3 天以上」三个条件,缺一不可。
为什么 script-src 'unsafe-inline' 会让 CSP 形同虚设
这个配置等于主动放行所有内联脚本,包括 <script>alert(1)</script>、<div onclick="eval('...')">、甚至 Markdown 渲染后带 <code>onerror 的 <img> 标签。浏览器看到 'unsafe-inline' 就跳过所有内联脚本校验,CSP 对反射型 XSS 几乎零防御力。
常见错误场景:
- 开发时为图省事写成
script-src 'self' 'unsafe-inline',上线后没改 - 用
flask-talisman却忘了关掉默认的unsafe_inline=True - FastAPI 中间件拼接 header 时,被后续逻辑覆盖或漏写
script-src指令
nonce 必须满足的三个硬性条件
只在响应头里写 script-src 'nonce-abc123' 不够,它必须和 HTML 中的 <script nonce="abc123"></script> 完全一致,且该 <script></script> 不能是 JS 动态插入的(比如 el.innerHTML = '<script nonce="...">'</script>)。
实操要点:
- 每次 HTTP 请求都生成新随机 nonce,不能复用、不能缓存
- FastAPI 中建议用
secrets.token_urlsafe(16)生成,Flask 推荐用talisman的csp_nonce()模板函数 - Webpack/Vite 打包的外链 JS 不需要 nonce,但 URL 必须出现在
script-src白名单中 -
v-html或dangerouslySetInnerHTML渲染的内容,CSP 不管——得靠bleach.clean()提前清洗
从 Content-Security-Policy-Report-Only 切换到正式策略的关键动作
跳过 report-only 直接上线正式 CSP,90% 的项目会在首日出现大量功能异常:第三方统计脚本挂了、CDN 字体加载失败、富文本编辑器的预览区空白……
正确做法是:
- 先用
Content-Security-Policy-Report-Only头 +report-uri /csp-report收集真实 violation 数据,至少跑满 3 天 - 分析报告中的
violated-directive和blocked-uri,逐条补白名单,不是简单加* - 确认无关键业务阻断后,再把 header 名改为
Content-Security-Policy - 上线后仍保留
report-uri,用于捕获绕过行为(比如通过javascript:URL 触发的攻击)
最易被忽略的一点:CSP 策略对每个响应独立生效。API 接口返回 JSON 时如果没设 header,前端页面加载它就不会受 CSP 保护;而 HTML 页面里嵌的 iframe,其 CSP 由 iframe 自身响应决定,和父页无关。











