From 92fc61374a342783a69816497b8df9707c8806c1 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:22:15 -0300 Subject: [PATCH] fix(oauth): align codebuddy-cn OAuth User-Agent with chat/usage (#12702) OAuth device-code auth/poll and token-refresh for codebuddy-cn presented CLI/2.63.2 CodeBuddy/2.63.2 while chat-completion and usage/quota requests presented CLI/2.108.1 CodeBuddy/2.108.1 for the same account. A 45-minor- version-apart client fingerprint across auth vs. chat calls is exactly the kind of internally-inconsistent signal an anti-abuse WAF flags as anomalous (Tencent gateway code 11128 'request illegal'). Centralize the version string into CODEBUDDY_CN_USER_AGENT (exported from src/lib/oauth/constants/oauth.ts) and reference it from the chat registry entry and the usage handler so all three surfaces can never drift apart again. Adds a permanent regression test asserting the OAuth, chat and usage User-Agent headers all match. --- ...2702-codebuddy-cn-useragent-consistency.md | 1 + .../providers/registry/codebuddy-cn/index.ts | 3 +- open-sse/services/usage/codebuddy-cn.ts | 4 ++- src/lib/oauth/constants/oauth.ts | 11 +++++- tests/unit/codebuddy-cn-provider.test.ts | 35 +++++++++++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 changelog.d/fixes/12702-codebuddy-cn-useragent-consistency.md diff --git a/changelog.d/fixes/12702-codebuddy-cn-useragent-consistency.md b/changelog.d/fixes/12702-codebuddy-cn-useragent-consistency.md new file mode 100644 index 0000000000..10beb4efdf --- /dev/null +++ b/changelog.d/fixes/12702-codebuddy-cn-useragent-consistency.md @@ -0,0 +1 @@ +- fix(oauth): align codebuddy-cn OAuth User-Agent with the chat/usage CLI version to avoid WAF false positives (#12702) diff --git a/open-sse/config/providers/registry/codebuddy-cn/index.ts b/open-sse/config/providers/registry/codebuddy-cn/index.ts index 593041e404..ae951828de 100644 --- a/open-sse/config/providers/registry/codebuddy-cn/index.ts +++ b/open-sse/config/providers/registry/codebuddy-cn/index.ts @@ -1,3 +1,4 @@ +import { CODEBUDDY_CN_USER_AGENT } from "@/lib/oauth/constants/oauth"; import type { RegistryEntry } from "../../shared.ts"; /** @@ -20,7 +21,7 @@ export const codebuddy_cnProvider: RegistryEntry = { authType: "oauth", authHeader: "bearer", headers: { - "User-Agent": "CLI/2.108.1 CodeBuddy/2.108.1", + "User-Agent": CODEBUDDY_CN_USER_AGENT, "X-Product": "SaaS", "X-IDE-Type": "CLI", "X-IDE-Name": "CLI", diff --git a/open-sse/services/usage/codebuddy-cn.ts b/open-sse/services/usage/codebuddy-cn.ts index 27b1df83fd..5b8837e535 100644 --- a/open-sse/services/usage/codebuddy-cn.ts +++ b/open-sse/services/usage/codebuddy-cn.ts @@ -14,6 +14,8 @@ * packs, "Bonus Pack N" for bonus packs (soonest-expiring first). */ +import { CODEBUDDY_CN_USER_AGENT } from "@/lib/oauth/constants/oauth"; + const USAGE_URL = "https://copilot.tencent.com/v2/billing/meter/get-user-resource"; interface TencentAccount { @@ -130,7 +132,7 @@ export async function getCodeBuddyCnUsage( Authorization: `Bearer ${token}`, "Content-Type": "application/json", Accept: "application/json", - "User-Agent": "CLI/2.108.1 CodeBuddy/2.108.1", + "User-Agent": CODEBUDDY_CN_USER_AGENT, "X-Product": "SaaS", "X-IDE-Type": "CLI", "X-IDE-Name": "CLI", diff --git a/src/lib/oauth/constants/oauth.ts b/src/lib/oauth/constants/oauth.ts index 68c63a4e46..8094bb58cd 100644 --- a/src/lib/oauth/constants/oauth.ts +++ b/src/lib/oauth/constants/oauth.ts @@ -106,12 +106,21 @@ export const QODER_CONFIG = { // CodeBuddy CN (Tencent — copilot.tencent.com) OAuth Configuration // (Custom Device-Auth Flow: POST stateUrl → open authUrl → GET pollUrl?state=). // No client_id/secret — the upstream CLI ships none. +// +// CODEBUDDY_CN_USER_AGENT is the single source of truth for the CLI/CodeBuddy version +// string. It MUST stay identical across OAuth (this file), chat completions +// (open-sse/config/providers/registry/codebuddy-cn/index.ts) and usage/quota +// (open-sse/services/usage/codebuddy-cn.ts) — a mismatched version string across a +// single account's auth vs. chat calls is exactly the kind of internally-inconsistent +// client fingerprint Tencent's WAF flags as anomalous (#12702). +export const CODEBUDDY_CN_USER_AGENT = "CLI/2.108.1 CodeBuddy/2.108.1"; + export const CODEBUDDY_CN_CONFIG = { baseUrl: "https://copilot.tencent.com", stateUrl: "https://copilot.tencent.com/v2/plugin/auth/state", tokenUrl: "https://copilot.tencent.com/v2/plugin/auth/token", refreshUrl: "https://copilot.tencent.com/v2/plugin/auth/token/refresh", - userAgent: "CLI/2.63.2 CodeBuddy/2.63.2", + userAgent: CODEBUDDY_CN_USER_AGENT, platform: "CLI", pollInterval: 5000, }; diff --git a/tests/unit/codebuddy-cn-provider.test.ts b/tests/unit/codebuddy-cn-provider.test.ts index 0d08c293cb..5467cf4462 100644 --- a/tests/unit/codebuddy-cn-provider.test.ts +++ b/tests/unit/codebuddy-cn-provider.test.ts @@ -585,3 +585,38 @@ test("codebuddy-cn is treated as a managed dual-auth provider (oauth + apikey ac "codebuddy-cn must be admitted by the dual-auth gate" ); }); + +test("#12702: codebuddy-cn presents the same CLI/CodeBuddy version across OAuth, chat and usage calls", async () => { + // A mismatched version string across a single account's auth vs. chat calls is exactly the + // kind of internally-inconsistent client fingerprint Tencent's WAF flags as anomalous + // (code 11128 "request illegal" / "blocked by security policy"). All three surfaces must + // read from the same CODEBUDDY_CN_USER_AGENT constant so they can never drift apart again. + const oauthUserAgent = CODEBUDDY_CN_CONFIG.userAgent; + const chatUserAgent = REGISTRY["codebuddy-cn"].headers?.["User-Agent"]; + assert.equal( + oauthUserAgent, + chatUserAgent, + `codebuddy-cn OAuth User-Agent (${oauthUserAgent}) must match the chat User-Agent (${chatUserAgent})` + ); + + const { CODEBUDDY_CN_USER_AGENT } = await import("../../src/lib/oauth/constants/oauth.ts"); + assert.equal(oauthUserAgent, CODEBUDDY_CN_USER_AGENT); + + const origFetch = globalThis.fetch; + let capturedUserAgent: string | undefined; + globalThis.fetch = (async (_url: unknown, init?: RequestInit) => { + capturedUserAgent = (init?.headers as Record | undefined)?.["User-Agent"]; + return new Response(JSON.stringify({}), { status: 200 }); + }) as typeof fetch; + try { + const { getCodeBuddyCnUsage } = await import("../../open-sse/services/usage/codebuddy-cn.ts"); + await getCodeBuddyCnUsage("ACCESS_TOKEN", undefined, undefined); + assert.equal( + capturedUserAgent, + CODEBUDDY_CN_USER_AGENT, + "codebuddy-cn usage/quota User-Agent must match the shared constant" + ); + } finally { + globalThis.fetch = origFetch; + } +});