fix(xecguard): sanitize scan result before recording it for logging (#32935)

This commit is contained in:
yucheng-berri 2026-07-11 19:46:02 -07:00 committed by GitHub
parent f717e3b2f0
commit f61fd2fb6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 1 deletions

View file

@ -44,6 +44,8 @@ from litellm.integrations.custom_guardrail import (
CustomGuardrail,
log_guardrail_information,
)
from litellm.litellm_core_utils.core_helpers import redact_nested_match_and_regex_keys
from litellm.litellm_core_utils.sensitive_data_masker import mask_credentials_in_payload
from litellm.llms.custom_httpx.http_handler import (
get_async_httpx_client,
httpxSpecialProvider,
@ -64,6 +66,13 @@ if TYPE_CHECKING:
)
def _sanitize_scan_result_for_logging(scan_result: dict) -> dict:
without_secrets = {key: value for key, value in scan_result.items() if key != "secret_fields"}
redacted = redact_nested_match_and_regex_keys(without_secrets)
masked = mask_credentials_in_payload(redacted if isinstance(redacted, dict) else without_secrets)
return masked if isinstance(masked, dict) else without_secrets
_DEFAULT_API_BASE = "https://api-xecguard.cycraft.ai"
_SCAN_ENDPOINT = "/xecguard/v1/scan"
_GROUNDING_ENDPOINT = "/xecguard/v1/grounding"
@ -253,7 +262,7 @@ class XecGuardGuardrail(CustomGuardrail):
slg = StandardLoggingGuardrailInformation(
guardrail_name=self.guardrail_name or "xecguard",
guardrail_mode=GuardrailEventHooks.logging_only,
guardrail_response=scan_result,
guardrail_response=_sanitize_scan_result_for_logging(scan_result),
guardrail_status=guardrail_status,
start_time=start_time.timestamp(),
end_time=end_time.timestamp(),

View file

@ -1675,6 +1675,33 @@ class TestXecGuardLoggingHook:
assert info_list[1]["guardrail_name"] == "test-xecguard"
assert info_list[1]["guardrail_response"]["trace_id"] == "lg-4"
@pytest.mark.asyncio
async def test_async_logging_hook_sanitizes_scan_result(
self, xecguard_guardrail, mock_request_data
):
resp = _make_response(
{
"decision": "SAFE",
"trace_id": "lg-5",
"secret_fields": {"authorization": "Bearer xgs_raw"},
"detections": [{"match": "raw matched span", "policy": "pii"}],
"api_key": "xgs_super_secret_value",
}
)
with patch.object(xecguard_guardrail.async_handler, "post", return_value=resp):
kwargs = {**mock_request_data, "standard_logging_object": {}}
await xecguard_guardrail.async_logging_hook(
kwargs=kwargs,
result=_build_model_response("some answer"),
call_type="acompletion",
)
info = kwargs["standard_logging_object"]["guardrail_information"][0]
guardrail_response = info["guardrail_response"]
assert "secret_fields" not in guardrail_response
assert guardrail_response["detections"][0]["match"] == "[REDACTED]"
assert guardrail_response["api_key"] != "xgs_super_secret_value"
assert guardrail_response["trace_id"] == "lg-5"
@pytest.mark.asyncio
async def test_async_logging_hook_without_response_records_info(
self, xecguard_guardrail, mock_request_data