This commit is contained in:
Vineeth Sai Varikuntla 2026-08-26 11:49:11 -07:00 committed by GitHub
commit 8feb378941
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 87 additions and 8 deletions

View file

@ -9,6 +9,7 @@ Logging Pass-Through Endpoints
"""
import base64
import binascii
import os
from base64 import b64encode
from typing import Final
@ -146,6 +147,27 @@ def _build_langfuse_proxy_target(
return str(updated_url), custom_headers
def _extract_api_key_from_basic_auth(authorization_header: str) -> str:
"""Pull the secret half out of a `Basic base64(public_key:secret_key)` header.
Returns "" for every shape that does not carry one, rather than raising: a
missing header, a value that is not base64, base64 that is not utf-8, and a
decoded value with no ":" in it. `user_api_key_auth` then rejects the empty
key with the normal 401, instead of the route raising IndexError (or
binascii.Error, or UnicodeDecodeError) out of an unauthenticated request and
turning it into a 500 with a traceback in the proxy log.
"""
encoded: Final = authorization_header.removeprefix("Basic ").strip()
if not encoded:
return ""
try:
decoded_str = base64.b64decode(encoded).decode("utf-8")
except (binascii.Error, UnicodeDecodeError):
return ""
_, separator, secret_key = decoded_str.partition(":")
return secret_key if separator else ""
@router.api_route(
"/langfuse/{endpoint:path}",
methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
@ -164,14 +186,8 @@ async def langfuse_proxy_route(
from litellm.proxy.proxy_server import proxy_config
## CHECK FOR LITELLM API KEY IN THE QUERY PARAMS - ?..key=LITELLM_API_KEY
api_key = request.headers.get("Authorization") or ""
## decrypt base64 hash
api_key = api_key.replace("Basic ", "")
decoded_bytes: Final = base64.b64decode(api_key)
decoded_str: Final = decoded_bytes.decode("utf-8")
api_key = decoded_str.split(":")[1] # assume api key is passed in as secret key
## decrypt base64 hash; the api key is passed in as the secret key
api_key: Final = _extract_api_key_from_basic_auth(request.headers.get("Authorization") or "")
user_api_key_dict: Final = await user_api_key_auth(request=request, api_key=f"Bearer {api_key}")

View file

@ -1,4 +1,5 @@
import socket
from base64 import b64encode
import pytest
from fastapi import HTTPException
@ -6,6 +7,7 @@ from fastapi import HTTPException
import litellm
from litellm.proxy.vertex_ai_endpoints.langfuse_endpoints import (
_build_langfuse_proxy_target,
_extract_api_key_from_basic_auth,
_get_langfuse_proxy_credentials,
)
@ -100,3 +102,64 @@ def test_dynamic_langfuse_proxy_target_preserves_host_header_for_http(monkeypatc
assert target_url == "http://8.8.8.8/api/public/projects"
assert headers["Host"] == "langfuse.example"
@pytest.mark.parametrize(
"authorization_header, expected",
[
# Every shape below except the two valid ones raised out of the route
# before, turning an unauthenticated request into a 500 with a traceback.
("", ""), # no Authorization header at all
("Basic ", ""), # header present, credentials empty
("Basic " + b64encode(b"pk-lf-1:sk-lf-2").decode(), "sk-lf-2"),
(b64encode(b"pk-lf-1:sk-lf-2").decode(), "sk-lf-2"), # bare base64, no scheme
("Basic " + b64encode(b"sk-lf-2").decode(), ""), # no ":" to split on
("Basic YWJj=", ""), # not decodable base64
("Basic //4=", ""), # decodes to bytes that are not utf-8
("Bearer sk-1234", ""), # wrong scheme
],
)
def test_extract_api_key_from_basic_auth_never_raises(authorization_header, expected):
assert _extract_api_key_from_basic_auth(authorization_header) == expected
def test_extract_api_key_keeps_a_secret_containing_a_colon():
"""RFC 7617 puts everything after the first ":" in the password, so a secret
with a colon in it must survive whole. `split(":")[1]` truncated it to the
first segment, which then failed authentication for a non-obvious reason."""
header = "Basic " + b64encode(b"pk-lf-1:sk:with:colons").decode()
assert _extract_api_key_from_basic_auth(header) == "sk:with:colons"
@pytest.mark.asyncio
async def test_langfuse_route_hands_missing_credentials_to_the_authenticator(monkeypatch):
"""An unauthenticated request must reach user_api_key_auth and be rejected
there, rather than raising IndexError before authentication runs."""
from fastapi import Request
from litellm.proxy.vertex_ai_endpoints import langfuse_endpoints
seen: dict = {}
async def fake_user_api_key_auth(request, api_key):
seen["api_key"] = api_key
raise HTTPException(status_code=401, detail={"error": "Authentication Error"})
monkeypatch.setattr(langfuse_endpoints, "user_api_key_auth", fake_user_api_key_auth)
request = Request(
{
"type": "http",
"method": "GET",
"path": "/langfuse/zzz",
"headers": [], # the reported case: no Authorization header
"query_string": b"",
}
)
with pytest.raises(HTTPException) as exc:
await langfuse_endpoints.langfuse_proxy_route(endpoint="zzz", request=request, fastapi_response=None)
assert exc.value.status_code == 401
assert seen["api_key"] == "Bearer "