forked from phoenix/litellm-mirror
fix(http_parsing_utils.py): remove ast.literal_eval()
from http utils
Security fix - https://huntr.com/bounties/96a32812-213c-4819-ba4e-36143d35e95b?token=bf414bbd77f8b346556e 64ab2dd9301ea44339910877ea50401c76f977e36cdd78272f5fb4ca852a88a7e832828aae1192df98680544ee24aa98f3cf6980d8 bab641a66b7ccbc02c0e7d4ddba2db4dbe7318889dc0098d8db2d639f345f574159814627bb084563bad472e2f990f825bff0878a9 e281e72c88b4bc5884d637d186c0d67c9987c57c3f0caf395aff07b89ad2b7220d1dd7d1b427fd2260b5f01090efce5250f8b56ea2 c0ec19916c24b23825d85ce119911275944c840a1340d69e23ca6a462da610
This commit is contained in:
parent
a6da3dea03
commit
bbf31346ca
7 changed files with 120 additions and 46 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
import ast
|
||||
import json
|
||||
from typing import List, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from fastapi import Request, UploadFile, status
|
||||
|
||||
|
@ -8,31 +8,43 @@ from litellm._logging import verbose_proxy_logger
|
|||
from litellm.types.router import Deployment
|
||||
|
||||
|
||||
async def _read_request_body(request: Optional[Request]) -> dict:
|
||||
async def _read_request_body(request: Optional[Request]) -> Dict:
|
||||
"""
|
||||
Asynchronous function to read the request body and parse it as JSON or literal data.
|
||||
Safely read the request body and parse it as JSON.
|
||||
|
||||
Parameters:
|
||||
- request: The request object to read the body from
|
||||
|
||||
Returns:
|
||||
- dict: Parsed request data as a dictionary
|
||||
- dict: Parsed request data as a dictionary or an empty dictionary if parsing fails
|
||||
"""
|
||||
try:
|
||||
request_data: dict = {}
|
||||
if request is None:
|
||||
return request_data
|
||||
return {}
|
||||
|
||||
# Read the request body
|
||||
body = await request.body()
|
||||
|
||||
if body == b"" or body is None:
|
||||
return request_data
|
||||
# Return empty dict if body is empty or None
|
||||
if not body:
|
||||
return {}
|
||||
|
||||
# Decode the body to a string
|
||||
body_str = body.decode()
|
||||
try:
|
||||
request_data = ast.literal_eval(body_str)
|
||||
except Exception:
|
||||
request_data = json.loads(body_str)
|
||||
return request_data
|
||||
except Exception:
|
||||
|
||||
# Attempt JSON parsing (safe for untrusted input)
|
||||
return json.loads(body_str)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
# Log detailed information for debugging
|
||||
verbose_proxy_logger.exception("Invalid JSON payload received.")
|
||||
return {}
|
||||
|
||||
except Exception as e:
|
||||
# Catch unexpected errors to avoid crashes
|
||||
verbose_proxy_logger.exception(
|
||||
"Unexpected error reading request body - {}".format(e)
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import pytest
|
||||
|
||||
import litellm
|
||||
|
||||
|
||||
def test_mlflow_logging():
|
||||
litellm.success_callback = ["mlflow"]
|
||||
litellm.failure_callback = ["mlflow"]
|
||||
|
||||
litellm.completion(
|
||||
model="gpt-4o-mini",
|
||||
messages=[{"role": "user", "content": "what llm are u"}],
|
||||
max_tokens=10,
|
||||
temperature=0.2,
|
||||
user="test-user",
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_async_mlflow_logging():
|
||||
litellm.success_callback = ["mlflow"]
|
||||
litellm.failure_callback = ["mlflow"]
|
||||
|
||||
await litellm.acompletion(
|
||||
model="gpt-4o-mini",
|
||||
messages=[{"role": "user", "content": "hi test from local arize"}],
|
||||
mock_response="hello",
|
||||
temperature=0.1,
|
||||
user="OTEL_USER",
|
||||
)
|
79
tests/local_testing/test_http_parsing_utils.py
Normal file
79
tests/local_testing/test_http_parsing_utils.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
import pytest
|
||||
from fastapi import Request
|
||||
from fastapi.testclient import TestClient
|
||||
from starlette.datastructures import Headers
|
||||
from starlette.requests import HTTPConnection
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_request_body_valid_json():
|
||||
"""Test the function with a valid JSON payload."""
|
||||
|
||||
class MockRequest:
|
||||
async def body(self):
|
||||
return b'{"key": "value"}'
|
||||
|
||||
request = MockRequest()
|
||||
result = await _read_request_body(request)
|
||||
assert result == {"key": "value"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_request_body_empty_body():
|
||||
"""Test the function with an empty body."""
|
||||
|
||||
class MockRequest:
|
||||
async def body(self):
|
||||
return b""
|
||||
|
||||
request = MockRequest()
|
||||
result = await _read_request_body(request)
|
||||
assert result == {}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_request_body_invalid_json():
|
||||
"""Test the function with an invalid JSON payload."""
|
||||
|
||||
class MockRequest:
|
||||
async def body(self):
|
||||
return b'{"key": value}' # Missing quotes around `value`
|
||||
|
||||
request = MockRequest()
|
||||
result = await _read_request_body(request)
|
||||
assert result == {} # Should return an empty dict on failure
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_request_body_large_payload():
|
||||
"""Test the function with a very large payload."""
|
||||
large_payload = '{"key":' + '"a"' * 10**6 + "}" # Large payload
|
||||
|
||||
class MockRequest:
|
||||
async def body(self):
|
||||
return large_payload.encode()
|
||||
|
||||
request = MockRequest()
|
||||
result = await _read_request_body(request)
|
||||
assert result == {} # Large payloads could trigger errors, so validate behavior
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_request_body_unexpected_error():
|
||||
"""Test the function when an unexpected error occurs."""
|
||||
|
||||
class MockRequest:
|
||||
async def body(self):
|
||||
raise ValueError("Unexpected error")
|
||||
|
||||
request = MockRequest()
|
||||
result = await _read_request_body(request)
|
||||
assert result == {} # Ensure fallback behavior
|
|
@ -415,3 +415,18 @@ def test_allowed_route_inside_route(
|
|||
)
|
||||
== expected_result
|
||||
)
|
||||
|
||||
|
||||
def test_read_request_body():
|
||||
from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
|
||||
from fastapi import Request
|
||||
|
||||
payload = "()" * 1000000
|
||||
request = Request(scope={"type": "http"})
|
||||
|
||||
async def return_body():
|
||||
return payload
|
||||
|
||||
request.body = return_body
|
||||
result = _read_request_body(request)
|
||||
assert result is not None
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue