mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-02 07:13:02 +00:00
fix(cli): read a 401 body only as UTF-8 and print nothing from any other body
Some checks failed
Cost map pins recheck / rerun-cost-map-guard (push) Has been cancelled
LiteLLM Rust DB / db-queries (push) Has been cancelled
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Some checks failed
Cost map pins recheck / rerun-cost-map-guard (push) Has been cancelled
LiteLLM Rust DB / db-queries (push) Has been cancelled
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
The fallback to the declared charset crashed on a NUL in the charset name, and a stray Shift_JIS lead byte swallowed the first letter of a key so redaction missed it. Reading only UTF-8 avoids both. A body in another charset now shows the HTTP error text
This commit is contained in:
parent
b827c19110
commit
669614c032
2 changed files with 26 additions and 42 deletions
|
|
@ -216,24 +216,10 @@ def _displayable(text: str) -> str:
|
|||
return "" if _UNPRINTABLE_CHARACTERS.search(normalized) else redact_string(normalized)
|
||||
|
||||
|
||||
def _text_in_declared_charset(response: requests.Response) -> str:
|
||||
try:
|
||||
return response.text
|
||||
except UnicodeError:
|
||||
return ""
|
||||
|
||||
|
||||
def _response_text(response: requests.Response) -> str:
|
||||
try:
|
||||
return response.content.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return _text_in_declared_charset(response)
|
||||
|
||||
|
||||
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(response) if response is not None else ""
|
||||
body: Final = response.content.decode("utf-8", errors="replace") if response is not None else ""
|
||||
reason: Final = _displayable(body) or _displayable(str(error)) or _NO_PROXY_REASON
|
||||
return reason if len(reason) <= _PROXY_REASON_CHARS else f"{reason[:_PROXY_REASON_CHARS]}..."
|
||||
|
||||
|
|
|
|||
|
|
@ -749,40 +749,37 @@ def test_a_utf8_401_body_with_no_charset_prints_as_written(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reason, charset",
|
||||
"content, charset",
|
||||
[
|
||||
("caf\u00e9", "iso-8859-1"),
|
||||
("the key\u2019s owner", "windows-1252"),
|
||||
("\u65e5\u672c", "shift_jis"),
|
||||
("refused", "utf-16"),
|
||||
(b"forbidden \xff\xfe", "utf-8"),
|
||||
(b"forbidden \xff\xfe", "undefined"),
|
||||
(b"forbidden \xff\xfe", "idna"),
|
||||
(b"forbidden \xff\xfe", "bad\x00name"),
|
||||
("forbidden caf\u00e9".encode("iso-8859-1"), "iso-8859-1"),
|
||||
("forbidden \u2019".encode("windows-1252"), "windows-1252"),
|
||||
("forbidden \u65e5\u672c".encode("shift_jis"), "shift_jis"),
|
||||
("forbidden".encode("utf-16"), "utf-16"),
|
||||
(f"forbidden \x81sk-{FAKE_SECRET}".encode("latin-1"), "shift_jis"),
|
||||
],
|
||||
ids=[
|
||||
"stray_bytes_in_utf8",
|
||||
"undefined_charset",
|
||||
"idna_charset",
|
||||
"nul_in_the_charset_name",
|
||||
"latin1",
|
||||
"windows_1252",
|
||||
"shift_jis",
|
||||
"utf16",
|
||||
"lead_byte_before_a_key",
|
||||
],
|
||||
)
|
||||
def test_a_401_body_in_a_declared_charset_prints_as_written(
|
||||
requests_mock: Mocker, secret_vault_factory: type[FakeSecretVault], reason: str, charset: str
|
||||
def test_a_401_body_that_is_not_utf8_is_never_printed(
|
||||
requests_mock: Mocker, secret_vault_factory: type[FakeSecretVault], content: bytes, charset: str
|
||||
) -> None:
|
||||
requests_mock.post(
|
||||
f"{BASE_URL}/key/generate",
|
||||
status_code=401,
|
||||
content=reason.encode(charset),
|
||||
headers={"Content-Type": f"text/plain; charset={charset}"},
|
||||
)
|
||||
|
||||
result: Final = _lite(
|
||||
["keys", "generate"], secret_vault_factory(), FakeBrowserSignIn(_credential()), ["--api-key", "sk-explicit"]
|
||||
)
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert f"The proxy said: {reason}" in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize("charset", ["utf-8", "undefined", "idna"])
|
||||
def test_a_401_body_its_charset_cannot_read_is_never_printed(
|
||||
requests_mock: Mocker, secret_vault_factory: type[FakeSecretVault], charset: str
|
||||
) -> None:
|
||||
requests_mock.post(
|
||||
f"{BASE_URL}/key/generate",
|
||||
status_code=401,
|
||||
content=b"forbidden \xff\xfe",
|
||||
content=content,
|
||||
headers={"Content-Type": f"text/plain; charset={charset}"},
|
||||
)
|
||||
|
||||
|
|
@ -795,6 +792,7 @@ def test_a_401_body_its_charset_cannot_read_is_never_printed(
|
|||
assert "The proxy said: " in result.stderr
|
||||
assert "forbidden" not in result.stderr
|
||||
assert "\ufffd" not in result.stderr
|
||||
assert FAKE_SECRET[-8:] not in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue