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:
Drishna Trivedi 2026-05-20 14:23:56 +05:30
parent cff3e0b75e
commit 401aa807ee
3 changed files with 30 additions and 10 deletions

View file

@ -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),

View file

@ -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")

View file

@ -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