From 8738280b09686bcb12df26d962f9aae134401e35 Mon Sep 17 00:00:00 2001 From: PiUnknown Date: Tue, 29 Sep 2026 21:25:13 +0530 Subject: [PATCH] fix(proxy): reject non-object JSON request bodies --- .../proxy/common_utils/http_parsing_utils.py | 8 ++++ .../proxy/auth/test_user_api_key_auth.py | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/litellm/proxy/common_utils/http_parsing_utils.py b/litellm/proxy/common_utils/http_parsing_utils.py index 1c2bd7ea217..af9c37934ff 100644 --- a/litellm/proxy/common_utils/http_parsing_utils.py +++ b/litellm/proxy/common_utils/http_parsing_utils.py @@ -208,6 +208,14 @@ async def _read_request_body(request: Request | None) -> dict: code=status.HTTP_400_BAD_REQUEST, ) + if not isinstance(parsed_body, dict): + raise ProxyException( + message="Invalid JSON request body: expected an object", + type="invalid_request_error", + param="request_body", + code=status.HTTP_400_BAD_REQUEST, + ) + # Cache the parsed result _safe_set_request_parsed_body(request=request, parsed_body=parsed_body) return parsed_body diff --git a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py index 470db99108a..aa9f314830d 100644 --- a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py +++ b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py @@ -6542,6 +6542,44 @@ async def test_user_api_key_auth_malformed_body_with_rejected_key_still_returns_ setattr(_proxy_server_mod, k, v) +@pytest.mark.asyncio +@pytest.mark.parametrize("raw_body", [b"[]", b"123", b"null"], ids=["array", "number", "null"]) +async def test_user_api_key_auth_rejects_a_non_object_json_body_with_400(raw_body: bytes): + from fastapi import Request + from starlette.datastructures import URL + + import litellm.proxy.proxy_server as _proxy_server_mod + + request = Request( + scope={ + "type": "http", + "headers": [(b"content-type", b"application/json")], + "method": "POST", + } + ) + request._url = URL(url="/chat/completions") + request._body = raw_body + + attrs = _proxy_attrs_for_centralized_checks(user_custom_auth=None) + originals = {a: getattr(_proxy_server_mod, a, None) for a in attrs} + try: + for k, v in attrs.items(): + setattr(_proxy_server_mod, k, v) + with patch( + "litellm.proxy.auth.user_api_key_auth._user_api_key_auth_builder", + new_callable=AsyncMock, + return_value=UserAPIKeyAuth(api_key="sk-test", user_id="u1"), + ): + with pytest.raises(ProxyException) as exc_info: + await user_api_key_auth(request=request, api_key="Bearer sk-test") + finally: + for k, v in originals.items(): + setattr(_proxy_server_mod, k, v) + + assert exc_info.value.code == str(status.HTTP_400_BAD_REQUEST) + assert exc_info.value.message == "Invalid JSON request body: expected an object" + + @pytest.mark.asyncio async def test_user_api_key_auth_does_not_double_log_a_malformed_body_from_a_rejected_key(): """The auth failure this caller also earns is already logged by the handler that