From 24a0b694e479e3217fd97c6b865e7cec393ab76c Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 30 Oct 2026 04:06:05 -0700 Subject: [PATCH] fix(cli): start the browser sign-in on Python 3.10's macOS browser and strip terminal control characters from 401 text Python 3.10's macOS browser class has no name attribute, so every 401 with no passed key crashed before the sign-in, and a 401 body could put escape sequences on the terminal --- .../proxy/client/cli/commands/auto_sign_in.py | 6 ++-- .../proxy/client/cli/test_auto_sign_in.py | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/client/cli/commands/auto_sign_in.py b/litellm/proxy/client/cli/commands/auto_sign_in.py index 10f5955bbac..79894abdebb 100644 --- a/litellm/proxy/client/cli/commands/auto_sign_in.py +++ b/litellm/proxy/client/cli/commands/auto_sign_in.py @@ -1,4 +1,5 @@ import os +import re import subprocess import sys import time @@ -41,6 +42,7 @@ _DISPLAY_VARIABLES: Final = ("DISPLAY", "WAYLAND_DISPLAY") _SIGNED_IN_LOGIN_WITHOUT_KEY_ROW: Final = HTTPStatus.NOT_FOUND _WAIT_FOR_ANOTHER_SIGN_IN_SECONDS: Final = LOGIN_TIMEOUT_SECONDS + 60 _PROXY_REASON_CHARS: Final = 500 +_TERMINAL_CONTROL_CHARACTERS: Final = re.compile(r"[\x00-\x08\x0b-\x1f\x7f-\x9f]") def _runs_in_ci(environ: Mapping[str, str]) -> bool: @@ -87,7 +89,7 @@ def _locate_browser( def _shows_a_window(browser: webbrowser.BaseBrowser) -> bool: - return Path(browser.name).name not in _TEXT_MODE_BROWSERS + return Path(getattr(browser, "name", "")).name not in _TEXT_MODE_BROWSERS @dataclass(frozen=True, slots=True) @@ -212,7 +214,7 @@ def _proxy_reason(error: UnauthorizedError) -> str: original: Final = error.orig_exception response: Final = original.response if isinstance(original, requests.HTTPError) else None body: Final = response.text.strip() if response is not None else "" - reason: Final = redact_string(body) if body else str(error) + reason: Final = _TERMINAL_CONTROL_CHARACTERS.sub("", redact_string(body) if body else str(error)) return reason if len(reason) <= _PROXY_REASON_CHARS else f"{reason[:_PROXY_REASON_CHARS]}..." diff --git a/tests/unit/proxy/client/cli/test_auto_sign_in.py b/tests/unit/proxy/client/cli/test_auto_sign_in.py index 869066e1404..661019e0677 100644 --- a/tests/unit/proxy/client/cli/test_auto_sign_in.py +++ b/tests/unit/proxy/client/cli/test_auto_sign_in.py @@ -307,6 +307,18 @@ def test_an_ssh_session_without_a_display_never_opens_a_browser_on_the_remote_sc assert requests_mock.call_count == 0 +class BrowserWithoutAName(webbrowser.BaseBrowser): + def __init__(self) -> None: + self._name = "default" + + +def test_a_browser_object_without_a_name_still_starts_the_sign_in(requests_mock: Mocker) -> None: + sign_in: Final = PkceBrowserSignIn(find_browser=lambda _name: BrowserWithoutAName(), environ={}) + + assert sign_in.unavailable_because() is None + assert requests_mock.call_count == 0 + + @pytest.mark.parametrize( "environ", [ @@ -640,6 +652,23 @@ def test_a_long_401_body_is_cut_before_it_reaches_the_terminal( assert sign_in.calls == [] +def test_terminal_control_characters_in_a_401_body_never_reach_the_terminal( + requests_mock: Mocker, secret_vault_factory: type[FakeSecretVault] +) -> None: + requests_mock.post( + f"{BASE_URL}/key/generate", status_code=401, text="\x1b]0;title\x07refused\x1b[2J\r\nsecond line\x9b31m" + ) + sign_in: Final = FakeBrowserSignIn(_credential()) + + result: Final = _lite(["keys", "generate"], secret_vault_factory(), sign_in, ["--api-key", "sk-explicit"]) + + assert result.exit_code == 1 + assert "refused" in result.stderr + assert "second line" in result.stderr + assert not {"\x1b", "\x07", "\x9b", "\r"} & set(result.stderr) + assert sign_in.calls == [] + + def test_a_failed_sign_in_names_the_reason_and_points_at_lite_login( requests_mock: Mocker, secret_vault_factory: type[FakeSecretVault] ) -> None: