mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-03 07:23:33 +00:00
fix(proxy): keep the request's redaction opt-in on proxy-only failure logs
This commit is contained in:
parent
e111c17c1e
commit
74b9bc5a40
3 changed files with 76 additions and 2 deletions
|
|
@ -740,7 +740,7 @@ def _key_or_team_allows_client_mock_response(
|
|||
)
|
||||
|
||||
|
||||
def _key_or_team_allows_client_message_redaction_opt_out(
|
||||
def key_or_team_allows_client_message_redaction_opt_out(
|
||||
user_api_key_dict: UserAPIKeyAuth,
|
||||
) -> bool:
|
||||
return _key_or_team_metadata_flag_is_true(
|
||||
|
|
@ -2612,7 +2612,7 @@ async def add_litellm_data_to_request(
|
|||
# These keys are injected by the proxy itself below — user-supplied values
|
||||
# must not be trusted.
|
||||
_allow_client_mock_response: Final = _key_or_team_allows_client_mock_response(user_api_key_dict)
|
||||
_allow_client_message_redaction_opt_out = _key_or_team_allows_client_message_redaction_opt_out(user_api_key_dict)
|
||||
_allow_client_message_redaction_opt_out = key_or_team_allows_client_message_redaction_opt_out(user_api_key_dict)
|
||||
for _internal_key in _UNTRUSTED_ROOT_CONTROL_FIELDS:
|
||||
if _allow_client_mock_response and _internal_key in _CLIENT_MOCK_CONTROL_FIELDS:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ from litellm.litellm_core_utils.duration_parser import get_budget_window_start
|
|||
from litellm.litellm_core_utils.env_utils import get_env_float, get_env_int
|
||||
from litellm.litellm_core_utils.litellm_logging import BilledRoundsDispatch, Logging, deployment_pricing_model_info
|
||||
from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_token
|
||||
from litellm.litellm_core_utils.redact_messages import should_redact_failed_request
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
|
||||
from litellm.litellm_core_utils.served_output_texts import (
|
||||
|
|
@ -253,6 +254,7 @@ from litellm.proxy.hooks.sensitive_data_routing import ( # noqa: F401, RUF100
|
|||
from litellm.proxy.litellm_pre_call_utils import (
|
||||
LiteLLMProxyRequestSetup,
|
||||
add_guardrails_from_auth_metadata,
|
||||
key_or_team_allows_client_message_redaction_opt_out,
|
||||
normalized_metadata_slot,
|
||||
)
|
||||
from litellm.proxy.management_helpers.key_settings_audit import with_settings_updated_at
|
||||
|
|
@ -3966,6 +3968,7 @@ class ProxyLogging:
|
|||
from litellm._uuid import uuid
|
||||
|
||||
request_data.setdefault("litellm_call_id", str(uuid.uuid4()))
|
||||
redact: Final = should_redact_failed_request(request_fields)
|
||||
|
||||
litellm_logging_obj, data = litellm.utils.function_setup(
|
||||
original_function=route or "IGNORE_THIS",
|
||||
|
|
@ -3974,6 +3977,13 @@ class ProxyLogging:
|
|||
**_proxy_only_error_log_fields( # pyright: ignore[reportArgumentType] # the allowlist never carries is_async_call
|
||||
request_fields, logged_metadata_keys
|
||||
),
|
||||
# The allowlist leaves out the request's redaction choice, so carry it over. As in the pre-call
|
||||
# checks, an opt-out only counts for a key or team allowed to send one
|
||||
**(
|
||||
{"turn_off_message_logging": redact}
|
||||
if redact or key_or_team_allows_client_message_redaction_opt_out(user_api_key_dict)
|
||||
else {}
|
||||
),
|
||||
)
|
||||
request_data["litellm_logging_obj"] = litellm_logging_obj # rebind-ok: lifted then popped by the caller
|
||||
request_data.update({REJECTED_BEFORE_REQUEST_SETUP_KEY: REJECTED_BEFORE_REQUEST_SETUP})
|
||||
|
|
|
|||
|
|
@ -202,6 +202,70 @@ async def test_proxy_only_error_log_keeps_tags_and_drops_caller_logging_fields(
|
|||
assert details["litellm_params"][metadata_field]["user_api_key_team_id"] == "team-a"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"opt_in",
|
||||
[
|
||||
{"turn_off_message_logging": True},
|
||||
{"metadata": {"turn_off_message_logging": True}},
|
||||
{"metadata": {"headers": {"x-litellm-enable-message-redaction": "true"}}},
|
||||
],
|
||||
ids=["body_flag", "metadata_flag", "enable_header"],
|
||||
)
|
||||
async def test_proxy_only_error_log_keeps_the_request_redaction_opt_in(opt_in: Mapping[str, object]) -> None:
|
||||
secret: Final = "my ssn is 123-45-6789"
|
||||
request_data: Final[dict[str, object]] = {
|
||||
"model": "gpt-4o",
|
||||
"messages": [{"role": "user", "content": secret}],
|
||||
**opt_in,
|
||||
}
|
||||
|
||||
await ProxyLogging(user_api_key_cache=DualCache())._handle_logging_proxy_only_error(
|
||||
request_data=request_data,
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="sk-rejected", team_id="team-a", request_route="/v1/rerank"),
|
||||
route="/v1/rerank",
|
||||
original_exception=HTTPException(status_code=400, detail=f"blocked: {secret}"),
|
||||
)
|
||||
|
||||
payload: Final = request_data["litellm_logging_obj"].model_call_details["standard_logging_object"]
|
||||
assert secret not in json.dumps(payload, default=str)
|
||||
assert "blocked: redacted-by-litellm" in payload["error_str"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("opt_out_allowed", [True, False], ids=["allowed", "not_allowed"])
|
||||
@pytest.mark.parametrize(
|
||||
"opt_out",
|
||||
[{"turn_off_message_logging": False}, {"metadata": {"headers": {"litellm-disable-message-redaction": "true"}}}],
|
||||
ids=["body_flag", "disable_header"],
|
||||
)
|
||||
async def test_proxy_only_error_log_keeps_a_redaction_opt_out_only_for_a_key_allowed_to_send_one(
|
||||
monkeypatch: pytest.MonkeyPatch, opt_out: Mapping[str, object], opt_out_allowed: bool
|
||||
) -> None:
|
||||
secret: Final = "my ssn is 123-45-6789"
|
||||
monkeypatch.setattr(litellm, "turn_off_message_logging", True)
|
||||
request_data: Final[dict[str, object]] = {
|
||||
"model": "gpt-4o",
|
||||
"messages": [{"role": "user", "content": secret}],
|
||||
**opt_out,
|
||||
}
|
||||
|
||||
await ProxyLogging(user_api_key_cache=DualCache())._handle_logging_proxy_only_error(
|
||||
request_data=request_data,
|
||||
user_api_key_dict=UserAPIKeyAuth(
|
||||
api_key="sk-rejected",
|
||||
team_id="team-a",
|
||||
request_route="/v1/rerank",
|
||||
metadata={"allow_client_message_redaction_opt_out": opt_out_allowed},
|
||||
),
|
||||
route="/v1/rerank",
|
||||
original_exception=HTTPException(status_code=400, detail=f"blocked: {secret}"),
|
||||
)
|
||||
|
||||
payload: Final = request_data["litellm_logging_obj"].model_call_details["standard_logging_object"]
|
||||
assert (f"blocked: {secret}" in payload["error_str"]) is opt_out_allowed
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("metadata_field", ["metadata", "litellm_metadata"])
|
||||
async def test_proxy_only_error_log_drops_malformed_metadata_tags(metadata_field: str) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue