fix(openai): 隐私/账号检查客户端改用 Firefox 指纹,规避 chatgpt.com Cloudflare 质询

chatgpt.com 的 Cloudflare 会对 req 内置的 ImpersonateChrome(TLS/HTTP2 指纹与 UA 均为
Chrome/120)返回 403 cf-mitigated=challenge,导致 accounts/check、subscriptions、
account_user_setting 三个 backend-api 调用长期失败:订阅到期时间不再更新、隐私开关显示 Fail。
仅改 UA 版本号无效(指纹仍是 120),Firefox 伪装在同一出口下稳定通过,故切换。

同时:
- 用 cf-mitigated 响应头识别质询页,隐私设置失败时正确标记为 training_set_cf_blocked
- accounts/check / subscriptions 失败日志由 Debug 提升为 Warn 并附 cf_challenge 字段,不再静默

Closes #4825

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
xpflyingandClaude Fable 5.1 committed 2026-09-11 18:10:15 +08:00
1 parent 4726bdd08b
commit 9eb120dd40
4 files changed
+56 -8

No files matched your search

@@ -17,7 +17,7 @@ import (
type reqClientOptions struct {
ProxyURL string // 代理 URL(支持 http/https/socks5)
Timeout time.Duration // 请求超时时间
Impersonate bool // 是否模拟 Chrome 浏览器指纹
Impersonate bool // 是否模拟浏览器指纹(当前为 Firefox,Chrome 伪装会被 chatgpt.com 的 Cloudflare 质询)
ForceHTTP2 bool // 是否强制使用 HTTP/2
}
@@ -50,7 +50,12 @@ func getSharedReqClient(opts reqClientOptions) (*req.Client, error) {
client = client.EnableForceHTTP2()
}
if opts.Impersonate {
client = client.ImpersonateChrome()
// chatgpt.com 的 Cloudflare 会对 req 内置的 Chrome 伪装(UA 固定为 Chrome/120,
// 与 sec-ch-ua 等 Client Hints 一起已明显过时)直接返回 403 cf-mitigated=challenge,
// 导致 accounts/check、subscriptions、隐私设置等 backend-api 调用全部失败,
// 订阅到期时间因此长期不更新(见 issue #4825)。Firefox 伪装的 UA/头部组合
// 在同一出口 IP 下稳定通过,故改用 Firefox 指纹。
client = client.ImpersonateFirefox()
}
trimmed, _, err := proxyurl.Parse(opts.ProxyURL)
if err != nil {
@@ -95,6 +100,6 @@ func CreatePrivacyReqClient(proxyURL string) (*req.Client, error) {
return getSharedReqClient(reqClientOptions{
ProxyURL: proxyURL,
Timeout: 30 * time.Second,
Impersonate: true, // Enable Chrome TLS fingerprint impersonation
Impersonate: true, // Enable browser TLS fingerprint impersonation (Firefox, see getSharedReqClient)
})
}
@@ -140,3 +140,12 @@ func TestInstrumentReqClientRecordsDependency(t *testing.T) {
header := collector.HeaderValue(time.Now(), "bypass")
require.True(t, strings.Contains(header, "dep_http;dur="), header)
}
func TestGetSharedReqClient_ImpersonateUsesFirefoxFingerprint(t *testing.T) {
sharedReqClients = sync.Map{}
client, err := getSharedReqClient(reqClientOptions{Timeout: time.Second, Impersonate: true})
require.NoError(t, err)
// chatgpt.com 的 Cloudflare 会质询 req 内置的 Chrome/120 伪装,必须保持 Firefox 指纹。
require.Contains(t, client.Headers.Get("User-Agent"), "Firefox/")
require.NotContains(t, client.Headers.Get("User-Agent"), "Chrome/")
}