mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(presidio): propagate masked messages into standard_logging_object in logging_only mode
async_logging_hook masked only kwargs[messages], but the standard_logging_object is built earlier in the pipeline and holds a separate copy that downstream logging callbacks read from, so logging_only mode still leaked unmasked PII to observability platforms. Mirror the masked messages into standard_logging_object[messages] too. Fixes #35951 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
24dbd2b2db
commit
fb34efc487
2 changed files with 53 additions and 0 deletions
|
|
@ -21,6 +21,7 @@ import aiohttp
|
|||
import litellm
|
||||
from litellm import get_secret
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.litellm_core_utils.logging_utils import truncate_base64_in_messages
|
||||
from litellm.types.utils import GenericGuardrailAPIInputs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -829,6 +830,10 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
|
|||
verbose_proxy_logger.debug("Presidio PII Masking: Redacted pii message: %s", messages)
|
||||
kwargs["messages"] = messages
|
||||
|
||||
standard_logging_object: Final = kwargs.get("standard_logging_object")
|
||||
if isinstance(standard_logging_object, dict) and standard_logging_object.get("messages") is not None:
|
||||
standard_logging_object["messages"] = truncate_base64_in_messages(messages)
|
||||
|
||||
return kwargs, result
|
||||
|
||||
async def async_post_call_success_hook( # type: ignore
|
||||
|
|
|
|||
|
|
@ -584,6 +584,54 @@ async def test_logging_hook_multiple_content_items(presidio_guardrail):
|
|||
print("✓ Logging hook multiple content items test passed")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logging_hook_propagates_masked_messages_into_standard_logging_object(
|
||||
presidio_guardrail,
|
||||
):
|
||||
"""
|
||||
The standard_logging_object is built before async_logging_hook runs and holds
|
||||
a separate copy of the messages. Downstream logging callbacks (Langfuse, Datadog,
|
||||
etc.) read from standard_logging_object["messages"], so the hook must propagate the
|
||||
masked messages there too. Regression test for issue #35951.
|
||||
"""
|
||||
test_kwargs = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "My credit card is 4111-1111-1111-1111 and email test@example.com",
|
||||
}
|
||||
],
|
||||
"model": "gpt-4",
|
||||
"standard_logging_object": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "My credit card is 4111-1111-1111-1111 and email test@example.com",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
async def mock_check_pii(text, output_parse_pii, presidio_config, request_data):
|
||||
return text.replace("4111-1111-1111-1111", "[CREDIT_CARD]").replace(
|
||||
"test@example.com", "[EMAIL]"
|
||||
)
|
||||
|
||||
presidio_guardrail.check_pii = mock_check_pii
|
||||
|
||||
result_kwargs, _ = await presidio_guardrail.async_logging_hook(
|
||||
kwargs=test_kwargs,
|
||||
result={"choices": [{"message": {"content": "Response"}}]},
|
||||
call_type="completion",
|
||||
)
|
||||
|
||||
slo_content = result_kwargs["standard_logging_object"]["messages"][0]["content"]
|
||||
assert "[CREDIT_CARD]" in slo_content
|
||||
assert "[EMAIL]" in slo_content
|
||||
assert "4111-1111-1111-1111" not in slo_content
|
||||
assert "test@example.com" not in slo_content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logging_only_does_not_mask_pre_call_request(
|
||||
mock_user_api_key, mock_cache
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue