Skip to content

fix(combo): classify Cloudflare 1010 fingerprint rejection as non-auth - #9929

Merged
diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.50from
HouMinXi:fix/cloudflare-1010-fingerprint-rejection
Aug 10, 2026
Merged

diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.50from
HouMinXi:fix/cloudflare-1010-fingerprint-rejection

Conversation

@HouMinXi

@HouMinXi HouMinXi commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

oc-ds-flash-free (routed via opencode.ai/zen/v1) returns 403 / Cloudflare error_code 1010 for a non-browser client whose User-Agent reaches the CDN, while curl on the same key and byte-identical body succeeds. Two such failures collapsed the free pool into a misleading ALL_ACCOUNTS_INACTIVE.

This PR treats a Cloudflare fingerprint rejection as what it is — the CDN refusing the client's TLS/UA signature, not the account's credentials — so it no longer trips auth-level exhaustion or poisons the provider pool.

Change (3 layers)

  1. errorClassifier.ts — new FINGERPRINT_REJECTION type. A 403 carrying error_code 1010 / browser_signature_banned / fingerprint_rejection is classified as a fingerprint-layer rejection, not FORBIDDEN.
  2. combo/targetExhaustion.ts — a fingerprint rejection is excluded from auth-level exhaustion, so one 1010 no longer skips every remaining combo target (which is what collapsed the pool into ALL_ACCOUNTS_INACTIVE).
  3. resolveTerminalConnectionStatus (auth) — a fingerprint rejection is not a terminal banned account state. The account is healthy; a different client on the same key succeeds.

UA passthrough is deliberately untouched: #5997 / #5720 make the forward-only behavior load-bearing, and host-side masking is out of scope.

Tests

  • The affected suite passes (93 tests across error classification + combo target exhaustion).
  • 13 new regression tests pin every classification edge (bare-1010 false positives, numeric boundary, word-boundary, URL-path vs prefixed forms, case-insensitive code/type, structured error_code). Each was verified by bug-injection (inject → FAIL → revert → PASS) to prove it actually guards the behavior it names.

opencode.ai/zen/v1 rejects non-browser clients (urllib) with 403
error_code 1010 while curl on the same key succeeds. The 403 was
treated as an auth-level failure and two of them crystallized a
misleading ALL_ACCOUNTS_INACTIVE on the free pool.

- errorClassifier: new FINGERPRINT_REJECTION type; a 403 carrying
  error_code 1010 / browser_signature_banned is the CDN refusing the
  client TLS/UA signature, not the account credentials.
- combo/targetExhaustion: fingerprint rejections skip auth-level
  exhaustion so remaining targets stay eligible.
- auth: resolveTerminalConnectionStatus no longer treats the
  fingerprint rejection as a terminal banned account state.

UA passthrough is deliberately untouched: diegosouzapw#5997/diegosouzapw#5720 make the
forward-only behavior load-bearing.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
@HouMinXi
HouMinXi requested a review from diegosouzapw as a code owner August 9, 2026 14:17
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @HouMinXi — correct and well-guarded classification of Cloudflare 1010 as non-auth (avoids poisoning healthy pools), with extensive regression tests incl. structuredError variants. Merge-ready.

3 similar comments
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @HouMinXi — correct and well-guarded classification of Cloudflare 1010 as non-auth (avoids poisoning healthy pools), with extensive regression tests incl. structuredError variants. Merge-ready.

@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @HouMinXi — correct and well-guarded classification of Cloudflare 1010 as non-auth (avoids poisoning healthy pools), with extensive regression tests incl. structuredError variants. Merge-ready.

@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @HouMinXi — correct and well-guarded classification of Cloudflare 1010 as non-auth (avoids poisoning healthy pools), with extensive regression tests incl. structuredError variants. Merge-ready.

@diegosouzapw
diegosouzapw merged commit 4fc04a5 into diegosouzapw:release/v3.8.50 Aug 10, 2026
4 of 5 checks passed
alvinveroy added a commit to alvinveroy/OmniRoute that referenced this pull request Sep 2, 2026
… 1010 hits

A Cloudflare 1010 / browser-signature rejection (diegosouzapw#9929) means the CDN
banned the CLIENT's TLS/UA signature — not the account. OmniRoute already
ships the fix for exactly this: a Chrome-124 impersonation transport
(open-sse/utils/tlsClient.ts via the optional wreq-js dependency), gated
behind ENABLE_TLS_FINGERPRINT. But the gate is off by default, the
dependency is optional, and nothing tells the operator any of this — so
repeated 1010s look like flaky upstreams while a one-line env change
would fix them.

Emit a throttled (once-per-process) remediation hint when a response is
classified FINGERPRINT_REJECTION and ENABLE_TLS_FINGERPRINT is not
enabled, naming the exact env vars and the optional dependency. No
behavior change otherwise; the hint is suppressed entirely once the
transport is enabled.
anhtran-ai added a commit to azox-ai/azox-omniroute that referenced this pull request Sep 10, 2026
…t rejection, not a ban (#1)

A Cloudflare managed/JS challenge served in front of an upstream provider is the
same class of block as a Cloudflare 1010 — the edge refused the CLIENT's
signature — but it is a different product surface and carries none of the 1010
markers isCloudflareFingerprintRejection() looks for.

It therefore fell through the entire 403 ladder in classifyProviderError() to the
terminal default FORBIDDEN, which chatCore persists via writeTerminalStatus() as
testStatus=banned / isActive=false. That state never auto-recovers, so a single
challenge takes the whole provider offline until an operator reconnects in the
dashboard.

Observed on POST chatgpt.com/backend-api/codex/responses/input_tokens for a
healthy Codex OAuth account: the response carried cf-mitigated: challenge,
server: cloudflare and a ~12KB text/html interstitial with
window._cf_chl_opt = {... cType: 'managed', cZone: 'chatgpt.com' ...}. The same
connection refreshed its OAuth token successfully in the same second and served
normal /responses traffic seconds before and after, so the account was never
banned upstream.

Classify the interstitial as FINGERPRINT_REJECTION, reusing the existing
non-terminal precedent from diegosouzapw#9929: authTerminalStatus already treats that type as
non-terminal, so the request falls through to the next combo target and the
account state stays untouched.

The markers are matched as full, distinctive Cloudflare-internal strings
(_cf_chl_opt, cdn-cgi/challenge-platform, the challenge-error-text span id
including its escaped-quote nested form) and never as the loose word
"challenge", so provider bodies discussing a challenge in prose are unaffected.

Tests cover the full interstitial, the gateway-nested error.message form, each
marker individually, prose false-positive guards, and regression guards proving
a genuine permission 403 and the ChatGPT Web Sentinel/Turnstile 403 (diegosouzapw#8813) both
still classify as FORBIDDEN.

Co-authored-by: anhth2 <anhth2@vng.com.vn>
anhtran-ai added a commit to azox-ai/azox-omniroute that referenced this pull request Sep 10, 2026
* fix(resilience): treat a Cloudflare managed challenge as a fingerprint rejection, not a ban

A Cloudflare managed/JS challenge served in front of an upstream provider is the
same class of block as a Cloudflare 1010 — the edge refused the CLIENT's
signature — but it is a different product surface and carries none of the 1010
markers isCloudflareFingerprintRejection() looks for.

It therefore fell through the entire 403 ladder in classifyProviderError() to the
terminal default FORBIDDEN, which chatCore persists via writeTerminalStatus() as
testStatus=banned / isActive=false. That state never auto-recovers, so a single
challenge takes the whole provider offline until an operator reconnects in the
dashboard.

Observed on POST chatgpt.com/backend-api/codex/responses/input_tokens for a
healthy Codex OAuth account: the response carried cf-mitigated: challenge,
server: cloudflare and a ~12KB text/html interstitial with
window._cf_chl_opt = {... cType: 'managed', cZone: 'chatgpt.com' ...}. The same
connection refreshed its OAuth token successfully in the same second and served
normal /responses traffic seconds before and after, so the account was never
banned upstream.

Classify the interstitial as FINGERPRINT_REJECTION, reusing the existing
non-terminal precedent from diegosouzapw#9929: authTerminalStatus already treats that type as
non-terminal, so the request falls through to the next combo target and the
account state stays untouched.

The markers are matched as full, distinctive Cloudflare-internal strings
(_cf_chl_opt, cdn-cgi/challenge-platform, the challenge-error-text span id
including its escaped-quote nested form) and never as the loose word
"challenge", so provider bodies discussing a challenge in prose are unaffected.

Tests cover the full interstitial, the gateway-nested error.message form, each
marker individually, prose false-positive guards, and regression guards proving
a genuine permission 403 and the ChatGPT Web Sentinel/Turnstile 403 (diegosouzapw#8813) both
still classify as FORBIDDEN.

* fix(responses): count input tokens locally for Codex OAuth

The ChatGPT subscription backend does not serve
/backend-api/codex/responses/input_tokens for the affected account. Native
requests to that path are intercepted by an OpenAI Cloudflare managed challenge,
while the same path over the bundled Chrome transport returns 404 Not Found.
Forwarding the client preflight can therefore never return a useful count and,
before the companion classifier fix, permanently disabled the healthy Codex
connection on the first challenge.

Add a static /v1/responses/input_tokens route that shadows the generic
Responses passthrough, uses the existing offline o200k_base token counter, and
returns the standard response.input_tokens contract without issuing any upstream
request. Count instructions, structured input, tool definitions and config;
apply a conservative five-percent margin so the failure mode is earlier client
compaction rather than a context-window overflow.

Preserve the API-key and model-policy boundary from the catch-all Responses path.
Tests pin the public schema, prove fetch is never called, cover text,
instructions, structured input, tools, non-text parts, server-held context ids,
invalid JSON, OPTIONS, and the conservative lower bound.

---------

Co-authored-by: anhth2 <anhth2@vng.com.vn>
@HouMinXi
HouMinXi deleted the fix/cloudflare-1010-fingerprint-rejection branch September 16, 2026 14:00
diegosouzapw added a commit that referenced this pull request Sep 18, 2026
…#14090)

* fix(resilience): treat a Cloudflare managed challenge as a fingerprint rejection, not a ban

A Cloudflare managed/JS challenge served in front of an upstream provider is the
same class of block as a Cloudflare 1010 — the edge refused the CLIENT's
signature — but it is a different product surface and carries none of the 1010
markers isCloudflareFingerprintRejection() looks for.

It therefore fell through the entire 403 ladder in classifyProviderError() to the
terminal default FORBIDDEN, which chatCore persists via writeTerminalStatus() as
testStatus=banned / isActive=false. That state never auto-recovers, so a single
challenge takes the whole provider offline until an operator reconnects in the
dashboard.

Observed on POST chatgpt.com/backend-api/codex/responses/input_tokens for a
healthy Codex OAuth account: the response carried cf-mitigated: challenge,
server: cloudflare and a ~12KB text/html interstitial with
window._cf_chl_opt = {... cType: 'managed', cZone: 'chatgpt.com' ...}. The same
connection refreshed its OAuth token successfully in the same second and served
normal /responses traffic seconds before and after, so the account was never
banned upstream.

Classify the interstitial as FINGERPRINT_REJECTION, reusing the existing
non-terminal precedent from #9929: authTerminalStatus already treats that type as
non-terminal, so the request falls through to the next combo target and the
account state stays untouched.

The markers are matched as full, distinctive Cloudflare-internal strings
(_cf_chl_opt, cdn-cgi/challenge-platform, the challenge-error-text span id
including its escaped-quote nested form) and never as the loose word
"challenge", so provider bodies discussing a challenge in prose are unaffected.

Tests cover the full interstitial, the gateway-nested error.message form, each
marker individually, prose false-positive guards, and regression guards proving
a genuine permission 403 and the ChatGPT Web Sentinel/Turnstile 403 (#8813) both
still classify as FORBIDDEN.

(cherry picked from commit 8204da6)

* fix(translator): skip replayed web_search_call metadata in Responses-to-Chat

OmniRoute's web-search fallback emits a native web_search_call output item
alongside function_call/function_call_output. Responses clients keep that item
in conversation history and replay it in the next request's input. When the
follow-up turn routes to a Chat Completions target (Claude), the translator hit
its default unsupported-feature branch and returned a deterministic HTTP 400:

  Unsupported Responses API feature: input item type 'web_search_call'
  cannot be represented in Chat Completions

Skip the replayed metadata next to tool_search_call/tool_search_result. The
paired function_call_output still carries the search results, so no context is
lost and the sources are not duplicated into assistant history.

(cherry picked from commit 2b3e63a)

* chore(changelog): credit the locked-fork landings of #13161 and #13304

Both PRs come from an organization fork (azox-ai) that refuses maintainer pushes
even with maintainerCanModify=true, so they cannot be re-synced in place and are
landed here by cherry-pick with the contributor's authorship preserved
(merge-gates §6). GitHub may not mark the PRs Merged, hence the explicit
credit in the fragments.

---------

Co-authored-by: anhth2 <anhth2@vng.com.vn>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
diegosouzapw#9929)

opencode.ai/zen/v1 rejects non-browser clients (urllib) with 403
error_code 1010 while curl on the same key succeeds. The 403 was
treated as an auth-level failure and two of them crystallized a
misleading ALL_ACCOUNTS_INACTIVE on the free pool.

- errorClassifier: new FINGERPRINT_REJECTION type; a 403 carrying
  error_code 1010 / browser_signature_banned is the CDN refusing the
  client TLS/UA signature, not the account credentials.
- combo/targetExhaustion: fingerprint rejections skip auth-level
  exhaustion so remaining targets stay eligible.
- auth: resolveTerminalConnectionStatus no longer treats the
  fingerprint rejection as a terminal banned account state.

UA passthrough is deliberately untouched: diegosouzapw#5997/diegosouzapw#5720 make the
forward-only behavior load-bearing.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ocked organization fork) (diegosouzapw#14090)

* fix(resilience): treat a Cloudflare managed challenge as a fingerprint rejection, not a ban

A Cloudflare managed/JS challenge served in front of an upstream provider is the
same class of block as a Cloudflare 1010 — the edge refused the CLIENT's
signature — but it is a different product surface and carries none of the 1010
markers isCloudflareFingerprintRejection() looks for.

It therefore fell through the entire 403 ladder in classifyProviderError() to the
terminal default FORBIDDEN, which chatCore persists via writeTerminalStatus() as
testStatus=banned / isActive=false. That state never auto-recovers, so a single
challenge takes the whole provider offline until an operator reconnects in the
dashboard.

Observed on POST chatgpt.com/backend-api/codex/responses/input_tokens for a
healthy Codex OAuth account: the response carried cf-mitigated: challenge,
server: cloudflare and a ~12KB text/html interstitial with
window._cf_chl_opt = {... cType: 'managed', cZone: 'chatgpt.com' ...}. The same
connection refreshed its OAuth token successfully in the same second and served
normal /responses traffic seconds before and after, so the account was never
banned upstream.

Classify the interstitial as FINGERPRINT_REJECTION, reusing the existing
non-terminal precedent from diegosouzapw#9929: authTerminalStatus already treats that type as
non-terminal, so the request falls through to the next combo target and the
account state stays untouched.

The markers are matched as full, distinctive Cloudflare-internal strings
(_cf_chl_opt, cdn-cgi/challenge-platform, the challenge-error-text span id
including its escaped-quote nested form) and never as the loose word
"challenge", so provider bodies discussing a challenge in prose are unaffected.

Tests cover the full interstitial, the gateway-nested error.message form, each
marker individually, prose false-positive guards, and regression guards proving
a genuine permission 403 and the ChatGPT Web Sentinel/Turnstile 403 (diegosouzapw#8813) both
still classify as FORBIDDEN.

(cherry picked from commit 8204da6)

* fix(translator): skip replayed web_search_call metadata in Responses-to-Chat

OmniRoute's web-search fallback emits a native web_search_call output item
alongside function_call/function_call_output. Responses clients keep that item
in conversation history and replay it in the next request's input. When the
follow-up turn routes to a Chat Completions target (Claude), the translator hit
its default unsupported-feature branch and returned a deterministic HTTP 400:

  Unsupported Responses API feature: input item type 'web_search_call'
  cannot be represented in Chat Completions

Skip the replayed metadata next to tool_search_call/tool_search_result. The
paired function_call_output still carries the search results, so no context is
lost and the sources are not duplicated into assistant history.

(cherry picked from commit 2b3e63a)

* chore(changelog): credit the locked-fork landings of diegosouzapw#13161 and diegosouzapw#13304

Both PRs come from an organization fork (azox-ai) that refuses maintainer pushes
even with maintainerCanModify=true, so they cannot be re-synced in place and are
landed here by cherry-pick with the contributor's authorship preserved
(merge-gates §6). GitHub may not mark the PRs Merged, hence the explicit
credit in the fragments.

---------

Co-authored-by: anhth2 <anhth2@vng.com.vn>
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.

2 participants