fix(core): reject empty API keys in Bearer auth headers (#9350)

Empty or None API keys resulted in "Bearer " header values which cause
httpx.LocalProtocolError. Use truthiness checks instead of `is not None`
to also reject empty strings before constructing Authorization headers.

Datadog: https://us5.datadoghq.com/error-tracking/issue/ad3c1e38-d557-11f0-a65d-da7ad0900000

🤖 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Kian Jones
2026-02-06 16:38:14 -08:00
committed by Caren Thomas
parent d592ec3135
commit 745dd1e124
5 changed files with 16 additions and 11 deletions

View File

@@ -176,11 +176,11 @@ class HTTPBasedServerConfig(BaseServerConfig):
Returns:
Dictionary of headers or None if no headers are configured
"""
if self.custom_headers is not None or (self.auth_header is not None and self.auth_token is not None):
if self.custom_headers is not None or (self.auth_header and self.auth_token):
headers = self.custom_headers.copy() if self.custom_headers else {}
# Add auth header if specified
if self.auth_header is not None and self.auth_token is not None:
# Add auth header if specified (skip if either is empty to avoid illegal header values)
if self.auth_header and self.auth_token:
headers[self.auth_header] = self.auth_token
return headers

View File

@@ -154,6 +154,11 @@ class ChatGPTOAuthClient(LLMClientBase):
Returns:
Dictionary of HTTP headers.
"""
if not creds.access_token:
raise LLMAuthenticationError(
message="ChatGPT OAuth access_token is empty or missing",
code=ErrorCode.UNAUTHENTICATED,
)
return {
"Authorization": f"Bearer {creds.access_token}",
"ChatGPT-Account-Id": creds.account_id,

View File

@@ -10,7 +10,7 @@ async def mistral_get_model_list_async(url: str, api_key: str) -> dict:
url = smart_urljoin(url, "models")
headers = {"Content-Type": "application/json"}
if api_key is not None:
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
logger.debug("Sending request to %s", url)

View File

@@ -59,7 +59,7 @@ async def openai_get_model_list_async(
url = smart_urljoin(url, "models")
headers = {"Content-Type": "application/json"}
if api_key is not None:
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
if "openrouter.ai" in url:
if model_settings.openrouter_referer:
@@ -478,7 +478,7 @@ def openai_chat_completions_request_stream(
data = prepare_openai_payload(chat_completion_request)
data["stream"] = True
kwargs = {"api_key": api_key, "base_url": url, "max_retries": 0}
kwargs = {"api_key": api_key or "DUMMY_API_KEY", "base_url": url, "max_retries": 0}
if "openrouter.ai" in url:
headers = {}
if model_settings.openrouter_referer:
@@ -511,7 +511,7 @@ def openai_chat_completions_request(
https://platform.openai.com/docs/guides/text-generation?lang=curl
"""
data = prepare_openai_payload(chat_completion_request)
kwargs = {"api_key": api_key, "base_url": url, "max_retries": 0}
kwargs = {"api_key": api_key or "DUMMY_API_KEY", "base_url": url, "max_retries": 0}
if "openrouter.ai" in url:
headers = {}
if model_settings.openrouter_referer:

View File

@@ -25,15 +25,15 @@ def post_json_auth_request(uri, json_payload, auth_type, auth_key):
# Used by OpenAI, together.ai, Mistral AI
elif auth_type == "bearer_token":
if auth_key is None:
raise ValueError(f"auth_type is {auth_type}, but auth_key is null")
if not auth_key:
raise ValueError(f"auth_type is {auth_type}, but auth_key is null or empty")
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {auth_key}"}
response = requests.post(uri, json=json_payload, headers=headers)
# Used by OpenAI Azure
elif auth_type == "api_key":
if auth_key is None:
raise ValueError(f"auth_type is {auth_type}, but auth_key is null")
if not auth_key:
raise ValueError(f"auth_type is {auth_type}, but auth_key is null or empty")
headers = {"Content-Type": "application/json", "api-key": f"{auth_key}"}
response = requests.post(uri, json=json_payload, headers=headers)