{"record":{"id":"346267df271cd000","repo":"Kuberwastaken/claurst","slug":"oauth-state-mismatch-possible-csrf-attack-346267","errorCode":null,"errorMessage":"OAuth state mismatch — possible CSRF attack","messagePattern":"OAuth state mismatch — possible CSRF attack","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-rust/crates/mcp/src/oauth.rs","lineNumber":291,"sourceCode":"        .write_all(response.as_bytes())\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Failed to write OAuth callback response: {}\", e))?;\n\n    if parsed_url.path() != callback_path {\n        anyhow::bail!(\n            \"OAuth callback path mismatch: expected '{}', got '{}'\",\n            callback_path,\n            parsed_url.path()\n        );\n    }\n\n    if let Some(expected_state) = expected_state {\n        let received_state = parsed_url\n            .query_pairs()\n            .find(|(key, _)| key == \"state\")\n            .map(|(_, value)| value.to_string());\n        if received_state.as_deref() != Some(expected_state) {\n            anyhow::bail!(\"OAuth state mismatch — possible CSRF attack\");\n        }\n    }\n\n    parsed_url\n        .query_pairs()\n        .find(|(key, _)| key == \"code\")\n        .map(|(_, value)| value.to_string())\n        .ok_or_else(|| anyhow::anyhow!(\"OAuth callback did not contain an authorization code\"))\n}\n\npub async fn run_mcp_auth_session(session: McpAuthSession) -> anyhow::Result<McpAuthResult> {\n    let (listener, host, callback_path) = bind_callback_listener(&session.redirect_uri).await?;\n    open::that(&session.auth_url)\n        .map_err(|e| anyhow::anyhow!(\"Failed to open browser for OAuth: {}\", e))?;\n\n    let code = wait_for_authorization_code(listener, &host, &callback_path, None).await?;\n    let mut token = exchange_code(\n        &session.metadata.token_endpoint,","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/mcp/src/oauth.rs#L273-L309","documentation":"The OAuth authorization response must echo back the `state` parameter the client generated. If the received state does not match the expected value, the response may be a CSRF forgery, so the library aborts the flow rather than exchanging the code.","triggerScenarios":"wait_for_authorization_code receives a callback whose `state` query parameter is missing or differs from expected_state (passed by run_mcp_auth_session).","commonSituations":"Stale browser tab from a previous auth attempt completes after a newer session started; the IDP drops or rewrites the state parameter; user manually edited the callback URL; multiple concurrent auth sessions share one callback port.","solutions":["Abort the stale browser tab and restart the OAuth login from the CLI","Verify the provider preserves the state parameter through the redirect","Avoid running multiple simultaneous MCP auth sessions for the same server","Clear cached auth state for the server and re-run the auth flow"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match run_mcp_auth_session(server).await {\n    Err(e) if e.to_string().contains(\"state mismatch\") => {\n        // discard stale tabs, restart the auth flow from scratch\n    }\n    other => other?,\n}","preventionTips":["Close stale OAuth tabs from previous login attempts before re-authenticating","Run only one auth session per server at a time","Use a provider that preserves the state parameter end-to-end"],"tags":["oauth","csrf","security","state"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}