{"record":{"id":"450ea1cebdbcd0b0","repo":"chatboxai/chatbox","slug":"oauth-state-mismatch","errorCode":null,"errorMessage":"OAuth state mismatch","messagePattern":"OAuth state mismatch","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/oauth/providers/openai.ts","lineNumber":78,"sourceCode":"    authUrl.searchParams.set('response_type', 'code')\n    authUrl.searchParams.set('client_id', CLIENT_ID)\n    authUrl.searchParams.set('redirect_uri', REDIRECT_URI)\n    authUrl.searchParams.set('scope', SCOPE)\n    authUrl.searchParams.set('code_challenge', challenge)\n    authUrl.searchParams.set('code_challenge_method', 'S256')\n    authUrl.searchParams.set('state', state)\n    authUrl.searchParams.set('id_token_add_organizations', 'true')\n    authUrl.searchParams.set('codex_cli_simplified_flow', 'true')\n    authUrl.searchParams.set('originator', 'chatbox')\n\n    const { promise, close } = createCallbackServer(CALLBACK_PORT, signal, CALLBACK_HOST)\n\n    try {\n      await openUrl(authUrl.toString())\n      const result = await promise\n\n      if (result.state !== state) {\n        throw new Error('OAuth state mismatch')\n      }\n\n      return await exchangeCodeForTokens(result.code, verifier)\n    } finally {\n      close()\n    }\n  },\n\n  async refreshToken(credentials) {\n    if (!credentials.refreshToken) {\n      throw new Error('No refresh token available')\n    }\n\n    const response = await fetch(TOKEN_URL, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n      body: new URLSearchParams({\n        grant_type: 'refresh_token',","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/oauth/providers/openai.ts#L60-L96","documentation":"Thrown by OpenAI's callback OAuth provider after the local callback server (port 1455) receives a redirect whose state parameter does not equal the random state generated at login start. This is the standard OAuth CSRF defense: a mismatch means the callback did not correspond to this login attempt.","triggerScenarios":"Browser was already on a stale authorize page from a previous login and redirected with an old state; a second login tab raced the first and the wrong callback arrived; user manually visited localhost:1455 with a crafted/old URL; an attacker injected a callback (the very attack the guard prevents).","commonSituations":"User had multiple ChatGPT login tabs open; a previous login's browser tab redirected after a new login started; redirects cached by the browser; testing tools replaying an old callback URL.","solutions":["Treat as a hard security failure: do not exchange the code; restart login() from scratch.","Ensure only one login() is in flight at a time per provider (serialize or cancel prior flows).","Close stale browser tabs on the authorize page before starting a new login.","Confirm createCallbackServer is bound to localhost only so external callers cannot inject callbacks."],"exampleFix":"// before\nif (result.state !== state) {\n  throw new Error('OAuth state mismatch')\n}\n\n// after\nif (result.state !== state) {\n  log.error('[OAuth:OpenAI] state mismatch — possible stale or forged callback')\n  throw new SecurityError('Login state mismatch. Close other ChatGPT login tabs and try again.')\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await provider.login({ openUrl, signal })\n} catch (e) {\n  if (/state mismatch/i.test(String(e))) {\n  // security: never exchange a mismatched callback; restart fresh\n  throw new SecurityError('Login state mismatch. Close other ChatGPT login tabs and retry.')\n  }\n  throw e\n}","preventionTips":["Serialize login() per provider — only one in flight at a time.","Bind the callback server to localhost only (CALLBACK_HOST='localhost') so external callers cannot inject callbacks.","Close stale browser tabs on the OpenAI authorize page before starting a new login.","Treat any state mismatch as a potential CSRF attack; never bypass the check."],"tags":["oauth","security","csrf","state","openai","callback"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}