mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
fix(content_filter): return 400 instead of 403 on guardrail violation
A blocked request from the LiteLLM content-filter guardrail (blocked
words, category keywords, conditional matches, regex patterns,
competitor-intent refuse) currently raises HTTP 403, which clients
render as an auth error ("Please run /login"). The violation is the
caller's input, not an auth problem - return 400 (Bad Request) so
clients surface it as an invalid-request error, matching how other
guardrails (aim, aporia_ai, azure prompt_shield, azure text_moderation,
bedrock, block_code_execution, crowdstrike_aidr, custom_code) already
behave.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a72414a061
commit
c945e0e2c6
4 changed files with 30 additions and 30 deletions
|
|
@ -1202,7 +1202,7 @@ class ContentFilterGuardrail(CustomGuardrail):
|
|||
)
|
||||
verbose_proxy_logger.warning(error_msg)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": error_msg,
|
||||
"category": category_name,
|
||||
|
|
@ -1242,7 +1242,7 @@ class ContentFilterGuardrail(CustomGuardrail):
|
|||
)
|
||||
verbose_proxy_logger.warning(error_msg)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": error_msg,
|
||||
"category": category_name,
|
||||
|
|
@ -1285,7 +1285,7 @@ class ContentFilterGuardrail(CustomGuardrail):
|
|||
error_msg = f"Content blocked: {pattern_name} pattern detected"
|
||||
verbose_proxy_logger.warning(error_msg)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
status_code=400,
|
||||
detail={"error": error_msg, "pattern": pattern_name},
|
||||
)
|
||||
elif action == ContentFilterAction.MASK:
|
||||
|
|
@ -1325,7 +1325,7 @@ class ContentFilterGuardrail(CustomGuardrail):
|
|||
error_msg += f" ({description})"
|
||||
verbose_proxy_logger.warning(error_msg)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": error_msg,
|
||||
"keyword": keyword,
|
||||
|
|
@ -1677,7 +1677,7 @@ class ContentFilterGuardrail(CustomGuardrail):
|
|||
"ContentFilterGuardrail: competitor intent refuse - %s", intent_val
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": msg,
|
||||
"intent": intent_val,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def _run(checker, text: str) -> dict:
|
|||
checker.check(text)
|
||||
return {"decision": "ALLOW", "score": 0.0, "matched_topic": None}
|
||||
except HTTPException as e:
|
||||
if e.status_code == 403:
|
||||
if e.status_code == 400:
|
||||
detail: Dict[str, Any] = e.detail if isinstance(e.detail, dict) else {}
|
||||
return {
|
||||
"decision": "BLOCK",
|
||||
|
|
@ -542,7 +542,7 @@ class _LlmJudgeChecker:
|
|||
|
||||
if "BLOCK" in decision:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": "Content blocked by LLM judge",
|
||||
"topic": "financial_advice",
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ class TestContentFilterWithCompetitorIntent:
|
|||
await guardrail.apply_guardrail(
|
||||
inputs, request_data={}, input_type="request"
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
|
||||
# Exact config from litellm/proxy/_new_secret_config.yaml (lines 27-53).
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "us_ssn" in str(exc_info.value.detail)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -563,7 +563,7 @@ class TestContentFilterGuardrail:
|
|||
):
|
||||
pass
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "us_ssn" in str(exc_info.value.detail)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1010,7 +1010,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "danger_word" in str(exc_info.value.detail)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1298,7 +1298,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
detail = exc_info.value.detail
|
||||
if isinstance(detail, dict):
|
||||
assert detail.get("category") == "harm_toxic_abuse"
|
||||
|
|
@ -1327,7 +1327,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
detail = exc_info.value.detail
|
||||
if isinstance(detail, dict):
|
||||
assert detail.get("category") == "harm_toxic_abuse"
|
||||
|
|
@ -1375,7 +1375,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403, f"Failed to block: '{test_input}'"
|
||||
assert exc_info.value.status_code == 400, f"Failed to block: '{test_input}'"
|
||||
detail = exc_info.value.detail
|
||||
if isinstance(detail, dict):
|
||||
assert detail.get("category") == "harm_toxic_abuse"
|
||||
|
|
@ -1443,7 +1443,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "te*st" in str(exc_info.value.detail)
|
||||
|
||||
def test_check_category_keywords_asterisk_pattern_matching(self):
|
||||
|
|
@ -1510,7 +1510,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403, f"Failed to block: '{test_input}'"
|
||||
assert exc_info.value.status_code == 400, f"Failed to block: '{test_input}'"
|
||||
detail = exc_info.value.detail
|
||||
if isinstance(detail, dict):
|
||||
assert detail.get("category") == "harm_toxic_abuse"
|
||||
|
|
@ -1560,7 +1560,7 @@ class TestContentFilterGuardrail:
|
|||
input_type="request",
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 403, f"Failed to block: '{test_input}'"
|
||||
assert exc_info.value.status_code == 400, f"Failed to block: '{test_input}'"
|
||||
detail = exc_info.value.detail
|
||||
if isinstance(detail, dict):
|
||||
assert detail.get("category") == "harm_toxic_abuse"
|
||||
|
|
@ -1646,7 +1646,7 @@ class TestContentFilterGuardrail:
|
|||
)
|
||||
|
||||
assert (
|
||||
exc_info.value.status_code == 403
|
||||
exc_info.value.status_code == 400
|
||||
), f"Failed to block Spanish: '{test_input}'"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1683,7 +1683,7 @@ class TestContentFilterGuardrail:
|
|||
)
|
||||
|
||||
assert (
|
||||
exc_info.value.status_code == 403
|
||||
exc_info.value.status_code == 400
|
||||
), f"Failed to block French: '{test_input}'"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1720,7 +1720,7 @@ class TestContentFilterGuardrail:
|
|||
)
|
||||
|
||||
assert (
|
||||
exc_info.value.status_code == 403
|
||||
exc_info.value.status_code == 400
|
||||
), f"Failed to block German: '{test_input}'"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1766,7 +1766,7 @@ class TestContentFilterGuardrail:
|
|||
)
|
||||
|
||||
assert (
|
||||
exc_info.value.status_code == 403
|
||||
exc_info.value.status_code == 400
|
||||
), f"Failed to block Australian: '{test_input}'"
|
||||
|
||||
async def test_html_tags_in_messages_not_blocked(self):
|
||||
|
|
@ -1942,7 +1942,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "harmful_child_safety" in str(exc_info.value.detail)
|
||||
|
||||
# Test case 2: Should BLOCK - identifier + block word combination
|
||||
|
|
@ -1956,7 +1956,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
# Test case 3: Should BLOCK - explicit content + minors
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
|
|
@ -1967,7 +1967,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
# Test case 4: Should NOT block - identifier word alone (no block word)
|
||||
result = await guardrail.apply_guardrail(
|
||||
|
|
@ -2009,7 +2009,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_conditional_category_sentence_boundaries(self):
|
||||
|
|
@ -2093,7 +2093,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "bias_racial" in str(exc_info.value.detail)
|
||||
|
||||
# Test case 2: Should BLOCK - identifier + dehumanizing language
|
||||
|
|
@ -2107,7 +2107,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
# Test case 3: Should BLOCK - supremacist content
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
|
|
@ -2120,7 +2120,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
# Test case 4: Should BLOCK - elimination rhetoric
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
|
|
@ -2133,7 +2133,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
# Test case 5: Should NOT block - identifier word alone (no block word)
|
||||
result = await guardrail.apply_guardrail(
|
||||
|
|
@ -2171,7 +2171,7 @@ class TestContentFilterGuardrail:
|
|||
request_data={},
|
||||
input_type="request",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
# Test case 9: Should NOT block - block word alone (no identifier)
|
||||
result = await guardrail.apply_guardrail(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue