mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-27 01:22:18 +00:00
fix(proxy/cwe-209): strip Python traceback from HTTP 503 error responses
The /cache/ping endpoint included a full Python traceback in its 503 error response body (inside the ProxyException message), leaking internal file paths, line numbers, and call stacks to any caller. Two MCP route handlers in proxy_server.py similarly interpolated str(e) into "Internal server error" detail strings. Fix: log the traceback server-side via verbose_proxy_logger.exception() and omit it from the ProxyException payload / HTTPException detail returned to clients. Tests updated to assert no "traceback" keyword or frame paths appear in the 503 body, with a new dedicated regression test. CWE-209: Generation of Error Message Containing Sensitive Information. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cff3e0b75e
commit
401aa807ee
3 changed files with 30 additions and 10 deletions
|
|
@ -98,13 +98,11 @@ async def cache_ping():
|
|||
litellm_cache_params=safe_dumps(litellm_cache_params),
|
||||
)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
verbose_proxy_logger.exception("Cache health check failed: %s", str(e))
|
||||
error_message = {
|
||||
"message": f"Service Unhealthy ({str(e)})",
|
||||
"litellm_cache_params": safe_dumps(litellm_cache_params),
|
||||
"health_check_cache_params": safe_dumps(cleaned_cache_params),
|
||||
"traceback": traceback.format_exc(),
|
||||
}
|
||||
raise ProxyException(
|
||||
message=safe_dumps(error_message),
|
||||
|
|
|
|||
|
|
@ -15696,10 +15696,10 @@ async def toolset_mcp_route(toolset_name: str, request: Request):
|
|||
except HTTPException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.error(
|
||||
f"Error handling toolset MCP route for {toolset_name}: {str(e)}"
|
||||
verbose_proxy_logger.exception(
|
||||
"Error handling toolset MCP route for %s: %s", toolset_name, str(e)
|
||||
)
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
async def _mcp_forward_as_path(path_segment: str, request: Request):
|
||||
|
|
@ -15877,7 +15877,7 @@ async def dynamic_mcp_route(mcp_server_name: str, request: Request):
|
|||
except HTTPException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.error(
|
||||
f"Error handling dynamic MCP route for {mcp_server_name}: {str(e)}"
|
||||
verbose_proxy_logger.exception(
|
||||
"Error handling dynamic MCP route for %s: %s", mcp_server_name, str(e)
|
||||
)
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
|
|
|||
|
|
@ -123,12 +123,34 @@ def test_cache_ping_failure(mock_redis_failure):
|
|||
assert "message" in error_details
|
||||
assert "litellm_cache_params" in error_details
|
||||
assert "health_check_cache_params" in error_details
|
||||
assert "traceback" in error_details
|
||||
|
||||
# Verify specific error message
|
||||
assert "invalid username-password pair" in error_details["message"]
|
||||
|
||||
|
||||
def test_cache_ping_failure_does_not_expose_traceback(mock_redis_failure):
|
||||
"""CWE-209: Stack trace must not appear in the HTTP 503 response body."""
|
||||
response = client.get("/cache/ping", headers={"Authorization": "Bearer sk-1234"})
|
||||
assert response.status_code == 503
|
||||
|
||||
data = response.json()
|
||||
error = data.get("error", {})
|
||||
raw_body = json.dumps(data)
|
||||
|
||||
# The word "traceback" (case-insensitive) must not appear anywhere in the response
|
||||
assert (
|
||||
"traceback" not in raw_body.lower()
|
||||
), "CWE-209: Python traceback exposed in HTTP 503 response body"
|
||||
# Internal frame paths should not leak either
|
||||
assert (
|
||||
'File "' not in raw_body
|
||||
), "CWE-209: Python stack frame paths exposed in HTTP 503 response body"
|
||||
|
||||
# The error message field should still describe the failure
|
||||
error_details = json.loads(error["message"])
|
||||
assert "invalid username-password pair" in error_details["message"]
|
||||
|
||||
|
||||
def test_cache_ping_no_cache_initialized():
|
||||
"""Test cache ping when no cache is initialized"""
|
||||
# Set cache to None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue