This commit is contained in:
Anneshu Nag 2026-10-19 19:55:37 +00:00 • committed by GitHub
commit cb523b86da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 51 additions and 4 deletions

View file

@ -145,10 +145,13 @@ every session-token claim set. Four 256-character ASCII URIs seal to roughly 1.5
the encoded client_id is checked against its own cap before registration succeeds.
VS Code registers four callbacks for its web and desktop environments."""
MAX_STATE_LENGTH: Final = 1024
MAX_STATE_LENGTH: Final = 2048
"""Bound on the client ``state`` sealed into the flow cookie and echoed on the auth-code
redirect. An unbounded ``state`` can push the sealed cookie past the browser's ~4KB cap
(silently dropped, breaking the flow); spec clients send a short opaque value."""
redirect. RFC 6749 sets no maximum: ``state`` is opaque and must round-trip unchanged,
and deployed clients send values past 1024 characters (Gemini on the web sends 1112).
An unbounded ``state`` can still push the sealed cookie past the browser's ~4KB cap
(silently dropped, breaking the flow); a 2048-character ``state`` alongside typical
client_id and redirect_uri values seals to roughly 3.5KB, inside that budget."""
MIN_CODE_VERIFIER_LENGTH: Final = 43
MAX_CODE_VERIFIER_LENGTH: Final = 128

View file

@ -23,6 +23,7 @@ from litellm.proxy._experimental.mcp_server.gateway_dcr_flow import (
JWT_BEARER_GRANT_TYPE,
MANUAL_DELIVERY_AUTH_CODE_TTL_SECONDS,
MAX_CLIENT_ID_LENGTH,
MAX_STATE_LENGTH,
SUBJECT_TOKEN_TYPES,
TOKEN_EXCHANGE_GRANT_TYPE,
AssertionIdentity,
@ -306,6 +307,49 @@ async def test_authorize_validation_failures_never_redirect_to_client():
assert json.loads(response.body)["error"] == expected_error
@pytest.mark.asyncio
async def test_authorize_accepts_gemini_length_state_and_echoes_it_back():
"""Gemini on the web sends an 1112-character state (issue #46414): authorize must
accept it and the code redirect must echo it back unchanged."""
client_id = (await _register([REDIRECT_URI]))["client_id"]
long_state: Final = "s" * 1112
response = aggregate_authorize(
request=_request(query=f"client_id={client_id}"),
client_id=client_id,
redirect_uri=REDIRECT_URI,
state=long_state,
code_challenge=CODE_CHALLENGE,
code_challenge_method="S256",
response_type="code",
session_user_id="u1",
)
assert response.status_code == 303
handle, cookies = _flow_cookie_from(response)
assert len(next(iter(cookies.values()))) < 4096
completed = await complete_connect_flow(
request=_request("/authorize/complete", cookies=cookies, method="POST"),
flow_handle=handle,
session_user_id="u1",
cache=DualCache(),
)
assert completed.status_code == 303
returned = parse_qs(urlparse(completed.headers["location"]).query)
assert returned["state"] == [long_state]
over_cap = aggregate_authorize(
request=_request(query=f"client_id={client_id}"),
client_id=client_id,
redirect_uri=REDIRECT_URI,
state="s" * (MAX_STATE_LENGTH + 1),
code_challenge=CODE_CHALLENGE,
code_challenge_method="S256",
response_type="code",
session_user_id="u1",
)
assert over_cap.status_code == 400
assert json.loads(over_cap.body)["error"] == "invalid_request"
@pytest.mark.asyncio
async def test_authorize_without_session_redirects_to_login_with_return_to():
client_id = (await _register([REDIRECT_URI]))["client_id"]
@ -631,7 +675,7 @@ async def test_authorize_rejects_over_long_state():
request=_request(query=f"client_id={client_id}"),
client_id=client_id,
redirect_uri=REDIRECT_URI,
state="s" * 2000,
state="s" * (MAX_STATE_LENGTH + 1),
code_challenge=CODE_CHALLENGE,
code_challenge_method="S256",
response_type="code",