扫描器标红的 unsafe-inline:27 个首页里,写了脚本来源的 13 条策略有 10 条带着它

unsafe-inline 是 Content-Security-Policy 里那个让页面自带的内联脚本免于来源清单约束的关键字。2026-09-10 读了 27 个首页:18 个发了策略,13 个真的写了脚本来源,其中 10 个带着这个关键字,而把它和 nonce 写在一起(规范认定它失效的那种写法)的有 0 个。

抓取与收录7 分钟读完848 次阅读
扫描器标红的 unsafe-inline:27 个首页里,写了脚本来源的 13 条策略有 10 条带着它

实测 · 2026-09-10 · 30 个域名 · 27 个可读 · 单次抓取

样本 / 口径:仍是 2026-08-15 起在用的那 30 个站,2026-09-10 各请求一次首页,桌面 Chrome UA、跟随跳转、不执行 JavaScript,只读最终 URL 的响应头,不解析正文。canva.com、medium.com、stackoverflow.com 返回 403,纳入统计的是 27 个。

安全扫描器给你标红 unsafe-inline 的时候,它说的是一件确凿的事:这个关键字一出现,上面那串来源清单对页面自带的内联脚本就不再生效。但 27 个首页里,18 个发了 Content-Security-Policy,其中 13 个真的写了脚本来源,而这 13 个里有 10 个带着 unsafe-inline。没有一个把它和 nonce 写在一起——那才是规范认定它失效的写法。

怎么测的

一次请求、从外部、不登录任何账号。下面所有数字只来自最终 URL 上的两个响应头:Content-Security-PolicyContent-Security-Policy-Report-Only。按分号切开每条策略,每段的第一个词当指令名、其余当来源清单,再在清单里按字面精确匹配关键字。

# 一个域名的完整测法
curl -sIL -A 'Mozilla/5.0 (Macintosh) Chrome/126.0' https://example.com/ \
  | grep -i '^content-security-policy'

# 只看脚本来源那一段
curl -sIL -A 'Mozilla/5.0 (Macintosh) Chrome/126.0' https://example.com/ \
  | tr ';' '\n' | grep -i 'script-src'

三条局限,第一条决定了下面的数字能怎么读。策略也可以写在 HTML 的 <meta http-equiv> 里,而我们从没打开过正文,所以这里算作「没发」的站,可能有一条我们看不见的策略。第二,首页只是一个网址:这些站的应用页、结算页多半发的是另一套策略,本次测量对那些页面什么都没说。第三,关键字是按字面匹配的,写在我们没读的指令里就不在数里。

还有第四件从外面判不了的事,而它恰好是做出海站的人最想知道的那件。Google 的 JavaScript SEO basics 写着「Google Search runs JavaScript with an evergreen version of Chromium」和「a headless Chromium renders the page and executes the JavaScript」(2026-09-10 访问)。但那个渲染器会不会像用户的浏览器一样执行你的 Content-Security-Policy,这页没说——实际上 Content-Security-Policy 这个词在那页和它的排错姊妹页上一次都没出现过。我们不猜。

27 个首页里有 9 个根本没发这个头

这个头很常见,但远没到人人都有。发不发和站有多大、读者有多技术都对不上。18 个发了的里面有 2 个同时还发了一条 report-only;没有任何一个站只发 report-only。

响应里有什么站数(共 27)
强制生效的策略18
  — 且另发 report-only2
只发 report-only0
没有这个头9

没有的 9 个是 bbc.com、framer.com、about.gitlab.com、netlify.com、react.dev、shopify.com、slack.com、supabase.com、wikipedia.org。另发 report-only 的两个是 mozilla.org 和 webflow.com——一支团队把更严的策略先挂上去收报告、暂时不打开,长这个样子。

头的长度是第一个提示:「有 CSP」并不是同一件事。18 条里有 4 条不到 130 个字符,railway.com 和 www.wired.com 各只有 22 个字符。另一头 notion.com 发了 7,781 个字符,光脚本来源就列了 92 条。

18 条策略里有 5 条压根没提脚本

策略只管它点了名的东西。18 条里有 5 条没写 script-src,也没有 default-src 可以兜底,等于脚本在任何层级都不受约束。这 5 条里有 4 条整条只有一个指令 frame-ancestors,管的是谁能把你的页面塞进 iframe,和页面里跑什么毫无关系。

指令条数(共 18)
img-src14
style-src14
script-src13
frame-ancestors13
default-src12
connect-src11
font-src11
media-src10
upgrade-insecure-requests8
frame-src8
base-uri5
report-uri 或 report-to3

这一列在同一批面板上有前情:三周前我们把它和它要取代的那个老响应头对着数过一遍,见 x-frame-options 的 27 个首页实测,当时有 5 个站在自相矛盾。放到这篇里它只管一件小事——有 4 个站发的这个「内容安全策略」通篇只有一条防点击劫持的规则,而一个扫描器对这 4 个站报「已配置 CSP」,等于什么都没告诉你。

写了脚本来源的 13 条里,10 条带 unsafe-inline

这是本次的主结果。13 条真的列了脚本来源的策略里,10 条带着那个把内联脚本重新放行的关键字,其中 8 条还顺手加了 unsafe-eval。真正限制住内联脚本的只有 3 家,而且三家的做法各不相同。

站点来源数unsafe-inlineunsafe-eval
notion.com92
nextjs.org28
cloudflare.com25
vercel.com21
mozilla.org14
stripe.com13无 — 用 hash
figma.com11
nytimes.com6
arstechnica.com5
techcrunch.com5
news.ycombinator.com5
github.com1
reddit.com1无 — 用 nonce

把这张表的两头放在一起读。github.com 和 reddit.com 各只允许一个脚本来源,两个关键字都不写;stripe.com 允许 13 个来源,内联块靠 hash 放行。剩下的都是先写了一串来源清单,再补上那个让内联块免于清单约束的值。techcrunch.com 走得最远:它的 5 个脚本来源里有一个裸的 *,两个关键字还都在。

样式那边更松。18 条策略里有 14 条允许内联样式,写在 style-src 里或者由 default-src 兜底。这一条比较接近躲不掉——大量组件框架就是往元素上直接写 style 属性——也正因如此,第一版 CSP 通常是把样式这一半先交出去写的。

规范原文怎么说,以及它什么时候失效

Content Security Policy Level 3 规范对这件事说得很死,值得看原句而不是看别人的转述。内联脚本「will be blocked unless every policy allows inline script, either implicitly by not specifying a script-src (or default-src) directive, or explicitly, by specifying 'unsafe-inline', a nonce-source or a hash-source that matches the inline block」(CSP Level 3,2026-09-10 访问)。

大多数人漏掉的是后面那半句的相互作用。同一份文档把 'unsafe-inline' 'nonce-abc' 列在「source lists that do not allow all inline behavior due to the presence of nonces and/or hashes」之下——指令里只要有一个 nonce,这个关键字就不起作用了。这正是既上严格策略又兼容老浏览器的标准写法,而它在这批面板上出现了 0 次。那 3 家真限制住内联脚本的,压根就没写这个关键字。

一串来源清单加上 'unsafe-inline',读起来是「脚本只能从这几处来」,实际是「这几处,外加页面里已经有的那些」。

规范自己的安全章节把该怎么办也写了,语气一样平:「nonces provide a substantial improvement over 'unsafe-inline' when layering a content security policy on top of old code. When considering 'unsafe-inline', authors are encouraged to consider nonces (or hashes) instead.」13 家里有 10 家没这么做。

这对你意味着什么

如果有人告诉过你,AI 爬虫把你的页面抓成一个空壳可能是 Content-Security-Policy 干的,这次测量把问题收窄了很多。一条带着 unsafe-inline 又列了一长串来源的策略,多半什么都没挡住,因为它本来就几乎不挡东西。先把自己的头打出来看一眼,再去找别的原因。

  • 先打印策略,看 script-src 在不在里面——这 18 条里有 5 条在任何层级都不约束脚本
  • 要上 nonce,就在同一次改动里把 'unsafe-inline' 删掉;两个都留,只在老浏览器里生效,读起来严、跑起来松
  • 别把扫描器报的「已配置 CSP」当成结论——这里有 4 条头只是 130 个字符的防点击劫持规则
  • 别拿一条宽松策略去解释「页面对爬虫渲染成空的」,先看原始 HTML 里到底有没有正文

空壳这件事多数时候另有原因,而且从同一个请求里就能测出来。我们做的那个工具是AI 爬虫可达性检查,响应头这一半背后的判断方法写在content-security-policy 会不会挡住渲染器里。

常见问题

你们是怎么测的?

2026-09-10 每个域名请求一次,桌面 Chrome UA、跟随跳转、不执行 JavaScript。只读最终 URL 上的 Content-Security-PolicyContent-Security-Policy-Report-Only 两个头,正文一次都没解析。30 个域名里 27 个返回 200。方法那节的两条命令能复现任意单站的数字。

unsafe-inline 到底危不危险?

这取决于页面上有没有可注入的位置,而响应头告诉不了你这件事。它能告诉你的是:来源清单对内联块不再生效,所以一条带着这个关键字的策略,防护力配不上它的长度。我们测的是有没有,不是能不能被利用。

Content-Security-Policy 会影响 Google 收录吗?

我们不知道,也没找到公开答案。Google 说它的渲染器是常青版 Chromium,而 Chromium 对用户是执行这些策略的。但 Google 那两页 JavaScript 文档一次都没提 Content-Security-Policy,所以从「它是个浏览器」推到「它会执行你的策略」这一步,我们不迈。

为什么这么多站都带着 unsafe-inline?

响应头说不了,我们也说不了。数据的形状指向「策略是在页面写完之后才补上去的」——接手一个满是内联埋点和事件属性的页面,这个关键字是唯一一个加上去不会当场出事的值。但我们看不到任何一家的上线记录,所以这只是一种读法。

第一版策略该怎么写?

先 report-only,挂在你真正在意的那些页面上,什么都别强制。这批里有两个站正在这么干。成本是一个响应头,一次都不会拦,换来的是一份你本来只能靠猜的违规清单。

扫描器标红的 unsafe-inline:27 个首页里,写了脚本来源的 13 条策略有 10 条带着它