Hmbown/CodeWhale · error · anyhow

OAuth HTTP response body exceeds

Error message

OAuth HTTP response body exceeds {MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES} bytes

What it means

The OAuth HTTP client enforces a hard cap (MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES) on how many bytes it will read from an OAuth endpoint's response body. While streaming the body chunk-by-chunk, if the next chunk would push the accumulated body past the limit, the read is aborted and this error is returned. This protects the client from memory exhaustion caused by a malicious or misbehaving OAuth server returning an oversized response.

Solutions

  1. Verify the OAuth issuer/discovery URL points at the real OAuth server, not a proxy or login page returning large HTML
  2. Inspect what the server actually returns (curl the URL) and fix the server-side response
  3. If the limit is genuinely too small for a legitimate deployment, raise MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES deliberately

Example fix

// before: metadata URL behind a captive portal returning an HTML page
"discovery_url": "https://portal.example.com/.well-known/oauth-authorization-server"
// after: point directly at the real issuer
"discovery_url": "https://auth.example.com/.well-known/oauth-authorization-server"
Defensive patterns

Strategy: validation

Validate before calling

let url = discovery_url;
if !url.starts_with("https://") || url.contains("portal") {
    // verify the endpoint actually returns a small JSON document before use
}
// optionally: HEAD/GET the URL and check Content-Length < MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES

Try / catch

match oauth_exchange_result {
    Err(e) if e.to_string().contains("exceeds") => {
        // treat as untrusted/misbehaving OAuth endpoint; refuse to proceed and log
    }
    other => other?,
}

Prevention

When it happens

Trigger: Any OAuth HTTP exchange (token request, discovery, JWKS fetch, etc.) where the server's response body length exceeds MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES; detected inside the response.chunk() streaming loop after the accumulated body plus the next chunk exceeds the limit.

Common situations: Pointing the OAuth discovery/authorization metadata URL at a misconfigured proxy or portal that returns a large HTML error/login page instead of a small JSON document; a compromised or rogue MCP server deliberately flooding the response; a reverse proxy returning a huge interstitial page.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/14fb4e60d96db8e0. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/oauth.rs:322

            let mut response = self
                .client
                .execute(
                    request,
                    matches!(redirect_policy, OAuthHttpRedirectPolicy::Follow),
                )
                .await
                .map_err(|error| -> OAuthHttpClientError { error.into() })?;
            let status = response.status();
            let version = response.version();
            let headers = response.headers().clone();
            let mut body = Vec::new();
            while let Some(chunk) = response
                .chunk()
                .await
                .map_err(|error| Box::new(error) as OAuthHttpClientError)?
            {
                if chunk.len() > MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES - body.len() {
                    return Err(anyhow!(
                        "OAuth HTTP response body exceeds {MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES} bytes"
                    )
                    .into());
                }
                body.extend_from_slice(&chunk);
            }
            if is_token_request {
                *self
                    .last_token_response
                    .lock()
                    .unwrap_or_else(std::sync::PoisonError::into_inner) =
                    Some(TokenEndpointReceipt::from_response(status, &headers, &body));
            }
            let mut builder = oauth2::http::Response::builder()
                .status(status)
                .version(version);
            for (name, value) in &headers {
                builder = builder.header(name, value);

View on GitHub (pinned to 73e0f67d83)