mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
fix(proxy): stop request body fields from conflicting with admin-set deployment fields (#51254)
This commit is contained in:
parent
a6ce922f07
commit
8ee4ce8418
3 changed files with 391 additions and 6 deletions
|
|
@ -23,6 +23,10 @@ from litellm.constants import (
|
|||
MINIMUM_CUSTOM_KEY_LENGTH,
|
||||
STANDARD_CUSTOMER_ID_HEADERS,
|
||||
)
|
||||
from litellm.litellm_core_utils.clientside_credential_handler import (
|
||||
_ADMIN_CONFIG_FIELDS_TO_CLEAR_ON_BASE_OVERRIDE, # pyright: ignore[reportPrivateUsage] # one canonical list of deployment credential fields
|
||||
clientside_credential_keys,
|
||||
)
|
||||
from litellm.litellm_core_utils.cloud_storage_security import REQUEST_S3_BUCKET_PARAMS, STORED_S3_BUCKET_PARAM
|
||||
from litellm.litellm_core_utils.credential_accessor import CredentialAccessor, reject_api_key_secret_reference
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import inferred_provider
|
||||
|
|
@ -54,7 +58,12 @@ from litellm.types.router import (
|
|||
server_owned_wif_fields_present,
|
||||
)
|
||||
from litellm.types.router import reject_server_owned_wif_params as _reject_server_owned_wif_params
|
||||
from litellm.types.utils import CustomPricingLiteLLMParams, LlmProviders, oauth_token_exchange_litellm_params
|
||||
from litellm.types.utils import (
|
||||
OIDC_SECRET_REFERENCE_PREFIX,
|
||||
CustomPricingLiteLLMParams,
|
||||
LlmProviders,
|
||||
oauth_token_exchange_litellm_params,
|
||||
)
|
||||
from litellm.types.videos.main import DecodedVideoId
|
||||
|
||||
|
||||
|
|
@ -380,6 +389,37 @@ def _without_route_named_vertex_target(request_body: Mapping[str, object], route
|
|||
return MappingProxyType({key: value for key, value in request_body.items() if key not in VERTEX_TARGET_PARAMS})
|
||||
|
||||
|
||||
_AZURE_ENTRA_CREDENTIAL_PARAMS: Final[tuple[str, ...]] = (
|
||||
"tenant_id",
|
||||
"client_id",
|
||||
"client_secret",
|
||||
"azure_username",
|
||||
"azure_password",
|
||||
"azure_scope",
|
||||
)
|
||||
_MCP_OAUTH_CLIENT_FORM_FIELDS: Final[frozenset[str]] = frozenset(("client_id", "client_secret"))
|
||||
_TRANSCRIPTION_SESSION_ROUTES: Final[frozenset[str]] = frozenset(
|
||||
(
|
||||
"/v1/realtime/transcription_sessions",
|
||||
"/realtime/transcription_sessions",
|
||||
"/openai/v1/realtime/transcription_sessions",
|
||||
)
|
||||
)
|
||||
_TRANSCRIPTION_SESSION_FIELDS: Final[frozenset[str]] = frozenset(("client_secret",))
|
||||
_TOKEN_INTROSPECTION_ROUTE: Final = "/introspect"
|
||||
_BANNED_PARAMS_ALLOWED_IN_METADATA: Final[frozenset[str]] = VERTEX_TARGET_PARAMS | frozenset(
|
||||
_AZURE_ENTRA_CREDENTIAL_PARAMS
|
||||
)
|
||||
|
||||
|
||||
def _entra_params_owned_by_route(route: str | None) -> frozenset[str]:
|
||||
if route in _TRANSCRIPTION_SESSION_ROUTES:
|
||||
return _TRANSCRIPTION_SESSION_FIELDS
|
||||
if route_manages_mcp_servers(route) or route == _TOKEN_INTROSPECTION_ROUTE:
|
||||
return _MCP_OAUTH_CLIENT_FORM_FIELDS
|
||||
return frozenset()
|
||||
|
||||
|
||||
_NESTED_CONFIG_KEYS: Final[tuple[str, ...]] = ("litellm_embedding_config", "extra_body")
|
||||
|
||||
# Metadata containers that carry per-request configuration consumed by the
|
||||
|
|
@ -483,6 +523,7 @@ _BANNED_REQUEST_BODY_PARAMS: Final[tuple[str, ...]] = (
|
|||
# caller-supplied value is the same exfil shape as
|
||||
# ``aws_web_identity_token`` on the Bedrock path.
|
||||
"azure_ad_token",
|
||||
*_AZURE_ENTRA_CREDENTIAL_PARAMS,
|
||||
# Endpoint-targeting fields that retarget the outbound request or
|
||||
# an observability callback. An attacker-controlled value either
|
||||
# exfiltrates the request payload (incl. messages + admin-set
|
||||
|
|
@ -548,8 +589,34 @@ _BANNED_REQUEST_BODY_PARAMS: Final[tuple[str, ...]] = (
|
|||
*sorted(CustomPricingLiteLLMParams.model_fields.keys()),
|
||||
)
|
||||
|
||||
_SECRET_REFERENCE_PREFIXES: Final[tuple[str, ...]] = ("os.environ/", OIDC_SECRET_REFERENCE_PREFIX)
|
||||
_SECRET_RESOLVING_PARAMS: Final[frozenset[str]] = frozenset(
|
||||
(*_BANNED_REQUEST_BODY_PARAMS, *_ADMIN_CONFIG_FIELDS_TO_CLEAR_ON_BASE_OVERRIDE, *clientside_credential_keys)
|
||||
)
|
||||
|
||||
|
||||
def _reject_secret_references(body: Mapping[str, object]) -> None:
|
||||
_reject_secret_reference_in(MappingProxyType({k: v for k, v in body.items() if k in _SECRET_RESOLVING_PARAMS}))
|
||||
|
||||
|
||||
def _reject_secret_reference_in(params: Mapping[str, object]) -> None:
|
||||
referencing: Final = next(
|
||||
(
|
||||
param
|
||||
for param, value in params.items()
|
||||
if isinstance(value, str) and value.startswith(_SECRET_REFERENCE_PREFIXES)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if referencing is not None:
|
||||
raise ValueError(
|
||||
f"Rejected Request: {referencing} holds a secret reference, which only a deployment or credential a "
|
||||
"proxy admin configures can carry."
|
||||
)
|
||||
|
||||
|
||||
_BANNED_METADATA_PARAMS: Final[tuple[str, ...]] = tuple(
|
||||
param for param in _BANNED_REQUEST_BODY_PARAMS if param not in VERTEX_TARGET_PARAMS
|
||||
param for param in _BANNED_REQUEST_BODY_PARAMS if param not in _BANNED_PARAMS_ALLOWED_IN_METADATA
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -707,6 +774,8 @@ def _check_banned_params(
|
|||
*,
|
||||
manages_deployments: bool = False,
|
||||
mcp_server_fields_allowed: bool = False,
|
||||
route_owned_params: frozenset[str] = frozenset(),
|
||||
rejects_secret_references: bool = True,
|
||||
banned_params: tuple[str, ...] = _BANNED_REQUEST_BODY_PARAMS,
|
||||
opt_in_groups: Callable[[], _OptInGroups],
|
||||
defer_model_checks: bool = False,
|
||||
|
|
@ -726,9 +795,13 @@ def _check_banned_params(
|
|||
reject_server_owned_wif_params(wif_body)
|
||||
if not manages_deployments:
|
||||
reject_federated_credential_reference(body)
|
||||
if rejects_secret_references:
|
||||
_reject_secret_references(body)
|
||||
for param in banned_params:
|
||||
if param not in body:
|
||||
continue
|
||||
if param in route_owned_params:
|
||||
continue
|
||||
if general_settings.get("allow_client_side_credentials") is True:
|
||||
# Proxy-wide opt-in: every banned param is permitted, exit
|
||||
# entirely so the rest of the loop doesn't waste work.
|
||||
|
|
@ -951,6 +1024,7 @@ def is_request_body_safe(
|
|||
llm_router,
|
||||
manages_deployments=manages_deployments,
|
||||
mcp_server_fields_allowed=route_manages_mcp_servers(route),
|
||||
route_owned_params=_entra_params_owned_by_route(route),
|
||||
opt_in_groups=opt_in_groups,
|
||||
defer_model_checks=defer_model_checks,
|
||||
defer_s3_buckets=defer_s3_buckets,
|
||||
|
|
@ -976,6 +1050,7 @@ def is_request_body_safe(
|
|||
llm_router,
|
||||
manages_deployments=manages_deployments,
|
||||
banned_params=_BANNED_METADATA_PARAMS,
|
||||
rejects_secret_references=False,
|
||||
opt_in_groups=opt_in_groups,
|
||||
defer_model_checks=defer_model_checks,
|
||||
defer_s3_buckets=defer_s3_buckets,
|
||||
|
|
@ -987,6 +1062,7 @@ def is_request_body_safe(
|
|||
llm_router,
|
||||
manages_deployments=manages_deployments,
|
||||
banned_params=_BANNED_METADATA_PARAMS,
|
||||
rejects_secret_references=False,
|
||||
opt_in_groups=opt_in_groups,
|
||||
defer_model_checks=defer_model_checks,
|
||||
defer_s3_buckets=defer_s3_buckets,
|
||||
|
|
@ -1020,15 +1096,30 @@ def is_request_body_safe(
|
|||
llm_router,
|
||||
manages_deployments=manages_deployments,
|
||||
banned_params=_BANNED_METADATA_PARAMS,
|
||||
rejects_secret_references=False,
|
||||
opt_in_groups=opt_in_groups,
|
||||
defer_model_checks=defer_model_checks,
|
||||
defer_s3_buckets=defer_s3_buckets,
|
||||
)
|
||||
for user_config_params in _user_config_litellm_params(request_body):
|
||||
reject_api_key_secret_reference(user_config_params)
|
||||
_reject_user_config_secret_references(user_config_params)
|
||||
return True
|
||||
|
||||
|
||||
def _reject_user_config_secret_references(litellm_params: Mapping[str, object]) -> None:
|
||||
for params in (litellm_params, *_nested_configs(litellm_params), *_data_source_parameters(litellm_params)):
|
||||
_reject_secret_reference_in(params)
|
||||
|
||||
|
||||
def _data_source_parameters(litellm_params: Mapping[str, object]) -> tuple[Mapping[str, object], ...]:
|
||||
return tuple(
|
||||
parameters
|
||||
for source in _mappings_in(litellm_params.get("dataSources"))
|
||||
if (parameters := _coerce_metadata_to_dict(source.get("parameters"))) is not None
|
||||
)
|
||||
|
||||
|
||||
def _reject_request_credential_params(litellm_params: Mapping[str, object], *, manages_deployments: bool) -> None:
|
||||
if manages_deployments:
|
||||
reject_server_owned_wif_fields(
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def test_token_exchange_settings_in_request_body_are_rejected(field: str) -> Non
|
|||
match="server-owned workload identity federation or OAuth token exchange parameter",
|
||||
) as error:
|
||||
is_request_body_safe(
|
||||
request_body={"model": "microsoft_365_copilot/chat", field: "attacker-chosen"},
|
||||
request_body={"model": "microsoft_365_copilot/chat", field: "caller-chosen"},
|
||||
general_settings={"allow_client_side_credentials": True},
|
||||
llm_router=None,
|
||||
model="microsoft_365_copilot/chat",
|
||||
|
|
@ -151,6 +151,302 @@ def test_model_opt_in_cannot_allow_a_token_exchange_endpoint_in_a_request_body()
|
|||
)
|
||||
|
||||
|
||||
_AZURE_ENTRA_CREDENTIAL_PARAMS: Final = (
|
||||
"tenant_id",
|
||||
"client_id",
|
||||
"client_secret",
|
||||
"azure_username",
|
||||
"azure_password",
|
||||
"azure_scope",
|
||||
)
|
||||
|
||||
|
||||
def _azure_entra_router(configurable_clientside_auth_params: tuple[str, ...] = ()) -> Router:
|
||||
return Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "azure-group",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-4o",
|
||||
"api_base": "https://admin-endpoint.example.com",
|
||||
"tenant_id": "admin-tenant",
|
||||
"client_id": "admin-client",
|
||||
"client_secret": "admin-secret",
|
||||
"configurable_clientside_auth_params": list(configurable_clientside_auth_params),
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param", _AZURE_ENTRA_CREDENTIAL_PARAMS)
|
||||
@pytest.mark.parametrize("container", (None, "extra_body", "litellm_embedding_config"))
|
||||
def test_azure_entra_credential_params_in_request_body_are_rejected(param: str, container: str | None) -> None:
|
||||
override: Final = {param: "caller-chosen"}
|
||||
with pytest.raises(ValueError, match=f"{param} is not allowed in request body"):
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", **({container: override} if container else override)},
|
||||
general_settings={},
|
||||
llm_router=_azure_entra_router(),
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param", _AZURE_ENTRA_CREDENTIAL_PARAMS)
|
||||
@pytest.mark.parametrize("metadata_key", ("metadata", "litellm_metadata"))
|
||||
def test_azure_entra_credential_names_are_allowed_as_request_metadata(param: str, metadata_key: str) -> None:
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", metadata_key: {param: "caller-tag"}},
|
||||
general_settings={},
|
||||
llm_router=_azure_entra_router(),
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
def test_deployment_opt_in_allows_only_the_named_azure_entra_credential_param() -> None:
|
||||
router: Final = _azure_entra_router(configurable_clientside_auth_params=("client_id",))
|
||||
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", "client_id": "caller-client"},
|
||||
general_settings={},
|
||||
llm_router=router,
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
is True
|
||||
)
|
||||
with pytest.raises(ValueError, match="client_secret"):
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", "client_id": "caller-client", "client_secret": "caller-secret"},
|
||||
general_settings={},
|
||||
llm_router=router,
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param", (*_AZURE_ENTRA_CREDENTIAL_PARAMS, "api_key", "aws_access_key_id", "azure_ad_token"))
|
||||
@pytest.mark.parametrize("reference", ("os.environ/LITELLM_MASTER_KEY", "oidc/env/LITELLM_MASTER_KEY"))
|
||||
@pytest.mark.parametrize("container", (None, "extra_body"))
|
||||
def test_secret_reference_in_a_credential_param_is_rejected_despite_client_side_opt_in(
|
||||
param: str, reference: str, container: str | None
|
||||
) -> None:
|
||||
override: Final = {param: reference}
|
||||
with pytest.raises(ValueError, match=f"{param} holds an? (oidc/ )?secret reference"):
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", **({container: override} if container else override)},
|
||||
general_settings={"allow_client_side_credentials": True},
|
||||
llm_router=_azure_entra_router(),
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
|
||||
|
||||
def test_secret_reference_is_rejected_for_a_param_the_deployment_opted_in() -> None:
|
||||
with pytest.raises(ValueError, match="client_id holds a secret reference"):
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", "client_id": "os.environ/LITELLM_MASTER_KEY"},
|
||||
general_settings={},
|
||||
llm_router=_azure_entra_router(configurable_clientside_auth_params=("client_id",)),
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"body",
|
||||
(
|
||||
{"prompt": "os.environ/PATH is what in Python?"},
|
||||
{"input": "oidc/env/ tokens explained"},
|
||||
{"api_key": "sk-caller-own-key"},
|
||||
{"client_id": "an os.environ/ mention that is not a prefix"},
|
||||
),
|
||||
)
|
||||
def test_values_that_are_not_secret_references_in_credential_params_are_allowed(body: Mapping[str, str]) -> None:
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", **body},
|
||||
general_settings={"allow_client_side_credentials": True},
|
||||
llm_router=_azure_entra_router(),
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
def test_deployment_management_body_may_carry_a_secret_reference() -> None:
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"model_name": "azure-group", "api_key": "os.environ/AZURE_API_KEY"},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="",
|
||||
route="/model/new",
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field", ("client_id", "client_secret"))
|
||||
def test_mcp_oauth_token_form_may_carry_oauth_client_fields(field: str) -> None:
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"grant_type": "authorization_code", "code": "abc", field: "mcp-oauth-client"},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="",
|
||||
route="/v1/mcp/server/oauth/abc123/token",
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field", ("tenant_id", "azure_username", "azure_password", "azure_scope"))
|
||||
def test_mcp_server_routes_still_reject_non_oauth_azure_entra_params(field: str) -> None:
|
||||
with pytest.raises(ValueError, match=field):
|
||||
is_request_body_safe(
|
||||
request_body={"grant_type": "authorization_code", "code": "abc", field: "caller-chosen"},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="",
|
||||
route="/v1/mcp/server/oauth/abc123/token",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", ({"client_id": "gateway-1"}, {"client_id": "gateway-1", "client_secret": "gw-secret"}))
|
||||
def test_token_introspection_form_may_carry_oauth_client_fields(body: Mapping[str, str]) -> None:
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"token": "llm_session_abc", **body},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="",
|
||||
route="/introspect",
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", ({"tenant_id": "caller-tenant"}, {"client_id": "os.environ/LITELLM_MASTER_KEY"}))
|
||||
def test_token_introspection_still_rejects_other_entra_params_and_secret_references(body: Mapping[str, str]) -> None:
|
||||
with pytest.raises(ValueError, match=next(iter(body))):
|
||||
is_request_body_safe(
|
||||
request_body={"token": "llm_session_abc", **body},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="",
|
||||
route="/introspect",
|
||||
)
|
||||
|
||||
|
||||
_TRANSCRIPTION_SESSION_ROUTES: Final = (
|
||||
"/v1/realtime/transcription_sessions",
|
||||
"/realtime/transcription_sessions",
|
||||
"/openai/v1/realtime/transcription_sessions",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", _TRANSCRIPTION_SESSION_ROUTES)
|
||||
def test_transcription_session_body_may_carry_its_client_secret_config(route: str) -> None:
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={
|
||||
"model": "gpt-4o-transcribe",
|
||||
"client_secret": {"expires_at": {"anchor": "created_at", "seconds": 600}},
|
||||
},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="",
|
||||
route=route,
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", _TRANSCRIPTION_SESSION_ROUTES)
|
||||
@pytest.mark.parametrize("body", ({"tenant_id": "caller-tenant"}, {"client_secret": "os.environ/LITELLM_MASTER_KEY"}))
|
||||
def test_transcription_session_routes_still_reject_other_entra_params_and_secret_references(
|
||||
route: str, body: Mapping[str, str]
|
||||
) -> None:
|
||||
with pytest.raises(ValueError, match=next(iter(body))):
|
||||
is_request_body_safe(
|
||||
request_body={"model": "gpt-4o-transcribe", **body},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="",
|
||||
route=route,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param", _AZURE_ENTRA_CREDENTIAL_PARAMS)
|
||||
@pytest.mark.parametrize("metadata_key", ("metadata", "litellm_metadata"))
|
||||
@pytest.mark.parametrize("value", ("os.environ/HOME", "oidc/azure/team-a"))
|
||||
def test_metadata_tags_whose_value_looks_like_a_secret_reference_are_allowed(
|
||||
param: str, metadata_key: str, value: str
|
||||
) -> None:
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"model": "azure-group", metadata_key: {param: value}},
|
||||
general_settings={},
|
||||
llm_router=_azure_entra_router(),
|
||||
model="azure-group",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
_USER_CONFIG_SECRET_REFERENCE_PARAMS: Final = (
|
||||
{"model": "openai/gpt-4o", "api_base": "https://caller.example.com", "api_key": "os.environ/LITELLM_MASTER_KEY"},
|
||||
{"model": "openai/gpt-4o", "organization": "os.environ/LITELLM_MASTER_KEY"},
|
||||
{"model": "openai/gpt-4o", "extra_body": {"api_key": "oidc/env/LITELLM_MASTER_KEY"}},
|
||||
{"model": "azure/gpt-4o", "dataSources": [{"parameters": {"key": "os.environ/LITELLM_MASTER_KEY"}}]},
|
||||
)
|
||||
|
||||
|
||||
def _user_config(slot: str, litellm_params: Mapping[str, object]) -> Mapping[str, object]:
|
||||
if slot == "default_litellm_params":
|
||||
return {"default_litellm_params": dict(litellm_params)}
|
||||
return {"model_list": [{"model_name": "caller-model", "litellm_params": dict(litellm_params)}]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("litellm_params", _USER_CONFIG_SECRET_REFERENCE_PARAMS)
|
||||
@pytest.mark.parametrize("slot", ("model_list", "default_litellm_params"))
|
||||
def test_user_config_secret_references_are_rejected_despite_client_side_opt_in(
|
||||
litellm_params: Mapping[str, object], slot: str
|
||||
) -> None:
|
||||
with pytest.raises(ValueError, match="holds a secret reference"):
|
||||
is_request_body_safe(
|
||||
request_body={"model": "caller-model", "user_config": _user_config(slot, litellm_params)},
|
||||
general_settings={"allow_client_side_credentials": True},
|
||||
llm_router=None,
|
||||
model="caller-model",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
|
||||
|
||||
def test_user_config_with_literal_credentials_is_allowed_with_client_side_opt_in() -> None:
|
||||
litellm_params: Final = {"model": "openai/gpt-4o", "api_base": "https://caller.example.com", "api_key": "sk-own"}
|
||||
|
||||
assert (
|
||||
is_request_body_safe(
|
||||
request_body={"model": "caller-model", "user_config": _user_config("model_list", litellm_params)},
|
||||
general_settings={"allow_client_side_credentials": True},
|
||||
llm_router=None,
|
||||
model="caller-model",
|
||||
route="/v1/chat/completions",
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
sorted(ANTHROPIC_WIF_KWARGS_KEYS | OPENAI_WIF_KWARGS_KEYS | set(oauth_token_exchange_litellm_params)),
|
||||
|
|
@ -5821,7 +6117,7 @@ def test_per_user_credential_slots_in_request_body_are_rejected(field: str) -> N
|
|||
connection by proxy-injected secret_fields; a body-supplied value could only spoof either."""
|
||||
with pytest.raises(ValueError, match="Rejected Request") as error:
|
||||
is_request_body_safe(
|
||||
request_body={"model": "github_copilot/gpt-4o", field: "attacker-chosen"},
|
||||
request_body={"model": "github_copilot/gpt-4o", field: "caller-chosen"},
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="github_copilot/gpt-4o",
|
||||
|
|
|
|||
|
|
@ -147,8 +147,6 @@ REQUEST_BODY_PARAM_CLASSIFICATION: Final[Mapping[str, Classification]] = Mapping
|
|||
"aws_access_key_id": Unplanted(),
|
||||
"aws_secret_access_key": Unplanted(),
|
||||
"aws_session_token": Unplanted(),
|
||||
"azure_password": Unplanted(),
|
||||
"client_secret": Unplanted(),
|
||||
"s3_access_key_id": Unplanted(),
|
||||
"s3_secret_access_key": Unplanted(),
|
||||
"valkey_password": Unplanted(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue