From cd6d38ea85b125da317e36e92f3ce8bba3f91199 Mon Sep 17 00:00:00 2001 From: aniket-kardile Date: Tue, 1 Sep 2026 13:03:50 +0530 Subject: [PATCH] fix(guardrails): mark Singulr block verdicts as blocked_content should_block=True raises GuardrailRaisedException without blocked_content=True, so the batch upload path treats a real block verdict as a technical failure and aborts the whole file instead of dropping only the blocked record. --- .../proxy/guardrails/guardrail_hooks/singulr/singulr.py | 4 ++++ .../proxy/guardrails/guardrail_hooks/test_singulr.py | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py b/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py index b733e9c1255..3255eace0bd 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py +++ b/litellm/proxy/guardrails/guardrail_hooks/singulr/singulr.py @@ -268,6 +268,7 @@ class SingulrGuardrail(CustomGuardrail): guardrail_name=self.guardrail_name, status_code=400, message=f"Blocked by Singulr, Blocking due to {guardrail_resp.blocking_due_to or 'unknown'}", + blocked_content=True, ) return inputs @@ -292,6 +293,7 @@ class SingulrGuardrail(CustomGuardrail): guardrail_name=self.guardrail_name, status_code=400, message=f"Blocked by Singulr, Blocking due to {guardrail_resp.blocking_due_to or 'unknown'}", + blocked_content=True, ) async def _apply_guardrail_on_mcp_response( @@ -319,6 +321,7 @@ class SingulrGuardrail(CustomGuardrail): guardrail_name=self.guardrail_name, status_code=400, message=f"Blocked by Singulr, Blocking due to {guardrail_resp.blocking_due_to or 'unknown'}", + blocked_content=True, ) return inputs @@ -379,6 +382,7 @@ class SingulrGuardrail(CustomGuardrail): guardrail_name=self.guardrail_name, status_code=400, message=f"Blocked by Singulr, Blocking due to {guardrail_resp.blocking_due_to or 'unknown'}", + blocked_content=True, ) return inputs diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py index 457b7c5c7f4..12e4469d158 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_singulr.py @@ -545,6 +545,7 @@ class TestSingulrBlockAction: input_type="request", ) assert "PII Information detected" in str(exc_info.value) + assert exc_info.value.blocked_content is True @pytest.mark.asyncio async def test_should_block_true_raises_on_response(self, singulr_guardrail): @@ -562,6 +563,7 @@ class TestSingulrBlockAction: input_type="response", ) assert "Toxic content detected" in str(exc_info.value) + assert exc_info.value.blocked_content is True @pytest.mark.asyncio async def test_block_without_reason_uses_unknown_placeholder(self, singulr_guardrail): @@ -607,12 +609,13 @@ class TestSingulrMcpRequest: resp = _make_response({"should_block": True, "blocking_due_to": "Disallowed tool"}) request_data = {"mcp_tool_name": "delete_file", "mcp_arguments": {"path": "/etc/passwd"}} with patch.object(singulr_guardrail.async_handler, "post", return_value=resp): - with pytest.raises(GuardrailRaisedException, match="Disallowed tool"): + with pytest.raises(GuardrailRaisedException, match="Disallowed tool") as exc_info: await singulr_guardrail.apply_guardrail( inputs={"texts": []}, request_data=request_data, input_type="request", ) + assert exc_info.value.blocked_content is True class TestSingulrMcpResponse: @@ -653,12 +656,13 @@ class TestSingulrMcpResponse: resp = _make_response({"should_block": True, "blocking_due_to": "Sensitive tool output"}) request_data = {"call_type": "call_mcp_tool", "mcp_tool_name": "search_docs"} with patch.object(singulr_guardrail.async_handler, "post", return_value=resp): - with pytest.raises(GuardrailRaisedException, match="Sensitive tool output"): + with pytest.raises(GuardrailRaisedException, match="Sensitive tool output") as exc_info: await singulr_guardrail.apply_guardrail( inputs={"texts": ["leaked secret"]}, request_data=request_data, input_type="response", ) + assert exc_info.value.blocked_content is True @pytest.mark.asyncio async def test_mcp_response_resolves_metadata_from_nested_litellm_params(self, singulr_guardrail):