Skip to content

fix(cf): 修复 cf_clearance 丢失与 User-Agent 配置覆盖导致 Cloudflare 403#473

Open
Soein wants to merge 28 commits intochenyme:mainfrom
Soein:fix/cf-clearance-cookie
Open

fix(cf): 修复 cf_clearance 丢失与 User-Agent 配置覆盖导致 Cloudflare 403#473
Soein wants to merge 28 commits intochenyme:mainfrom
Soein:fix/cf-clearance-cookie

Conversation

@Soein
Copy link
Copy Markdown

@Soein Soein commented Apr 14, 2026

问题背景

v2.0 重构后,发往 grok.com 的请求出现两个 Cloudflare WAF 403 问题,均与 cf_clearance Cookie 和 User-Agent 一致性有关。

Bug 1: build_sso_cookie 未读取 config 里的 cf_clearance

文件: app/dataplane/proxy/adapters/headers.py

build_sso_cookie()cf_clearance 参数默认为 None,被 _sanitize 当作空字符串处理。代码库 5 处调用方均未显式传入 cf_clearance,导致所有发往 grok.com 的 Cookie 头都缺失 cf_clearance=...,直接被 Cloudflare WAF 403。

修复:

  • _resolve_profile() 返回值改为三元组 (cf_cookies, user_agent, cf_clearance)
  • 同时读取 proxy.clearance.cf_clearance 和 legacy proxy.cf_clearance 路径
  • build_sso_cookie()cf_clearance 参数为 None 时从 profile fallback

Bug 2: _resolve_profile 读取优先级错误,legacy 路径被 defaults 覆盖

文件: app/dataplane/proxy/adapters/headers.py

_resolve_profile 优先读 proxy.clearance.user_agent(嵌套 v2 路径),fallback 才读 proxy.user_agent(扁平 v1 路径)。但 config.defaults.tomlproxy.clearance.user_agent 预设了占位 Chrome 136 UA(非空字符串),所以外部 cf-refresher sidecar 写到 proxy.user_agent 的新鲜 UA 永远被 defaults 覆盖。

结果:Cookie 里的 cf_clearance 是用真实 UA 获取的,实际请求 UA 变成了 defaults 的占位 Chrome 136。Cloudflare 把 cf_clearance 绑定到首次获取时的 UA,UA 一变即 403。

修复: 扁平 legacy 路径 (proxy.xxx) 优先,嵌套 (proxy.clearance.xxx) 作为 fallback。cf_clearance / cf_cookies 同样遵循此顺序保持一致。

兼容性

  • 新 config (proxy.clearance.*) 保持原行为
  • 旧 config (proxy.*, 由 cf-refresher sidecar 写入的路径) 现在也能被读到
  • 调用方显式传 cf_clearance='xxx''' 时行为不变

验证

本地部署验证修复后 grok.com 请求可正常携带 cf_clearance,Cloudflare 不再 403。

Soein and others added 28 commits February 22, 2026 00:44
解决冲突:
- app/api/v1/chat.py: 保留 deepsearch + heartbeat,合入 tools/tool_choice 函数调用和 _safe_sse_stream 错误处理
- app/services/grok/services/chat.py: 保留 deepsearch + remove_think_tags,合入 tools 参数和 dict content 类型处理
# Conflicts:
#	app/services/reverse/app_chat.py
# Conflicts:
#	app/services/grok/services/chat.py
#	app/services/reverse/app_chat.py
避免 New API 误判视频失败为成功导致用户被扣费
chat() 和 AppChatReverse 缺少 deepsearch_preset 参数导致
grok-4-thinking 等模型调用时报 TypeError
pip-audit 检测到 aiohttp 3.13.3 存在 CVE-2026-34513 ~ CVE-2026-34525CVE-2026-22815 等漏洞,升级至 3.13.5 修复
pip-audit 检测到 curl-cffi 0.14.0 存在 CVE-2026-33752 漏洞,
升级至 0.15.0 修复(Security Checks workflow chenyme#18 失败原因)。
- 接受上游目录重构 (app/services → app/dataplane, app/products)
- app/api/v1/chat.py、services/grok/services/chat.py、services/reverse/app_chat.py 跟随上游删除
- pyproject.toml 和 uv.lock 采用上游版本
  - cryptography 46.0.7 (覆盖本地 46.0.6 的 CVE 修复)
  - 新增 certifi 2025.10.5
- readme.md 大小写规范化为 README.md

本地以下自定义 fix 因结构重构需单独 port 到新架构:
- feat: 心跳机制 (stream_with_heartbeat) - 防 CDN 断连
- feat: remove_think_tags - 清理消息中 think 标签
- fix: deepsearch_preset 透传 (上游已通过 request_overrides 支持)
- fix: 视频请求失败返回正确 HTTP 状态码
上游大重构后以下本地补丁重新迁移到 app/products/openai 下:

- feat: 心跳机制 (_sse_with_heartbeat)
  在 router.py 为 /v1/chat/completions 和 /v1/responses 的 SSE 响应
  添加初始 2KB 填充 + 每 30s `: ping` 心跳,防反代/CDN 断连;
  并在 _SSE_HEADERS 增加 X-Accel-Buffering: no。

- feat: remove_think_tags
  chat.py._extract_message 清理消息中 <think>...</think> 块和
  内联 base64 图片(data:image/...;base64,...),避免把模型自己的
  推理内容和超大图片重发给上游。

- feat: deepsearch_preset 透传
  ChatCompletionRequest 新增 deepsearch 字段 (default/deeper),
  router 转换为 request_overrides={deepsearchPreset: ...}
  通过上游已有的 build_chat_payload request_overrides 链路透传。

- fix: 视频请求失败返回真实 HTTP 状态码
  chat_completions_endpoint 在视频模型分支下异常直接 raise,
  不再包装为 SSE 200,避免 New API 等计费网关误判为成功扣费。
BUG: v2.0 重构后 build_sso_cookie() 的 cf_clearance 参数默认为 None,被 _sanitize
直接当作空字符串处理。代码库中 5 处调用方都没传 cf_clearance, 导致所有发往
grok.com 的 Cookie 头都缺失 cf_clearance=..., 被 Cloudflare WAF 直接 403。

FIX:
- _resolve_profile() 改为返回三元组 (cf_cookies, user_agent, cf_clearance)
- 同时读取 proxy.clearance.cf_clearance 和 legacy proxy.cf_clearance 路径
- build_sso_cookie() 当 cf_clearance 参数为 None 时回落到 profile_clearance

兼容性:
- 新 config (proxy.clearance.cf_clearance) 保持原行为
- 旧 config (proxy.cf_clearance, 由 cf-refresher sidecar 写入的路径) 现在也能被读到
- 调用方显式传 cf_clearance='xxx' 或 '' 时行为不变

修复 upstream issues chenyme#450 (AppChatReverse 403) 和 chenyme#462 (set-birth-date 403 导致 NSFW 失败)。
用户可见现象: 在 v1.6.0 能正常对话的账号在 v2.0.x 全部 403, chat/rate-limits/
set-birth-date 三个端点均不可达。
问题: 修复第一版里 _resolve_profile 优先读 proxy.clearance.user_agent(嵌套 v2 路径),
fallback 才读 proxy.user_agent(扁平 v1 路径)。但 config.defaults.toml 里
proxy.clearance.user_agent 预设了占位 Chrome 136 UA(非空字符串),所以外部 cf-refresher
sidecar 写到 proxy.user_agent 的新鲜 Firefox UA 永远被 defaults 盖掉。

结果 Cookie 里带的 cf_clearance 是用 Firefox UA 获取的,但实际请求 UA 变成了 Chrome 136。
Cloudflare 把 cf_clearance 绑定到首次获取时的 UA,UA 一变即 403。

修复: 扁平 legacy 路径(proxy.xxx)优先, 嵌套(proxy.clearance.xxx) 作为 fallback。
这样 cf-refresher 写扁平路径时能立即覆盖 defaults 的占位值。cf_clearance/cf_cookies
同样遵循此顺序以保持一致。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant