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 <noreply@anthropic.com>
This commit is contained in:
Daniel JB Clark 2026-10-03 14:50:45 -04:00
parent 0e756bf0f8
commit abadd88dbe
No known key found for this signature in database
3 changed files with 31 additions and 4 deletions

View file

@ -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":

View file

@ -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,

View file

@ -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)