CF Cache Rules 堆叠机制踩坑:bypass 被覆盖导致用户数据泄露
写在前面:一个 Cache Rules 的排序问题,差点把 Gitea 登录态泄露出去。事后查文档才发现规则是"最后匹配胜出"而非"命中即停"——这个设计反直觉到让我以为出了 bug。
起因
今晚给博客加 RSS,顺手修了私有仓库站点的缓存配置。之前配了 CDN 缓存加速静态资源,顺便给 /api/ 和 /user/ 加了 bypass 规则——毕竟 Gitea 的动态页面不能缓存。
一切看起来正常。
异常
开始不对劲了。
生成 token、删除 token、更新设置——操作完了,页面显示的还是旧状态。刷新没用,Ctrl+F5 也没用。
更离谱的来了:开无痕窗口,直接看到了登录后的页面。没有登录,没有 cookie,赤裸裸的 Gitea 管理后台,上面还挂着我的用户名。
那一瞬间头皮发麻——用户的登录态被 CDN 缓存了,并泄露给了所有人。
排查
第一反应是 bypass 规则没生效。开启 CF Development Mode(临时绕过所有缓存),问题消失。关掉,问题复现。
问题 100% 在 CF 缓存层。
破案
关键工具:CF 的"跟踪"功能
在盲目抓狂之前,先说一个救命功能:CF 面板 Security → Investigate 下的 "跟踪"(Trace) 功能。
它可以模拟一个请求经过 CF 的全部数据链路,告诉你每一步哪个规则被触发了、哪个没触发。完整链路长这样:
跟踪开始
↓
file upload scan
↓
WAF Managed Rules
1 个规则集, 3 个规则
↓
Super Bot Fight Mode
1 个规则集, 1 个规则
↓
request managed headers
↓
Cache Rules
1 个规则集, 2 个规则 ← 关键!两条规则都匹配了
↓
snippets (0 个片段)
↓
cloud connector (0 个连接器)
↓
缓存参数 (1 缓存参数)
↓
响应托管标头
↓
跟踪结束 → HTTP 200
就是这一步暴露了问题:Cache Rules 显示 2 个规则 都匹配了——不是只有 bypass 那条命中,而是两条都命中了。
根因:堆叠执行,最后匹配胜出
让 CF 的 AI Agent 去读配置确认后,它给出了致命一击:
Cache Rules 不是"命中即停",而是"堆叠执行,最后匹配的胜出"。
我之前的规则顺序是:
规则1: Bypass → /api/、/user/ → Bypass
规则2: 全部缓存 → 所有请求 → Eligible for cache
规则3: 长期缓存 ...
当请求 /user/settings 时:
- 规则1 匹配 → 设置 bypass ✓
- 规则2 也匹配(主机名匹配所有请求)→ 设置 eligible for cache ✓
规则2 在后面,覆盖了规则1。最终 /user/ 路径被缓存了。
排查建议:遇到 CF 缓存行为异常,别先 curl,先去"跟踪"里跑一条请求看规则匹配情况。能省掉 80% 的排查时间。
修复
正确的排序是 通用在前,特殊在后:
规则1:长期缓存 ...
规则2: 全部缓存 → 所有请求 → Eligible for cache
规则3: Bypass → /api/、/user/ → Bypass(最后匹配,覆盖规则1)
换句话说:从长缓存到短缓存到不缓存,越特殊越靠后。
教训
- CF Cache Rules 的"堆叠"设计反直觉——绝大多数安全产品的规则引擎都是"命中即停",CF 偏偏是"全匹配后最后一条胜出"。文档里写了,但没人会主动去看这条。
- 善用"跟踪"功能排查缓存问题——Security → Investigate 下的 Trace 能展示完整请求链路,一眼看出哪些规则命中了。比 curl 盲猜高效得多。
- 不要在生产环境测试缓存配置——幸好 Gitea 是自用,不然这就是个安全事故。
- CF 的 AI Agent 意外地好用——虽然一开始没权限读配置,但描述清楚现象后它直接定位了根因。
后续
私有仓库站点现在一切正常。但这次经历让我重新审视了 CF 的缓存模型——它不是一个简单的"配了就生效"的系统,而是一个多层堆叠的规则引擎。用好了是利器,用不好是定时炸弹。
记于凌晨三点。修 bug 修到怀疑人生,结果只是两条规则的顺序问题。