chore(proxy): cover extra_body + azure_ad_token in banned-params check
``extra_body`` is the OpenAI-SDK passthrough container. Provider modules read provider-auth fields out of it directly (Azure's ``extra_body.azure_ad_token``, Bedrock's ``extra_body.aws_web_identity_token``, etc.) without re-validating, so the boundary check has to walk it the same way it walks ``litellm_embedding_config``. Adding it to ``_NESTED_CONFIG_KEYS`` extends single-level banned-key descent into the container — top-level admin opt-ins (``allow_client_side_credentials`` / ``configurable_clientside_auth_params``) still apply. ``azure_ad_token`` was not in ``_BANNED_REQUEST_BODY_PARAMS`` despite being the bearer-token field the Azure transformer resolves through ``get_secret`` (same shape as ``aws_web_identity_token`` on the Bedrock STS path). Added so it can't be supplied per-request without an admin opt-in.
This commit is contained in:
parent
1294165768
commit
2519ac161e
@ -169,9 +169,13 @@ def _allow_model_level_clientside_configurable_parameters(
|
||||
|
||||
# Config dicts whose entries are spread as ``**dict`` into outbound LLM
|
||||
# API calls. ``litellm_embedding_config`` is consumed by the Milvus
|
||||
# vector store transformer; future nested-config keys with the same
|
||||
# threat shape should be added here.
|
||||
_NESTED_CONFIG_KEYS: Tuple[str, ...] = ("litellm_embedding_config",)
|
||||
# vector store transformer. ``extra_body`` is the OpenAI-SDK passthrough
|
||||
# container: provider modules pull provider-auth fields out of it
|
||||
# (e.g. Azure's ``extra_body.azure_ad_token``, Bedrock's
|
||||
# ``extra_body.aws_web_identity_token``) without re-validating, so the
|
||||
# banned-key check has to descend into it the same way it descends into
|
||||
# ``litellm_embedding_config``.
|
||||
_NESTED_CONFIG_KEYS: Tuple[str, ...] = ("litellm_embedding_config", "extra_body")
|
||||
|
||||
# Metadata containers that carry per-request configuration consumed by the
|
||||
# observability callbacks. The same banned-param list applies — a value
|
||||
@ -246,6 +250,13 @@ _BANNED_REQUEST_BODY_PARAMS: Tuple[str, ...] = (
|
||||
"aws_web_identity_token",
|
||||
"aws_role_name",
|
||||
"vertex_credentials",
|
||||
# Azure managed-identity / federated-auth token. The Azure provider
|
||||
# transformer reads ``azure_ad_token`` (top-level or via
|
||||
# ``extra_body``) and resolves it through ``get_secret`` before
|
||||
# passing it as the bearer token to the Azure endpoint, so a
|
||||
# caller-supplied value is the same exfil shape as
|
||||
# ``aws_web_identity_token`` on the Bedrock path.
|
||||
"azure_ad_token",
|
||||
# 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
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
"""
|
||||
``extra_body`` is the OpenAI-SDK passthrough container — provider modules
|
||||
pull provider-auth fields out of it without re-validating. Without
|
||||
descending into it, the banned-param boundary check is bypassed by
|
||||
nesting the same fields under ``extra_body``.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../.."))
|
||||
)
|
||||
|
||||
from litellm.proxy.auth.auth_utils import ( # noqa: E402
|
||||
_BANNED_REQUEST_BODY_PARAMS,
|
||||
is_request_body_safe,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"banned_param",
|
||||
[
|
||||
"aws_web_identity_token",
|
||||
"aws_sts_endpoint",
|
||||
"aws_role_name",
|
||||
"api_base",
|
||||
"base_url",
|
||||
"vertex_credentials",
|
||||
"azure_ad_token",
|
||||
],
|
||||
)
|
||||
def test_banned_param_under_extra_body_is_rejected(banned_param):
|
||||
body = {
|
||||
"model": "bedrock/anthropic.claude-v2",
|
||||
"messages": [{"role": "user", "content": "x"}],
|
||||
"extra_body": {banned_param: "anything-attacker-chose"},
|
||||
}
|
||||
with pytest.raises(ValueError, match="not allowed in request body"):
|
||||
is_request_body_safe(
|
||||
request_body=body,
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="bedrock/anthropic.claude-v2",
|
||||
)
|
||||
|
||||
|
||||
def test_azure_ad_token_is_in_banned_list():
|
||||
assert "azure_ad_token" in _BANNED_REQUEST_BODY_PARAMS
|
||||
|
||||
|
||||
def test_extra_body_with_safe_fields_is_allowed():
|
||||
body = {
|
||||
"model": "openai/gpt-4",
|
||||
"messages": [{"role": "user", "content": "x"}],
|
||||
"extra_body": {"reasoning_effort": "low", "seed": 42},
|
||||
}
|
||||
assert is_request_body_safe(
|
||||
request_body=body,
|
||||
general_settings={},
|
||||
llm_router=None,
|
||||
model="openai/gpt-4",
|
||||
)
|
||||
|
||||
|
||||
def test_admin_opt_in_still_permits_extra_body_credentials():
|
||||
# ``general_settings.allow_client_side_credentials`` is the documented
|
||||
# admin escape for clientside-credential passthrough. Walking
|
||||
# ``extra_body`` for banned params must not break the escape.
|
||||
body = {
|
||||
"model": "openai/gpt-4",
|
||||
"messages": [{"role": "user", "content": "x"}],
|
||||
"extra_body": {"api_base": "https://my-private-openai.internal"},
|
||||
}
|
||||
assert is_request_body_safe(
|
||||
request_body=body,
|
||||
general_settings={"allow_client_side_credentials": True},
|
||||
llm_router=None,
|
||||
model="openai/gpt-4",
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user