fix(health)!: let configured deployment parameters win over request overrides

When a connection test names a model that resolves to a configured deployment,
that deployment's routing and credential parameters are authoritative. A request
supplying a complete connection of its own is unaffected.

BREAKING CHANGE: /health/test_connection no longer lets a request replace the
routing or credential parameters of a configured model it names. Supply the full
connection parameters instead of naming a configured model.

(cherry picked from commit e5effcb861)
This commit is contained in:
Yuneng Jiang 2026-08-05 14:14:33 -07:00
parent dde20e405c
commit 2653829374
No known key found for this signature in database
2 changed files with 66 additions and 2 deletions

View file

@ -6,7 +6,17 @@ import secrets
import time
import traceback
from datetime import datetime, timedelta
from typing import Any, Dict, Iterable, Literal, Optional, TypedDict, Union, cast
from typing import (
Any,
Dict,
Iterable,
Literal,
Mapping,
Optional,
TypedDict,
Union,
cast,
)
import fastapi
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
@ -28,6 +38,9 @@ from litellm.proxy._types import (
UserAPIKeyAuth,
WebhookEvent,
)
from litellm.proxy.auth.auth_utils import (
_BANNED_REQUEST_BODY_PARAMS, # pyright: ignore[reportPrivateUsage] # one canonical list, shared with the request-body check
)
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler
from litellm.proxy.health_check import (
@ -79,6 +92,27 @@ def _reject_os_environ_references(params: dict) -> None:
stack.append(value)
def _reject_banned_param_overrides(request_params: Mapping[str, object]) -> None:
"""Reject request params that would replace a configured deployment's routing or credentials.
Applied only when a configured deployment supplies the base parameters. The
request may still adjust benign fields; routing and credential fields come
from the configuration. A caller who wants a fully custom connection supplies
the complete parameter set instead of naming a configured model.
"""
for param in _BANNED_REQUEST_BODY_PARAMS:
if param in request_params:
raise HTTPException(
status_code=400,
detail={
"error": (
f"{param} cannot be overridden when testing a configured model. "
"Provide the full connection parameters instead of naming a configured model."
)
},
)
def get_callback_identifier(callback):
"""
Get the callback identifier string, handling both strings and objects.
@ -1860,7 +1894,6 @@ async def test_model_connection(
)
# Merge: config params (from proxy config) as base, request params override
# This allows users to override specific params while using config for credentials
litellm_params = {**config_litellm_params, **request_litellm_params}
## Auth check
@ -1875,6 +1908,8 @@ async def test_model_connection(
prisma_client=prisma_client,
premium_user=premium_user,
)
if config_litellm_params:
_reject_banned_param_overrides(request_litellm_params)
# Include health_check_params if provided
litellm_params = _update_litellm_params_for_health_check(
model_info={},

View file

@ -2364,3 +2364,32 @@ def test_clean_endpoint_data_strips_credentials_keeps_routing_fields():
assert "aws_access_key_id" not in cleaned
assert cleaned.get("api_base") == "https://example.test/v1"
assert cleaned.get("api_version") == "2024-10-21"
class TestRejectBannedParamOverrides:
"""Routing and credential fields come from the deployment configuration when a
request names a configured model; a request that wants its own connection
supplies the whole parameter set instead."""
def test_banned_param_is_refused(self):
from fastapi import HTTPException
from litellm.proxy.health_endpoints._health_endpoints import (
_reject_banned_param_overrides,
)
for param in ("api_base", "base_url", "vertex_credentials", "aws_web_identity_token"):
with pytest.raises(HTTPException) as exc_info:
_reject_banned_param_overrides({"model": "gpt-4o", param: "caller-supplied"})
assert exc_info.value.status_code == 400
assert param in str(exc_info.value.detail)
def test_benign_params_are_allowed(self):
from litellm.proxy.health_endpoints._health_endpoints import (
_reject_banned_param_overrides,
)
_reject_banned_param_overrides({})
_reject_banned_param_overrides({"model": "gpt-4o"})
_reject_banned_param_overrides({"model": "gpt-4o", "api_key": "sk-caller-owned"})
_reject_banned_param_overrides({"mode": "chat", "timeout": 30})