From e5842c75ecaa195b82bcdeb8fa3626eeee3d0ed6 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:37:02 -0300 Subject: [PATCH] feat(providers): add proactive cf_clearance/User-Agent hint to grok-web connection dialog (#7567) (#7713) --- changelog.d/features/7567-grokweb-ua-hint.md | 1 + src/i18n/messages/en.json | 1 + src/i18n/messages/pt-BR.json | 1 + src/shared/providers/webSessionCredentials.ts | 8 ++ .../unit/grokweb-cookie-ua-hint-7567.test.ts | 118 ++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 changelog.d/features/7567-grokweb-ua-hint.md create mode 100644 tests/unit/grokweb-cookie-ua-hint-7567.test.ts diff --git a/changelog.d/features/7567-grokweb-ua-hint.md b/changelog.d/features/7567-grokweb-ua-hint.md new file mode 100644 index 0000000000..6370085f54 --- /dev/null +++ b/changelog.d/features/7567-grokweb-ua-hint.md @@ -0,0 +1 @@ +- feat(providers): grok-web's add-connection dialog now shows a proactive hint explaining that grok.com's `cf_clearance` cookie is pinned to the IP, User-Agent, and TLS fingerprint of the browser that earned it — on a 403, paste the exact browser User-Agent into the existing Custom User-Agent field under Advanced Settings and reuse the same IP/proxy (#7567) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 0c3150469e..e404babdf2 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -4620,6 +4620,7 @@ "blackboxWebCookiePlaceholder": "Blackbox Web Cookie Placeholder", "t3ChatWebCookieHint": "Open t3.chat → DevTools → Application → Local Storage → https://t3.chat, copy 'convex-session-id'. Then open DevTools → Network, copy the full Cookie header from any chat request. Paste both values in the fields below.", "t3ChatWebCookiePlaceholder": "convex-session-id=abc123...", + "grokWebCookieHint": "grok.com's cf_clearance cookie is pinned to the IP, User-Agent, and TLS fingerprint of the browser where you copied it — pasting it from a different machine/IP causes a 403. Paste sso and sso-rw here, then open Advanced Settings and fill Custom User-Agent with the EXACT User-Agent string of that same browser, and use the same IP/proxy for this connection.", "blockClaudeExtraUsageDescription": "Hide extra Claude usage rows reported by some providers when they duplicate primary token accounting.", "blockClaudeExtraUsageLabel": "Block duplicate Claude usage rows", "disableCoolingDescription": "Skip the transient cooldown so this connection stays eligible even after recoverable errors (terminal states like banned/expired still apply).", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index d0d012bc19..3fe6698df4 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -4618,6 +4618,7 @@ "blackboxWebCookiePlaceholder": "Cole o valor de __Secure-authjs.session-token", "t3ChatWebCookieHint": "Abra t3.chat → DevTools → Aplicativo → Armazenamento local → https://t3.chat, copie 'convex-session-id'. Em seguida, abra DevTools → Rede, copie o cabeçalho completo do cookie de qualquer solicitação de chat. Cole os dois valores nos campos abaixo.", "t3ChatWebCookiePlaceholder": "id-de-sessão-convexa=abc123...", + "grokWebCookieHint": "O cookie cf_clearance do grok.com é vinculado ao IP, ao User-Agent e à impressão digital TLS do navegador de onde você o copiou — colá-lo de outra máquina/IP causa um erro 403. Cole sso e sso-rw aqui e depois abra Configurações Avançadas e preencha o Custom User-Agent com o valor EXATO do User-Agent desse mesmo navegador, usando o mesmo IP/proxy para esta conexão.", "blockClaudeExtraUsageDescription": "Quando habilitado, o OmniRoute marca esta conta do Claude Code como indisponível assim que a API de usage reporta `extra_usage.queued`, para que o fallback troque para outra conta antes de continuar com cobranças extras pay-as-you-go.", "blockClaudeExtraUsageLabel": "Bloquear Claude Extra Usage", "disableCoolingDescription": "__MISSING__:Skip the transient cooldown so this connection stays eligible even after recoverable errors (terminal states like banned/expired still apply).", diff --git a/src/shared/providers/webSessionCredentials.ts b/src/shared/providers/webSessionCredentials.ts index 7e5f45be35..387e080a73 100644 --- a/src/shared/providers/webSessionCredentials.ts +++ b/src/shared/providers/webSessionCredentials.ts @@ -45,6 +45,14 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = { placeholder: "sso=...; sso-rw=...", acceptsFullCookieHeader: true, storageKeys: ["cookie", "sso", "sso-rw"], + // #7567 — grok.com's cf_clearance cookie is pinned to IP + User-Agent + TLS + // fingerprint of the browser that earned it, so pasting it from a different + // machine/IP causes a 403 that is actually correct Cloudflare behavior. Point + // users at the Custom User-Agent field under Advanced Settings + same IP/proxy, + // instead of the generic (and here misleading) single-cookie hint. + hintKey: "grokWebCookieHint", + hintFallback: + "grok.com's cf_clearance cookie is pinned to the IP, User-Agent, and TLS fingerprint of the browser where you copied it — pasting it from a different machine/IP causes a 403. Paste sso and sso-rw here, then open Advanced Settings and fill Custom User-Agent with the EXACT User-Agent string of that same browser, and use the same IP/proxy for this connection.", }, "gemini-web": { kind: "cookie", diff --git a/tests/unit/grokweb-cookie-ua-hint-7567.test.ts b/tests/unit/grokweb-cookie-ua-hint-7567.test.ts new file mode 100644 index 0000000000..19f0232767 --- /dev/null +++ b/tests/unit/grokweb-cookie-ua-hint-7567.test.ts @@ -0,0 +1,118 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import type { ProviderMessageTranslator } from "../../src/app/(dashboard)/dashboard/providers/[id]/providerCredentialText.ts"; + +// #7567 — grok-web's add-connection dialog showed the generic, uninformative +// cookie hint ("Required cookie: sso + sso-rw. Paste the Cookie header +// value…") even though the real failure mode (403 from a pasted cf_clearance +// pinned to a different IP/User-Agent/TLS fingerprint) is only resolvable by +// also filling the Custom User-Agent field under Advanced Settings and reusing +// the same IP/proxy. A dedicated hintKey (grokWebCookieHint) now overrides the +// generic copy on the ADD flow, following the exact #5465 (t3.chat) pattern. +const { getWebSessionCredentialHint } = await import( + "../../src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts" +); +const { WEB_SESSION_CREDENTIAL_REQUIREMENTS } = await import( + "../../src/shared/providers/webSessionCredentials.ts" +); + +const GROK_HINT = + "grok.com's cf_clearance cookie is pinned to the IP, User-Agent, and TLS fingerprint of the browser where you copied it — pasting it from a different machine/IP causes a 403. Paste sso and sso-rw here, then open Advanced Settings and fill Custom User-Agent with the EXACT User-Agent string of that same browser, and use the same IP/proxy for this connection."; + +const GENERIC_COOKIE_HINT_TEMPLATE = + "Required cookie: {credential}. Paste the Cookie header value from your own signed-in {provider} web session. Do not include the Cookie: prefix."; + +/** Minimal translator stub mimicking next-intl's `t` + `t.has`. */ +function makeTranslator(messages: Record): ProviderMessageTranslator { + const t = ((key: string, values?: Record) => { + const raw = messages[key]; + if (raw === undefined) return key; + return values + ? Object.entries(values).reduce( + (acc, [name, value]) => acc.replaceAll(`{${name}}`, String(value)), + raw + ) + : raw; + }) as ProviderMessageTranslator; + t.has = (key: string) => key in messages; + return t; +} + +test("grok-web requirement carries the grokWebCookieHint override (#7567)", () => { + const req = WEB_SESSION_CREDENTIAL_REQUIREMENTS["grok-web"] as { hintKey?: string }; + assert.equal(req.hintKey, "grokWebCookieHint"); +}); + +test("grok-web add-connection hint explains cf_clearance IP/UA/TLS pinning and the Custom User-Agent fix (#7567)", () => { + const t = makeTranslator({ + grokWebCookieHint: GROK_HINT, + webCookieCredentialHint: GENERIC_COOKIE_HINT_TEMPLATE, + }); + + const hint = getWebSessionCredentialHint( + t, + WEB_SESSION_CREDENTIAL_REQUIREMENTS["grok-web"], + "Grok", + false + ); + + assert.equal(hint, GROK_HINT); + assert.ok(hint && hint.includes("User-Agent"), "must mention User-Agent"); + assert.ok(hint && hint.includes("IP"), "must mention IP pinning"); + assert.ok(hint && hint.includes("cf_clearance"), "must mention cf_clearance"); +}); + +test("grok-web add-connection hint does not fall back to the generic circular cookie hint (#7567)", () => { + const t = makeTranslator({ + grokWebCookieHint: GROK_HINT, + webCookieCredentialHint: GENERIC_COOKIE_HINT_TEMPLATE, + }); + + const hint = getWebSessionCredentialHint( + t, + WEB_SESSION_CREDENTIAL_REQUIREMENTS["grok-web"], + "Grok", + false + ); + + assert.ok( + hint && !hint.includes("Required cookie: sso + sso-rw"), + "must not fall back to the generic single-cookie template" + ); +}); + +test("grok-web edit-connection flow is unaffected by the new hintKey (#7567 regression guard)", () => { + const t = makeTranslator({ + grokWebCookieHint: GROK_HINT, + webCookieEditHint: "Leave blank to keep the current session cookie. Required cookie: {credential}.", + }); + + const hint = getWebSessionCredentialHint( + t, + WEB_SESSION_CREDENTIAL_REQUIREMENTS["grok-web"], + "Grok", + true + ); + + assert.ok( + hint && hint.startsWith("Leave blank to keep the current session cookie."), + "editing=true must keep the generic edit-flow copy, not the new hintKey" + ); + assert.ok(hint && !hint.includes("cf_clearance"), "edit flow must not surface the new add-flow hint"); +}); + +test("grok-web hintFallback is used verbatim when the translation key is missing (#7567)", () => { + const t = makeTranslator({ + // grokWebCookieHint intentionally absent — t.has() must return false. + webCookieCredentialHint: GENERIC_COOKIE_HINT_TEMPLATE, + }); + + const requirement = WEB_SESSION_CREDENTIAL_REQUIREMENTS["grok-web"] as { + hintFallback?: string; + }; + assert.ok(requirement.hintFallback, "grok-web must define a hintFallback"); + + const hint = getWebSessionCredentialHint(t, WEB_SESSION_CREDENTIAL_REQUIREMENTS["grok-web"], "Grok", false); + + assert.equal(hint, requirement.hintFallback); +});