{"record":{"id":"8a36d9f56b560af9","repo":"slopus/happy","slug":"state-mismatch-possible-csrf-attack","errorCode":null,"errorMessage":"State mismatch. Possible CSRF attack","messagePattern":"State mismatch\\. Possible CSRF attack","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/happy-cli/src/commands/connect/authenticateGemini.ts","lineNumber":144,"sourceCode":"            const url = new URL(req.url!, `http://localhost:${port}`);\n            \n            if (url.pathname === '/oauth2callback') {\n                const code = url.searchParams.get('code');\n                const receivedState = url.searchParams.get('state');\n                const error = url.searchParams.get('error');\n                \n                if (error) {\n                    res.writeHead(302, { \n                        'Location': 'https://developers.google.com/gemini-code-assist/auth_failure_gemini' \n                    });\n                    res.end();\n                    server.close();\n                    reject(new Error(`Authentication error: ${error}`));\n                    return;\n                }\n                \n                if (receivedState !== state) {\n                    res.writeHead(400);\n                    res.end('State mismatch. Possible CSRF attack');\n                    server.close();\n                    reject(new Error('Invalid state parameter'));\n                    return;\n                }\n                \n                if (!code) {\n                    res.writeHead(400);\n                    res.end('No authorization code received');\n                    server.close();\n                    reject(new Error('No authorization code received'));\n                    return;\n                }\n                \n                try {\n                    // Exchange code for tokens\n                    const tokens = await exchangeCodeForTokens(code, verifier, port);\n                    ","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/commands/connect/authenticateGemini.ts#L126-L162","documentation":"In the Gemini/Google OAuth callback server, the received `state` query parameter is compared with the state generated when authenticateGemini() built the authorization URL. A mismatch is treated as a possible CSRF attack: the browser gets 'State mismatch. Possible CSRF attack' (HTTP 400) and the promise is rejected with 'Invalid state parameter'. This protects the local redirect endpoint from forged or replayed callbacks.","triggerScenarios":"http://localhost:<port>/oauth2callback is hit with a `state` that differs from this run's generated state — stale tab from a prior auth attempt completing the callback, two concurrent authenticateGemini() runs racing on the same port, or the state param dropped/rewritten in transit.","commonSituations":"User re-runs Gemini connect while an old Google consent tab is still open and completes the stale one; Google session already authorized so Google redirects immediately with an old cached flow; a bookmarked callback URL from a previous session; port 54545 occupied by another happy process handling its own state.","solutions":["Close all old Google auth tabs/windows, then rerun `happy` connect so the state in the URL matches the running callback server.","Make sure only one connect flow is running at a time (don't run connect in two terminals concurrently).","Open the exact auth URL the CLI just printed — do not reuse an earlier URL whose state has expired.","If a rogue process is squatting on port 54545, kill it or let the CLI pick a fresh port, then retry."],"exampleFix":"// before: completing an old consent tab\n// old tab: http://localhost:54545/oauth2callback?code=...&state=<old-state>  → rejected\n// after: close old tabs, rerun\ncode: await authenticateGemini(); // fresh state matches fresh callback","handlingStrategy":"try-catch","validationCode":"// Ensure the callback port is free and no other auth flow is running\nconst portInUse = !(await isPortFree(54545));\nif (portInUse) console.warn('Port 54545 busy — another Gemini auth flow may be running; close it before reconnecting');","typeGuard":null,"tryCatchPattern":"try {\n  const tokens = await authenticateGemini();\n} catch (err) {\n  if (err instanceof Error && /Invalid state parameter|CSRF/i.test(err.message)) {\n    console.error('State mismatch — an old or forged callback reached the server. Close old Google auth tabs and rerun connect.');\n  } else throw err;\n}","preventionTips":["Close previous Google consent tabs before starting a new connect flow.","Never run two Gemini authentications concurrently; each generates its own state and they can cross callbacks.","Always use the auth URL printed by the current run, not a saved one.","If the default port is occupied by another happy process, stop that process so states don't get mixed across servers."],"tags":["oauth","csrf","state-mismatch","google","authentication"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}