From abadd88dbe22f9d99a10ef73c35750bfa531a54b Mon Sep 17 00:00:00 2001 From: Daniel JB Clark Date: Sat, 3 Oct 2026 14:50:45 -0400 Subject: [PATCH] fix(clinepass): never borrow the global litellm.api_key for chat _complete_clinepass fell back to litellm.api_key when no ClinePass credential was configured, so a caller's general-purpose (usually OpenAI) key was sent to the Cline host as a Bearer token. get_llm_provider already resolves the key without that fallback; use what it resolved. Adds a regression test that asserts the sentinel key never leaves the process, and checks the request actually reached the transport so it cannot pass vacuously. Also suppress three basedpyright reportUnknownArgumentType diagnostics this change introduced (the untyped model cost map key and the dispatch context's messages/headers), which tipped the per-rule budget by +3. Co-Authored-By: Claude Sonnet 5.5 --- litellm/__init__.py | 2 +- litellm/main.py | 6 ++--- .../test_clinepass_endpoint_guard.py | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 83a97c6fd0e..cb13e3df825 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -868,7 +868,7 @@ def _populate_provider_model_sets(model_cost_map: Dict) -> None: elif value.get("litellm_provider") == "deepseek": deepseek_models.add(key) elif value.get("litellm_provider") == "clinepass": - clinepass_models.add(key) + clinepass_models.add(key) # pyright: ignore[reportUnknownArgumentType] # key comes from the untyped model cost map elif value.get("litellm_provider") == "tencent": tencent_models.add(key) elif value.get("litellm_provider") == "runwayml": diff --git a/litellm/main.py b/litellm/main.py index 979808143d7..e8eb20f2060 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2466,15 +2466,15 @@ def _complete_clinepass(ctx: _CompletionDispatchContext) -> _CompletionDispatchR stream: Final = ctx.stream timeout: Final = ctx.timeout - api_key = api_key or get_secret_str("CLINEPASS_API_KEY") or litellm.api_key + api_key = api_key or get_secret_str("CLINEPASS_API_KEY") api_base = api_base or litellm.api_base or get_secret_str("CLINEPASS_API_BASE") or "https://api.cline.bot/api/v1" ## COMPLETION CALL response: Final = base_llm_http_handler.completion( model=model, - messages=messages, - headers=headers, + messages=messages, # pyright: ignore[reportUnknownArgumentType] # ctx.messages is list[Unknown] + headers=headers, # pyright: ignore[reportUnknownArgumentType] # ctx.headers is dict[Unknown, Unknown] model_response=model_response, api_key=api_key, api_base=api_base, diff --git a/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py b/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py index 1ddb517d99b..9897b58897f 100644 --- a/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py +++ b/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py @@ -125,3 +125,30 @@ def test_unknown_kwargs_are_flattened_not_wrapped_in_extra_body(): assert "extra_body" not in params assert params["some_vendor_knob"] == 7 + + +def test_chat_does_not_fall_back_to_the_global_litellm_api_key(monkeypatch): + """`litellm.api_key` is the caller's general-purpose (usually OpenAI) key. + + With no ClinePass credential configured, chat must not borrow it: doing so + sends that key to the Cline host as a Bearer token. + """ + sent: list[tuple[str, str]] = [] + + def record_and_stop(self, *args, **kwargs): + sent.append((str(kwargs.get("url")), str((kwargs.get("headers") or {}).get("Authorization", "")))) + raise RuntimeError("stop before any network I/O") + + monkeypatch.setattr(HTTPHandler, "post", record_and_stop, raising=True) + monkeypatch.delenv("CLINEPASS_API_KEY", raising=False) + monkeypatch.setattr(litellm, "api_key", SENTINEL_OPENAI_KEY) + + with contextlib.suppress(Exception): + litellm.completion( + model="clinepass/deepseek-v4-flash", + messages=[{"role": "user", "content": "hi"}], + num_retries=0, + ) + + assert sent, "the chat request never reached the transport, so nothing was checked" + assert all(SENTINEL_OPENAI_KEY not in authorization for _, authorization in sent)