From 0aadf51342a1c82963b78622a6b986b89559a259 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Mon, 23 Mar 2026 21:53:29 -0700 Subject: [PATCH] fix(proxy): ignore return_to in SSO when control_plane_url is not configured Instead of returning a 400 error when return_to is passed without control_plane_url configured, silently ignore it and proceed with the normal same-origin SSO flow. --- litellm/proxy/management_endpoints/ui_sso.py | 37 +++++++++---------- .../proxy/management_endpoints/test_ui_sso.py | 10 ++--- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index d06ce56f81..0c50012abe 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -404,14 +404,14 @@ async def google_login( state=cli_state, ) if return_to is not None and sso_redirect is not None: - SSOAuthenticationHandler._validate_return_to(return_to) - sso_redirect.set_cookie( - key="litellm_cp_return_to", - value=return_to, - max_age=600, - httponly=True, - samesite="lax", - ) + if SSOAuthenticationHandler._validate_return_to(return_to): + sso_redirect.set_cookie( + key="litellm_cp_return_to", + value=return_to, + max_age=600, + httponly=True, + samesite="lax", + ) return sso_redirect elif ui_username is not None: # No Google, Microsoft SSO @@ -1778,22 +1778,19 @@ class SSOAuthenticationHandler: """ @staticmethod - def _validate_return_to(return_to: str) -> None: + def _validate_return_to(return_to: str) -> bool: """ Validate that return_to matches the configured control_plane_url origin. - Raises HTTPException(400) if: - - control_plane_url is not configured in general_settings - - return_to origin does not match control_plane_url origin + Returns True if return_to is valid and should be used. + Returns False if control_plane_url is not configured (return_to is ignored). + Raises HTTPException(400) if return_to origin does not match control_plane_url origin. """ from litellm.proxy.proxy_server import general_settings control_plane_url = general_settings.get("control_plane_url") if control_plane_url is None: - raise HTTPException( - status_code=400, - detail="return_to is not allowed: control_plane_url is not configured", - ) + return False def _origin(url: str) -> tuple: parsed = urlparse(url) @@ -1809,6 +1806,8 @@ class SSOAuthenticationHandler: detail="return_to does not match the configured control_plane_url", ) + return True + @staticmethod async def get_sso_login_redirect( redirect_url: str, @@ -2589,9 +2588,9 @@ class SSOAuthenticationHandler: # Control-plane cross-origin: store JWT behind a single-use opaque # code (60s TTL) so the token never appears in browser history / logs. # The control plane redeems it via POST /v3/login/exchange. - if return_to is not None: - SSOAuthenticationHandler._validate_return_to(return_to) - + if return_to is not None and SSOAuthenticationHandler._validate_return_to( + return_to + ): code = secrets.token_urlsafe(32) cache_key = f"login_code:{code}" cache_value = {"token": jwt_token, "redirect_url": return_to} diff --git a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py index fc9c37b7f8..ff636ca04a 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py +++ b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py @@ -5164,15 +5164,13 @@ def test_generic_response_convertor_extra_attributes_missing_field(monkeypatch): class TestValidateReturnTo: """Tests for SSOAuthenticationHandler._validate_return_to""" - def test_rejects_when_no_control_plane_url_configured(self, monkeypatch): - """return_to should be rejected if control_plane_url is not in general_settings.""" + def test_returns_false_when_no_control_plane_url_configured(self, monkeypatch): + """return_to should be silently ignored if control_plane_url is not in general_settings.""" monkeypatch.setattr( "litellm.proxy.proxy_server.general_settings", {} ) - with pytest.raises(HTTPException) as exc_info: - SSOAuthenticationHandler._validate_return_to("https://cp.example.com/ui") - assert exc_info.value.status_code == 400 - assert "not configured" in exc_info.value.detail + result = SSOAuthenticationHandler._validate_return_to("https://cp.example.com/ui") + assert result is False def test_allows_matching_origin(self, monkeypatch): """return_to matching the configured control_plane_url origin should pass."""