Skip to content

fix(guardrails): return HTTP 400 for litellm content filter blocks - #28418

Merged
mateo-berri merged 5 commits into
litellm_internal_stagingfrom
litellm_change_litellm_content_filter_block_response_to_400
May 30, 2026
Merged

fix(guardrails): return HTTP 400 for litellm content filter blocks#28418
mateo-berri merged 5 commits into
litellm_internal_stagingfrom
litellm_change_litellm_content_filter_block_response_to_400

Conversation

@shivamrawat1

@shivamrawat1 shivamrawat1 commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Resolves LIT-3253
Description
This PR updates guardrail block handling so policy-driven blocks use HTTP 400 consistently, while preserving the existing passthrough contract for standalone custom code guardrails.

Cause
Some guardrail block paths returned inconsistent client-facing status codes. Content filter blocks were using 403, while policy-builder block behavior should represent a bad/blocked request as 400. Custom code guardrails also needed to continue supporting their standalone passthrough behavior while still being treated as a block when used in policy pipelines.

Fix
Changed content filter block responses from 403 to 400.
Preserved standalone custom code guardrail pre-call block(...) behavior via passthrough synthetic response.
Kept post-call custom code guardrail blocks as HTTP 400.
Ensured passthrough-style guardrail interventions can be treated as pipeline failures, so policy builder on_fail: block returns a true block response.
Added tests for custom code passthrough behavior and policy pipeline block handling.
Tests
uv run pytest tests/test_litellm/proxy/policy_engine/test_pipeline_executor.py tests/test_litellm/proxy/guardrails/test_custom_code_security.py -v
Before:
Api trigger for block was raising api failure.
Content-filter guardrails
Screenshot 2026-05-30 at 12 47 45 PM
Custom guardrails:
Screenshot 2026-05-30 at 1 13 01 PM

After:
Content-filter guardrails
Api trigger for block raises block as expected.
Screenshot 2026-05-30 at 12 57 38 PM
Custom guardrails:
Screenshot 2026-05-30 at 1 02 07 PM

Align litellm_content_filter hard rejects with the standard guardrail block status code so clients receive 400 instead of 403.

Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps

greptile-apps Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR standardises guardrail block status codes to HTTP 400 and fixes custom-code guardrail pre-call passthrough semantics. The 403 → 400 change is an intentional, acknowledged breaking change that will be captured in release notes.

  • content_filter.py: Replaces five HTTPException(status_code=403) raises with 400 across all content-filter block paths, making them consistent with other guardrails and with the _is_guardrail_intervention check which already recognised only 400 as an intentional block — meaning 403 blocks were previously logged as guardrail_failed_to_respond and were invisible to the policy pipeline.
  • custom_code_guardrail.py: Adds except ModifyResponseException: raise so that pre-call block() calls no longer get swallowed by the generic except Exception handler and re-raised as a 500 execution error.
  • All tests are updated to assert 400 instead of 403; new tests cover standalone passthrough behavior, post-call HTTP 400, and pipeline block integration for both PassthroughBlockGuardrail and CustomCodeGuardrail.

Confidence Score: 5/5

Safe to merge. The 403 → 400 change is intentional and acknowledged; the custom-code guardrail passthrough fix is minimal and well-tested.

The status-code change is a deliberate, documented breaking change with comprehensive test coverage across all affected paths. The custom-code guardrail fix correctly prevents pre-call passthrough exceptions from being swallowed as 500 errors, and the new pipeline tests validate end-to-end block behavior. No logic errors, no unhandled edge cases.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py Five HTTPException status codes changed from 403 to 400 across all block paths; intentional breaking change, consistent with other guardrails and the existing _is_guardrail_intervention check.
litellm/proxy/guardrails/guardrail_hooks/custom_code/custom_code_guardrail.py Adds except ModifyResponseException: raise to prevent pre-call passthrough blocks from being wrapped as 500 execution errors; the fix is minimal and correct.
tests/test_litellm/proxy/guardrails/test_custom_code_security.py Two new tests covering pre-call passthrough (ModifyResponseException) and post-call HTTP 400 block for custom code guardrails; tests correctly differentiate input_type="request" vs "response".
tests/test_litellm/proxy/policy_engine/test_pipeline_executor.py Two new pipeline integration tests added: one for a generic PassthroughBlockGuardrail and one for CustomCodeGuardrail; both assert terminal_action=="block" and outcome=="fail" for on_fail: block steps.
tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py Bulk test update: 15 status_code assertions changed from 403 to 400, matching the production change; no test logic weakened.
litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/test_eval.py Benchmark harness updated to check 400 instead of 403; also updates the LLM-judge stub to raise 400; consistent with the production change.
tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_competitor_intent.py Single assertion updated from 403 to 400; no logic change.

Reviews (2): Last reviewed commit: "fix(guardrails): preserve custom code pa..." | Re-trigger Greptile

@codecov

codecov Bot commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@shivamrawat1

Copy link
Copy Markdown
Collaborator Author

@greptile "Breaking change: 403 → 400 without a migration path" this is expected. We will mention the breaking changes in our release notes. Please update score based on this.

@greptile-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Acknowledged — if the breaking change is documented in the release notes, that concern is resolved. The status code change is well-motivated (aligning with how LiteLLM's guardrail telemetry distinguishes guardrail_intervened vs guardrail_failed_to_respond), and the fix is consistent across all block paths.

Updated score: 4/5 — the change is correct and purposeful. The remaining point is for the lack of a feature flag option (per the backwards-compatibility rule), but given this is an intentional, documented breaking change rather than an accidental regression, that's a reasonable trade-off.

Tip: You can give me more context with .greptile/rules.md so I get this right next time.

shivamrawat1 and others added 4 commits May 23, 2026 15:11
Pre-call custom code guardrail blocks now raise HTTPException(400) instead of using the passthrough ModifyResponseException path that returned a synthetic 200 response.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep standalone custom code guardrail blocks on the passthrough contract while covering policy pipeline block handling for passthrough-style guardrail interventions.

Co-authored-by: Cursor <cursoragent@cursor.com>
@shivamrawat1

Copy link
Copy Markdown
Collaborator Author

@greptile review again holistically. note that "Breaking change: 403 → 400 without a migration path" this is expected. We will mention the breaking changes in our release notes. Please update score based on this.

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; thanks!

@mateo-berri
mateo-berri merged commit 4c3efe9 into litellm_internal_staging May 30, 2026
111 of 118 checks passed
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…erriAI#28418)

* fix(guardrails): return HTTP 400 for litellm content filter blocks

Align litellm_content_filter hard rejects with the standard guardrail block status code so clients receive 400 instead of 403.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(guardrails): return HTTP 400 for custom code guardrail blocks

Pre-call custom code guardrail blocks now raise HTTPException(400) instead of using the passthrough ModifyResponseException path that returned a synthetic 200 response.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(guardrails): preserve custom code passthrough blocks

Keep standalone custom code guardrail blocks on the passthrough contract while covering policy pipeline block handling for passthrough-style guardrail interventions.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants