{"record":{"id":"11be86c3268985d5","repo":"BoundaryML/baml","slug":"csrf-state-mismatch","errorCode":null,"errorMessage":"CSRF state mismatch","messagePattern":"CSRF state mismatch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/cli/src/propelauth.rs","lineNumber":158,"sourceCode":"                Err(e) => {\n                    log::debug!(\"Error opening browser: {e:#}\");\n                    println!(\"  An error occurred while opening the browser.\");\n                    println!();\n                    println!(\"  Click here to login:\\n\\n{auth_url}\");\n                }\n            }\n        } else {\n            println!(\"  Click here to login:\\n\\n{auth_url}\");\n        }\n\n        // Wait for the code from the channel\n        let params = rx\n            .recv()\n            .await\n            .context(\"Timed out waiting for the authorization server\")?;\n\n        if params.state != state {\n            anyhow::bail!(\"CSRF state mismatch\");\n        }\n\n        log::debug!(\"Received authorization callback: {params:?}\");\n        Ok((params.code, redirect_uri))\n    }\n\n    pub(crate) async fn request_access_token(\n        &self,\n        code: &str,\n        redirect_uri: &str,\n        code_verifier: &str,\n    ) -> Result<GetAccessTokenResponse> {\n        // Make the POST request\n        let client = baml_runtime::request::create_client()?;\n        let response = client\n            .post(format!(\"{}/propelauth/oauth/token\", self.auth_url))\n            .header(\"Content-Type\", \"application/x-www-form-urlencoded\")\n            .form(&[","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/cli/src/propelauth.rs#L140-L176","documentation":"During the PropelAuth OAuth authorization-code flow, the CLI's local callback server receives a `state` parameter that does not match the random state the CLI generated. This CSRF protection check fails to prevent cross-site request forgery / response mixing attacks on the redirect.","triggerScenarios":"The browser redirects to the local callback with a stale or tampered state value; another login tab completing a flow that overwrote the expected state; the auth server dropping or altering the state query parameter.","commonSituations":"User logs in twice with two tabs; browser extension or proxy strips query params; retrying login after a previous timed-out attempt; manually pasting a callback URL from an older attempt.","solutions":["Close all stale login tabs and restart `baml login` from scratch in one tab","Ensure the full callback URL including the state parameter reaches the local server (disable interfering extensions/proxies)","Retry the login after clearing browser cookies for the auth domain"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await login();\n} catch (e) {\n  if (String(e).includes('CSRF state mismatch')) {\n    closeStaleTabs();\n    await login(); // retry once with fresh state\n  }\n}","preventionTips":["Run only one login flow at a time","Restart login after any timeout instead of reusing old redirect URLs","Avoid extensions/proxies that rewrite callback query parameters"],"tags":["oauth","security","csrf","cli"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}