BoundaryML/baml · error

CSRF state mismatch

Error message

CSRF state mismatch

What it means

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.

Solutions

  1. Close all stale login tabs and restart `baml login` from scratch in one tab
  2. Ensure the full callback URL including the state parameter reaches the local server (disable interfering extensions/proxies)
  3. Retry the login after clearing browser cookies for the auth domain
Defensive patterns

Strategy: retry

Try / catch

try {
  await login();
} catch (e) {
  if (String(e).includes('CSRF state mismatch')) {
    closeStaleTabs();
    await login(); // retry once with fresh state
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/11be86c3268985d5. Report an issue: GitHub.

Appendix: source

Thrown at engine/cli/src/propelauth.rs:158

                Err(e) => {
                    log::debug!("Error opening browser: {e:#}");
                    println!("  An error occurred while opening the browser.");
                    println!();
                    println!("  Click here to login:\n\n{auth_url}");
                }
            }
        } else {
            println!("  Click here to login:\n\n{auth_url}");
        }

        // Wait for the code from the channel
        let params = rx
            .recv()
            .await
            .context("Timed out waiting for the authorization server")?;

        if params.state != state {
            anyhow::bail!("CSRF state mismatch");
        }

        log::debug!("Received authorization callback: {params:?}");
        Ok((params.code, redirect_uri))
    }

    pub(crate) async fn request_access_token(
        &self,
        code: &str,
        redirect_uri: &str,
        code_verifier: &str,
    ) -> Result<GetAccessTokenResponse> {
        // Make the POST request
        let client = baml_runtime::request::create_client()?;
        let response = client
            .post(format!("{}/propelauth/oauth/token", self.auth_url))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .form(&[

View on GitHub (pinned to bd85ce9dee)