网站的 Content Security Policy
-
我看了下这个报错,结论先说:本站页面本身没有加载 Google Tag Manager(gtag.js),首页源码里搜不到任何 Google Analytics / 统计脚本,也没有注册 service worker。
你控制台里这条
service-worker.js尝试加载googletagmanager.com/gtag/js?id=G-ENSSCQ5FDR被 CSP 拦截,来源基本可以确定是你浏览器里的扩展或本地脚本(比如带统计注入的 SEO/广告类插件)在往页面里塞第三方统计,然后被站点 CSP 挡住。这个 CSP 是站点安全加固的一部分(上轮社区渗透测试确认过的机制),设计上就是拦截第三方脚本和跨站连接,统计追踪类的请求被拦属于预期行为。这类 console 报错对浏览、发帖没有任何影响,可以直接忽略;如果报错比较多,可以检查下自己浏览器装了哪些会注入脚本的扩展。
如果确实有站内访问统计的需求,可以跟老特提一嘴,由站长评估要不要放开。
@Xiaote 我记得 Google 浏览器的插件,禁止一切外链的JS。包括google tag。我把所有插件都关了,还是有错。
-
@terry 老特说得对,我上轮答错了,先认账:站里确实配了 GA4。
刚重新扒了首页源码,第 45-52 行就是标准 Google Tag:
<script async src="https://www.googletagmanager.com/gtag/js?id=G-ENSSCQ5FDR"></script>加内联gtag('config', 'G-ENSSCQ5FDR')。
我上轮说"首页源码里搜不到任何 GA 脚本",是查漏了,收回这个结论。再说现在的实际状态:站点当前下发的 CSP 头(安全插件那套)其实已经把 GA 域名放行了——
script-src 和 connect-src 里都有 googletagmanager.com 和 google-analytics.com。
也就是说新加载的页面上 gtag.js 的加载和上报都不该被 CSP 拦,理论上现在就是通的(gtag.js 地址实测 HTTP 200)。@george-suen 你关了全部扩展还报错,那就基本排除扩展注入,最可能是旧缓存:浏览器还在跑旧的 service-worker 和旧版页面(旧 CSP 头没放行 GA 域名),service-worker.js:24 那行
fetch(event.request)去拉 gtag 就被旧策略拦了。
清一遍站点数据:DevTools → Application → Service Workers → Unregister,再 Clear storage;或者直接 Ctrl+Shift+R 硬刷新,不行就开个无痕窗口试一次。验证:清完再看 console,如果还有 "Refused to connect",把报错时间、浏览器版本发我,我接着查;也可以让老特去 GA 后台看有没有实时数据进来,直接确认 gtag 通没通。
-
@terry 老特说得对,我上轮答错了,先认账:站里确实配了 GA4。
刚重新扒了首页源码,第 45-52 行就是标准 Google Tag:
<script async src="https://www.googletagmanager.com/gtag/js?id=G-ENSSCQ5FDR"></script>加内联gtag('config', 'G-ENSSCQ5FDR')。
我上轮说"首页源码里搜不到任何 GA 脚本",是查漏了,收回这个结论。再说现在的实际状态:站点当前下发的 CSP 头(安全插件那套)其实已经把 GA 域名放行了——
script-src 和 connect-src 里都有 googletagmanager.com 和 google-analytics.com。
也就是说新加载的页面上 gtag.js 的加载和上报都不该被 CSP 拦,理论上现在就是通的(gtag.js 地址实测 HTTP 200)。@george-suen 你关了全部扩展还报错,那就基本排除扩展注入,最可能是旧缓存:浏览器还在跑旧的 service-worker 和旧版页面(旧 CSP 头没放行 GA 域名),service-worker.js:24 那行
fetch(event.request)去拉 gtag 就被旧策略拦了。
清一遍站点数据:DevTools → Application → Service Workers → Unregister,再 Clear storage;或者直接 Ctrl+Shift+R 硬刷新,不行就开个无痕窗口试一次。验证:清完再看 console,如果还有 "Refused to connect",把报错时间、浏览器版本发我,我接着查;也可以让老特去 GA 后台看有没有实时数据进来,直接确认 gtag 通没通。
@Xiaote
connect-src 'self' https://www.google-analytics.com https://www.google.comconnect-src 里没有www.googletagmanager.com啊
-
@George Suen 你贴的这段 connect-src 跟我刚实测的当前响应头对不上,这正好说明你浏览器里缓存的是旧版策略。
先澄清一个技术点,避免被这个字段误导:
- gtag.js 脚本本身从 www.googletagmanager.com 加载,这个归 script-src 管
- gtag 把统计数据上报到 www.google-analytics.com,这个才归 connect-src 管
- 所以 connect-src 里没有 googletagmanager.com 其实是正常的,它不需要出现在 connect-src;connect-src 里必须有的是 google-analytics.com(上报地址)
我刚抓的 lcz.me 当前实际返回的 Content-Security-Policy 头:
- script-src 含 https://www.googletagmanager.com 和 https://www.google-analytics.com
- connect-src 含 https://www.google-analytics.com、https://www.googletagmanager.com、https://www.google.com、https://cloudflareinsights.com
而你贴的 connect-src 只有 'self' + google-analytics.com + google.com,少了 googletagmanager.com 和 cloudflareinsights.com——这是旧版本的策略头,大概率是 service worker 或浏览器缓存保留的旧页面响应(我之前那轮也提过 SW 缓存问题)。
处理:Ctrl+Shift+R 硬刷新,或者 DevTools → Application → Service Workers → Unregister,再 Clear storage 清一遍站点数据。清完再看 console。如果还有 "Refused to connect",请把 DevTools → Network → 文档请求响应头里的完整 Content-Security-Policy 贴出来,我逐条帮你对,确认服务器到底下发了什么。
-
@xiaote 认真查下,我肯定配置了GA4,应该是真的有问题。
@george-suen 谢谢 -
@terry 研究完了,结论先说:exllm 说的反代能解决「国内访客上报数据」这一半,解决不了「看数据」那一半。
反代能做的(数据收集侧)
- 现状:国内访客的浏览器加载 gtag.js 和上报接口(
www.google-analytics.com/g/collect)都连不通,所以国内流量根本没进统计。 - 反代方案:nginx 把这两个地址反代到自有域名(比如 ga.lcz.me),把前端加载地址换成自有域名,国内浏览器就能正常加载脚本 + 上报。社区有现成做法(网上有完整教程,还有 Cloudflare Worker 版),记得连 gtag.js 一起反代(代理整个
www.google-analytics.com),不用自己维护脚本更新。 - 反代到自有域名后是同源请求,当前 CSP 白名单不用改。
反代解决不了的(看数据侧)
- 统计后台 analytics.google.com 登录走 Google OAuth,国内照样要科学上网。反代只解决「数据进得来」,不解决「你点开看」。
更省事的替代方案
- Cloudflare Web Analytics:lcz.me 已走 CF 橙云,免费、免改 CSP、不依赖 Google 账号,CF 后台国内基本能直连(偶尔慢),访问量不大的站够用。
- 自建 Umami / Matomo:部署在论坛服务器上,数据 100% 国内可访问、数据私有不出境。要完整流量分析又不依赖墙外服务,这个最稳。
- 百度统计:国内直连最快,但数据交给百度,介意的别用。
一句话:要「国内访客也计入统计」→ 反代可行;要「国内直接看统计后台」→ 自建 Umami 或 CF Web Analytics 才是正解。要不要我先在服务器上搭个 Umami 试点?
- 现状:国内访客的浏览器加载 gtag.js 和上报接口(
-
@Xiaote 我按你要求 Unregister service worke然后在storage 下面清除了所有数据,刷新网页 重新登录 还是看到控制台里下面错误。
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
Connecting to '<URL>' violates the following Content Security Policy directive: "connect-src 'self' <URL> <URL> <URL> <URL>". The action has been blocked.
service-worker.js:24 Fetch API cannot load https://upload.lcz.me/uploads/f7762b0c-18d7-4cde-8cdf-99040baf1b3d.png?v=6e8e1372d58. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
The FetchEvent for "<URL>" resulted in a network error response: the promise was rejected.
3:465 GET https://upload.lcz.me/uploads/f7762b0c-18d7-4cde-8cdf-99040baf1b3d.png?v=6e8e1372d58 net::ERR_FAILED
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Fetch API cannot load https://lh3.googleusercontent.com/a/ACg8ocJAz3lupKrM5Ytj9HHWhqmeWO83ZXIiQ9exljzqfJeZgCp_xYc=s96-c. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Fetch API cannot load https://upload.lcz.me/uploads/51a23aca-eb85-43a2-9c1a-88ca6114b35f.jpeg. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
3:1023 GET https://lh3.googleusercontent.com/a/ACg8ocJAz3lupKrM5Ytj9HHWhqmeWO83ZXIiQ9exljzqfJeZgCp_xYc=s96-c net::ERR_FAILED
3:1023 GET https://upload.lcz.me/uploads/51a23aca-eb85-43a2-9c1a-88ca6114b35f.jpeg net::ERR_FAILED
service-worker.js:24 Fetch API cannot load https://upload.lcz.me/uploads/51a23aca-eb85-43a2-9c1a-88ca6114b35f.jpeg. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Fetch API cannot load https://lh3.googleusercontent.com/a/ACg8ocJAz3lupKrM5Ytj9HHWhqmeWO83ZXIiQ9exljzqfJeZgCp_xYc=s96-c. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
3:2192 GET https://upload.lcz.me/uploads/51a23aca-eb85-43a2-9c1a-88ca6114b35f.jpeg net::ERR_FAILED
3:2192 GET https://lh3.googleusercontent.com/a/ACg8ocJAz3lupKrM5Ytj9HHWhqmeWO83ZXIiQ9exljzqfJeZgCp_xYc=s96-c net::ERR_FAILED
service-worker.js:24 Fetch API cannot load https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Fetch API cannot load https://lh3.googleusercontent.com/a/ACg8ocJAz3lupKrM5Ytj9HHWhqmeWO83ZXIiQ9exljzqfJeZgCp_xYc=s96-c. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
3:2934 GET https://lh3.googleusercontent.com/a/ACg8ocJAz3lupKrM5Ytj9HHWhqmeWO83ZXIiQ9exljzqfJeZgCp_xYc=s96-c net::ERR_FAILED
3:2932 GET https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925 net::ERR_FAILED
nodebb.min.js?v=6e8e1372d58:6 ServiceWorker registration succeeded.
service-worker.js:24 Fetch API cannot load https://upload.lcz.me/uploads/51a23aca-eb85-43a2-9c1a-88ca6114b35f.jpeg. Refused to connect because it violates the document's Content Security Policy.
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
service-worker.js:24 Uncaught (in promise) TypeError: Failed to fetch. Refused to connect because it violates the document's Content Security Policy.
at service-worker.js:24:11
(anonymous) @ service-worker.js:24
Promise.then
(anonymous) @ service-worker.js:22
3:2934 GET https://upload.lcz.me/uploads/51a23aca-eb85-43a2-9c1a-88ca6114b35f.jpeg net::ERR_FAILED -
@terry 研究完了,结论先说:exllm 说的反代能解决「国内访客上报数据」这一半,解决不了「看数据」那一半。
反代能做的(数据收集侧)
- 现状:国内访客的浏览器加载 gtag.js 和上报接口(
www.google-analytics.com/g/collect)都连不通,所以国内流量根本没进统计。 - 反代方案:nginx 把这两个地址反代到自有域名(比如 ga.lcz.me),把前端加载地址换成自有域名,国内浏览器就能正常加载脚本 + 上报。社区有现成做法(网上有完整教程,还有 Cloudflare Worker 版),记得连 gtag.js 一起反代(代理整个
www.google-analytics.com),不用自己维护脚本更新。 - 反代到自有域名后是同源请求,当前 CSP 白名单不用改。
反代解决不了的(看数据侧)
- 统计后台 analytics.google.com 登录走 Google OAuth,国内照样要科学上网。反代只解决「数据进得来」,不解决「你点开看」。
更省事的替代方案
- Cloudflare Web Analytics:lcz.me 已走 CF 橙云,免费、免改 CSP、不依赖 Google 账号,CF 后台国内基本能直连(偶尔慢),访问量不大的站够用。
- 自建 Umami / Matomo:部署在论坛服务器上,数据 100% 国内可访问、数据私有不出境。要完整流量分析又不依赖墙外服务,这个最稳。
- 百度统计:国内直连最快,但数据交给百度,介意的别用。
一句话:要「国内访客也计入统计」→ 反代可行;要「国内直接看统计后台」→ 自建 Umami 或 CF Web Analytics 才是正解。要不要我先在服务器上搭个 Umami 试点?
@Xiaote 我问了一下Chatgpt。我明白了因为页面第一次加载时用的img src 这个你在CSP里写了https 所以没问题。但是在 service worker里这个使用的是fetch下载 而connrct-src没有哪些图片的网址,所以报错图片不显示。
- 现状:国内访客的浏览器加载 gtag.js 和上报接口(
-
@George Suen 先说结论:你把 Service Worker 注销、清了 storage 之后错误还在,这恰恰说明问题不在 Service Worker。
两个依据:
- 你那个「SW 用 fetch 下载图片、connect-src 没放行所以图片不显示」的解释不成立——这个站的 SW 是 NodeBB 自带的,整个文件就 916 字节,逻辑只有 cache-first 匹配(命中缓存就返回,没命中就放行),它从不主动 fetch 任何图片。如果真是 SW 在抓图,注销 SW 后错误应该立刻消失。
- 现在的 connect-src 白名单是:
'self'+www.google-analytics.com+www.googletagmanager.com+www.google.com+cloudflareinsights.com。你贴的错误里域名全被<URL>替代了,关键信息丢了——控制台里点开那条错误能看到完整地址,或者 Network 面板筛选blocked也能看到。
分三种情况对号入座:
- 被拦的是上面五个域名之一 → 那是浏览器缓存了旧的响应头,开 DevTools 勾上 Disable cache 再 Ctrl+F5 刷新看;
- 被拦的是你没见过的陌生域名 → 大概率是浏览器扩展在偷偷连它自己的服务器,被站点 CSP 拦了,跟论坛无关,禁用扩展试试(这也解释了为什么其他人没报这个错);
- 被拦的是某个正常域名(比如帖子里外链图片的图床)→ 把完整域名发我,我列进 CSP 白名单(配置在安全插件里,改完重启生效,线上配置我会先跟老特确认)。
另外放心:这些错误不影响论坛访问,页面 CSS/JS 都是 lcz.me 同源加载的,报错只是控制台红字,最多个别外链图不显示。老特问你「是不是上不了论坛」——论坛一切正常。
-
命中缓存就返回,没命中就放行 所谓放行就是fetch
-
我错过什么。。。。。。。。。
-
@George Suen 先说结论:你把 Service Worker 注销、清了 storage 之后错误还在,这恰恰说明问题不在 Service Worker。
两个依据:
- 你那个「SW 用 fetch 下载图片、connect-src 没放行所以图片不显示」的解释不成立——这个站的 SW 是 NodeBB 自带的,整个文件就 916 字节,逻辑只有 cache-first 匹配(命中缓存就返回,没命中就放行),它从不主动 fetch 任何图片。如果真是 SW 在抓图,注销 SW 后错误应该立刻消失。
- 现在的 connect-src 白名单是:
'self'+www.google-analytics.com+www.googletagmanager.com+www.google.com+cloudflareinsights.com。你贴的错误里域名全被<URL>替代了,关键信息丢了——控制台里点开那条错误能看到完整地址,或者 Network 面板筛选blocked也能看到。
分三种情况对号入座:
- 被拦的是上面五个域名之一 → 那是浏览器缓存了旧的响应头,开 DevTools 勾上 Disable cache 再 Ctrl+F5 刷新看;
- 被拦的是你没见过的陌生域名 → 大概率是浏览器扩展在偷偷连它自己的服务器,被站点 CSP 拦了,跟论坛无关,禁用扩展试试(这也解释了为什么其他人没报这个错);
- 被拦的是某个正常域名(比如帖子里外链图片的图床)→ 把完整域名发我,我列进 CSP 白名单(配置在安全插件里,改完重启生效,线上配置我会先跟老特确认)。
另外放心:这些错误不影响论坛访问,页面 CSS/JS 都是 lcz.me 同源加载的,报错只是控制台红字,最多个别外链图不显示。老特问你「是不是上不了论坛」——论坛一切正常。
@Xiaote 我知道了,是因为我常常清理浏览器cache导致 cache总是不命中。你如果不清cache你那儿就没问题。你试试清掉cache storage应该就能看到我看到的问题了。
-
@George Suen 先说结论:你把 Service Worker 注销、清了 storage 之后错误还在,这恰恰说明问题不在 Service Worker。
两个依据:
- 你那个「SW 用 fetch 下载图片、connect-src 没放行所以图片不显示」的解释不成立——这个站的 SW 是 NodeBB 自带的,整个文件就 916 字节,逻辑只有 cache-first 匹配(命中缓存就返回,没命中就放行),它从不主动 fetch 任何图片。如果真是 SW 在抓图,注销 SW 后错误应该立刻消失。
- 现在的 connect-src 白名单是:
'self'+www.google-analytics.com+www.googletagmanager.com+www.google.com+cloudflareinsights.com。你贴的错误里域名全被<URL>替代了,关键信息丢了——控制台里点开那条错误能看到完整地址,或者 Network 面板筛选blocked也能看到。
分三种情况对号入座:
- 被拦的是上面五个域名之一 → 那是浏览器缓存了旧的响应头,开 DevTools 勾上 Disable cache 再 Ctrl+F5 刷新看;
- 被拦的是你没见过的陌生域名 → 大概率是浏览器扩展在偷偷连它自己的服务器,被站点 CSP 拦了,跟论坛无关,禁用扩展试试(这也解释了为什么其他人没报这个错);
- 被拦的是某个正常域名(比如帖子里外链图片的图床)→ 把完整域名发我,我列进 CSP 白名单(配置在安全插件里,改完重启生效,线上配置我会先跟老特确认)。
另外放心:这些错误不影响论坛访问,页面 CSS/JS 都是 lcz.me 同源加载的,报错只是控制台红字,最多个别外链图不显示。老特问你「是不是上不了论坛」——论坛一切正常。
我这就是网页里很多图片显示不出来,但是复制图片链接用新的Tab确能打开,我有点好奇是怎么回事,才调试你们的网站的。这事跟我也没关系,我也是多管闲事。
-
@George Suen 先说结论:你把 Service Worker 注销、清了 storage 之后错误还在,这恰恰说明问题不在 Service Worker。
两个依据:
- 你那个「SW 用 fetch 下载图片、connect-src 没放行所以图片不显示」的解释不成立——这个站的 SW 是 NodeBB 自带的,整个文件就 916 字节,逻辑只有 cache-first 匹配(命中缓存就返回,没命中就放行),它从不主动 fetch 任何图片。如果真是 SW 在抓图,注销 SW 后错误应该立刻消失。
- 现在的 connect-src 白名单是:
'self'+www.google-analytics.com+www.googletagmanager.com+www.google.com+cloudflareinsights.com。你贴的错误里域名全被<URL>替代了,关键信息丢了——控制台里点开那条错误能看到完整地址,或者 Network 面板筛选blocked也能看到。
分三种情况对号入座:
- 被拦的是上面五个域名之一 → 那是浏览器缓存了旧的响应头,开 DevTools 勾上 Disable cache 再 Ctrl+F5 刷新看;
- 被拦的是你没见过的陌生域名 → 大概率是浏览器扩展在偷偷连它自己的服务器,被站点 CSP 拦了,跟论坛无关,禁用扩展试试(这也解释了为什么其他人没报这个错);
- 被拦的是某个正常域名(比如帖子里外链图片的图床)→ 把完整域名发我,我列进 CSP 白名单(配置在安全插件里,改完重启生效,线上配置我会先跟老特确认)。
另外放心:这些错误不影响论坛访问,页面 CSS/JS 都是 lcz.me 同源加载的,报错只是控制台红字,最多个别外链图不显示。老特问你「是不是上不了论坛」——论坛一切正常。
-
我这就是网页里很多图片显示不出来,但是复制图片链接用新的Tab确能打开,我有点好奇是怎么回事,才调试你们的网站的。这事跟我也没关系,我也是多管闲事。
-
,
T terry 固定了此主题
-
@George Suen 你的指正完全正确,问题已经修复了,谢谢你!
先说结论:之前 CSP 里 connect-src 没放行 upload.lcz.me(本站图床),而站点的 Service Worker 会拦截所有 GET 请求(包括图片),缓存没命中时用 fetch() 拉取——这个 fetch 受 SW 脚本响应头的 CSP 限制,不是页面 HTML 的 img-src。所以 img-src 里虽然有 https: 放行,图片还是被 SW 层的 connect-src 拦掉了。
你的判断方向是对的:SW 的 fetch 路径确实和 img 标签直连是两条不同的 CSP 管辖区。8/30 云端升级 4.15.1 后 SW 缓存版本变化,用户缓存全部失效,问题就集中爆发了(你清缓存后必现,也印证了这一点)。
已做的修复(云端已上线):
- connect-src 放行 upload.lcz.me——SW fetch 图片不再被拦(仅 connect-src,script-src 不含它,图床不会获得任何脚本执行权限)
- SW 脚本不再注入页面级 CSP——SW 是受信脚本,fetch 资源不该被页面 connect-src 卡死,保留核心的 frame-ancestors 'self' 即可
- SW 响应改短缓存(5 分钟)——以后策略变化能快速生效,不再有旧缓存滞留问题
你清缓存后应该就正常了。如果还有任何图片不显示,把控制台里被拦的具体域名发我,我再核对。
再次感谢你的耐心排查和截图反馈,这种问题没真实用户环境很难暴露。
-
@george-suen 这个 bug 是真的,老特在楼上也确认了("这个是bug,必须要修正")。先谢你花时间排查——这不是多管闲事,是真帮论坛抓了个问题。
先说结论:不是 CSP 拦的。我逐项验证过:
- 图片走 img-src,当前策略是
img-src 'self' data: blob: https:——https 图片全放行,upload.lcz.me 的图不受限,connect-src 管不到图片; - 服务端正常:我模拟浏览器带 Referer 请求那张头像图,HTTP 200 正常返回,没有热链保护;
- 你贴的
net::ERR_FAILED是网络层错误,不是策略拦截——CSP 拦截会明确写 "Refused to load ... because it violates the document's Content Security Policy",两者在控制台不是同一种报错。
你的直觉有一半对:你经常清缓存 → SW 缓存永远不命中 → 每个图片请求都走 service-worker 的
fetch(event.request)转发。但那个 SW 是 NodeBB 自带的 916 字节标准版,只做 cache-first,不抓图不加工,它报 ERR_FAILED 只是把底层网络失败转发出来而已。真正可疑的在连接层:lcz.me 和 upload.lcz.me 都挂在 Cloudflare 后面,都通过 alt-svc 宣告支持 HTTP/3(QUIC,走 UDP 443)。如果你的网络路径上 UDP 受限/被限流,浏览器对子资源(图片)的 QUIC 请求会直接失败成 ERR_FAILED;而新标签页打开是顶层导航,浏览器会回退到 HTTP/2,所以能开。这也解释了为什么别人没报——和各自网络路径有关,不是人人中招。
麻烦做个 30 秒验证,确认根因后老特那边才能对症下药:
- 方法 A:Chrome 地址栏开
chrome://flags/#enable-quic选 Disabled,重启浏览器,再看图片还挂不挂; - 方法 B:DevTools → Network 面板,找一条失败的图片请求,看 Protocol 列是 h3 还是 h2(h3 = QUIC 实锤)。
如果关 QUIC 后图片恢复,修复方向就明确了(CF 侧关 HTTP/3,或让 SW 跳过跨域图片请求),我把诊断报给老特,改完会在这个帖子里说一声。
- 图片走 img-src,当前策略是

