From 7a5b6d7990c9fccfd44ea2e38beb566c1cf1f936 Mon Sep 17 00:00:00 2001 From: er-s-an <92594860+er-s-an@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:23:23 +0800 Subject: [PATCH] fix(http): raise aiohttp max_field_size for large response headers GitHub OAuth token responses include ~9KB Content-Security-Policy headers that exceed aiohttp's 8190 default and fail MCP token exchange with LineTooLong. Pass a higher max_field_size/max_line_size on both the shared proxy session and the aiohttp transport session factory. Fixes #48468 --- litellm/constants.py | 4 ++++ litellm/llms/custom_httpx/http_handler.py | 4 ++++ litellm/proxy/proxy_server.py | 9 ++++++- .../test_aiohttp_cleanup_closed.py | 21 ++++++++++++++++ .../unit/proxy/test_aiohttp_cleanup_closed.py | 24 ++++++++++++++----- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 0d6dfb5c0d4..0aa01783da3 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -388,6 +388,10 @@ AIOHTTP_CONNECTOR_LIMIT: Final = get_env_int("AIOHTTP_CONNECTOR_LIMIT", 1000) AIOHTTP_CONNECTOR_LIMIT_PER_HOST: Final = get_env_int("AIOHTTP_CONNECTOR_LIMIT_PER_HOST", 500) AIOHTTP_KEEPALIVE_TIMEOUT: Final = get_env_int("AIOHTTP_KEEPALIVE_TIMEOUT", 15) AIOHTTP_TTL_DNS_CACHE: Final = get_env_int("AIOHTTP_TTL_DNS_CACHE", 300) +# Raised above aiohttp's 8190 default so large response headers (e.g. GitHub CSP on OAuth +# token exchange) parse instead of raising LineTooLong. https://github.com/BerriAI/litellm/issues/48468 +AIOHTTP_MAX_FIELD_SIZE: Final = get_env_int("AIOHTTP_MAX_FIELD_SIZE", 32768) +AIOHTTP_MAX_LINE_SIZE: Final = get_env_int("AIOHTTP_MAX_LINE_SIZE", 32768) # TCP keep-alive (SO_KEEPALIVE) — opt-in. Required when running behind NAT/LBs # whose idle timeout is shorter than provider response timeouts (e.g. AWS NAT # Gateway: 350s vs OpenAI/Azure: 600s). Without this, the kernel sends nothing diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index 0b64885df45..8081705a2c6 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -29,6 +29,8 @@ from litellm.constants import ( AIOHTTP_CONNECTOR_LIMIT, AIOHTTP_CONNECTOR_LIMIT_PER_HOST, AIOHTTP_KEEPALIVE_TIMEOUT, + AIOHTTP_MAX_FIELD_SIZE, + AIOHTTP_MAX_LINE_SIZE, AIOHTTP_NEEDS_CLEANUP_CLOSED, AIOHTTP_SO_KEEPALIVE, AIOHTTP_TCP_KEEPCNT, @@ -1351,6 +1353,8 @@ class AsyncHTTPHandler: connector=TCPConnector(**transport_connector_kwargs), cookie_jar=DummyCookieJar(), trust_env=trust_env, + max_field_size=AIOHTTP_MAX_FIELD_SIZE, + max_line_size=AIOHTTP_MAX_LINE_SIZE, ) # Use shared session if provided and valid diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 04f84f1f7b5..0ac8f47b575 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -68,6 +68,8 @@ from litellm.constants import ( AIOHTTP_CONNECTOR_LIMIT, AIOHTTP_CONNECTOR_LIMIT_PER_HOST, AIOHTTP_KEEPALIVE_TIMEOUT, + AIOHTTP_MAX_FIELD_SIZE, + AIOHTTP_MAX_LINE_SIZE, AIOHTTP_NEEDS_CLEANUP_CLOSED, AIOHTTP_TTL_DNS_CACHE, AUDIO_SPEECH_CHUNK_SIZE, @@ -1401,7 +1403,12 @@ async def initialize_shared_aiohttp_session() -> "ClientSession | None": connector_kwargs["socket_factory"] = socket_factory connector: Final = TCPConnector(**connector_kwargs) - session: Final = ClientSession(connector=connector, cookie_jar=DummyCookieJar()) + session: Final = ClientSession( + connector=connector, + cookie_jar=DummyCookieJar(), + max_field_size=AIOHTTP_MAX_FIELD_SIZE, + max_line_size=AIOHTTP_MAX_LINE_SIZE, + ) verbose_proxy_logger.info( "SESSION REUSE: Created shared aiohttp session for connection pooling (ID: %s, limit=%s, limit_per_host=%s)", diff --git a/tests/unit/llms/custom_httpx/test_aiohttp_cleanup_closed.py b/tests/unit/llms/custom_httpx/test_aiohttp_cleanup_closed.py index 03f83903e55..ad6e38675dd 100644 --- a/tests/unit/llms/custom_httpx/test_aiohttp_cleanup_closed.py +++ b/tests/unit/llms/custom_httpx/test_aiohttp_cleanup_closed.py @@ -37,3 +37,24 @@ def test_create_aiohttp_transport_omits_enable_cleanup_closed_when_not_needed( transport.get_valid_client_session() assert "enable_cleanup_closed" not in mock_tcp_connector.call_args.kwargs + + +def test_create_aiohttp_transport_raises_header_size_limits(): + from litellm.constants import AIOHTTP_MAX_FIELD_SIZE, AIOHTTP_MAX_LINE_SIZE + from litellm.llms.custom_httpx import http_handler as http_handler_module + + connector_mock = MagicMock(name="connector") + session_mock = MagicMock(name="session") + + with patch.object(http_handler_module, "TCPConnector", return_value=connector_mock): + with ( + patch.object(http_handler_module, "ClientSession", return_value=session_mock) as mock_session, + patch.object(http_handler_module, "DummyCookieJar", return_value=MagicMock(name="cookie_jar")), + ): + transport = http_handler_module.AsyncHTTPHandler.create_aiohttp_transport(shared_session=None) + transport.get_valid_client_session() + + assert mock_session.call_args.kwargs["max_field_size"] == AIOHTTP_MAX_FIELD_SIZE + assert mock_session.call_args.kwargs["max_line_size"] == AIOHTTP_MAX_LINE_SIZE + assert AIOHTTP_MAX_FIELD_SIZE > 8190 + assert AIOHTTP_MAX_LINE_SIZE > 8190 diff --git a/tests/unit/proxy/test_aiohttp_cleanup_closed.py b/tests/unit/proxy/test_aiohttp_cleanup_closed.py index c9b4d6475ab..7217e02f9c5 100644 --- a/tests/unit/proxy/test_aiohttp_cleanup_closed.py +++ b/tests/unit/proxy/test_aiohttp_cleanup_closed.py @@ -11,9 +11,7 @@ def test_initialize_shared_aiohttp_session_sets_enable_cleanup_closed_when_neede session_mock = MagicMock(name="session") monkeypatch.setattr(proxy_server_module, "AIOHTTP_NEEDS_CLEANUP_CLOSED", True) - with patch( - "aiohttp.TCPConnector", return_value=connector_mock - ) as mock_tcp_connector: + with patch("aiohttp.TCPConnector", return_value=connector_mock) as mock_tcp_connector: with patch("aiohttp.ClientSession", return_value=session_mock): asyncio.run(proxy_server_module._initialize_shared_aiohttp_session()) @@ -29,10 +27,24 @@ def test_initialize_shared_aiohttp_session_omits_enable_cleanup_closed_when_not_ session_mock = MagicMock(name="session") monkeypatch.setattr(proxy_server_module, "AIOHTTP_NEEDS_CLEANUP_CLOSED", False) - with patch( - "aiohttp.TCPConnector", return_value=connector_mock - ) as mock_tcp_connector: + with patch("aiohttp.TCPConnector", return_value=connector_mock) as mock_tcp_connector: with patch("aiohttp.ClientSession", return_value=session_mock): asyncio.run(proxy_server_module._initialize_shared_aiohttp_session()) assert "enable_cleanup_closed" not in mock_tcp_connector.call_args.kwargs + + +def test_initialize_shared_aiohttp_session_raises_header_size_limits(): + from litellm.constants import AIOHTTP_MAX_FIELD_SIZE, AIOHTTP_MAX_LINE_SIZE + from litellm.proxy import proxy_server as proxy_server_module + + connector_mock = MagicMock(name="connector") + session_mock = MagicMock(name="session") + + with patch("aiohttp.TCPConnector", return_value=connector_mock): + with patch("aiohttp.ClientSession", return_value=session_mock) as mock_session: + asyncio.run(proxy_server_module._initialize_shared_aiohttp_session()) + + assert mock_session.call_args.kwargs["max_field_size"] == AIOHTTP_MAX_FIELD_SIZE + assert mock_session.call_args.kwargs["max_line_size"] == AIOHTTP_MAX_LINE_SIZE + assert AIOHTTP_MAX_FIELD_SIZE > 8190