Skip to content

fix(adapters): block responses are text/plain - #144

Merged
rennf93 merged 1 commit into
rennf93:masterfrom
HardMax71:fix/block-response-content-type
Sep 25, 2026
Merged

rennf93 merged 1 commit into
rennf93:masterfrom
HardMax71:fix/block-response-content-type

Conversation

@HardMax71

@HardMax71 HardMax71 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Delivers issue: #143

Description

StarletteResponseFactory.create_response now builds the response with media_type="text/plain", so block and error responses carry Content-Type: text/plain; charset=utf-8 next to the X-Content-Type-Options: nosniff guard-core adds.

  • guard/adapters.py: the media type on create_response. Every message response goes through it: the checks' 403/400/429, the Redis-unavailable 503, and custom_error_responses messages.
  • tests/test_middleware/test_block_response_content_type.py: new tests, listed below.

Motivation and Context

Starlette only sets a Content-Type when it is given a media type, so these responses had none, while nosniff tells the client not to guess one. The Tornado adapter already sends text/plain; charset=utf-8 from the same factory method. A custom_response_modifier that sets its own Content-Type (for problem+json, say) still wins, since it runs after the factory. Details and a repro in #143.


Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation change
  • Performance improvement
  • Code cleanup or refactoring

How Has This Been Tested

  • REDIS_URL=redis://localhost:6391 REDIS_PREFIX=test:fastapi_guard: pytest tests/test_middleware/test_block_response_content_type.py: 3 passed (Python 3.10.20, guard-core 4.0.5, FastAPI 0.141.1, Starlette 1.7.0, a throwaway local Redis). A blacklisted IP's 403 and a custom_error_responses message are text/plain; charset=utf-8 with nosniff (both fail on master); a modifier that sets application/problem+json keeps it.
  • Full suite (without tests/live_smoke): 408 passed, 8 failed. The same 8 in test_security_middleware.py fail on master in this environment, as in fix(middleware): resolve the route Starlette redirects a trailing-slash request to #142.
  • ruff format --check, ruff check, mypy guard and on the new test, vulture, bandit, xenon, deptry: clean.
  • The repro in [BUG] Block responses have no Content-Type while X-Content-Type-Options: nosniff is set #143 against this branch: status 403 | content-type: text/plain; charset=utf-8 | body: Forbidden.

Checklist

  • My code follows the code style of this project (Mypy, Ruff)
  • I have added tests to cover my changes
  • All new and existing tests passed
  • My change requires a change to the documentation
  • I have updated the documentation accordingly
  • I have checked that my changes don't introduce any new warnings or errors
  • I have updated the version number if necessary
  • I have added any new dependencies to the appropriate requirements file

Summary by CodeRabbit

  • Bug Fixes
    • Blocked responses now use a plain-text content type and include nosniff protection.
    • Custom 403 messages are returned as plain text, while content types set by response modifiers are preserved.

StarletteResponseFactory.create_response built the response without a media type, so every block and error response (403, 400, 429, the Redis-unavailable 503, custom_error_responses messages) went out with no Content-Type while guard-core adds X-Content-Type-Options: nosniff. It now sets text/plain, as the Tornado adapter does; a custom_response_modifier that sets its own Content-Type still wins.

Closes rennf93#143
Copilot AI lite review requested due to automatic review settings September 25, 2026 17:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0f60b18b-4bba-459d-96d0-fde23d919159

📥 Commits

Reviewing files that changed from the base of the PR and between c3a46ed and 4059fbe.

📒 Files selected for processing (2)
  • guard/adapters.py
  • tests/test_middleware/test_block_response_content_type.py

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

Blocked responses now use a plain-text media type by default. Middleware tests check default and custom blocked responses, the nosniff header, and preservation of a response modifier’s content type.

Changes

Blocked response content type

Layer / File(s) Summary
Set and verify blocked response media type
guard/adapters.py, tests/test_middleware/test_block_response_content_type.py
The response factory sets the media type to text/plain. Middleware tests check blocked-response status and headers, including nosniff on the default response and application/problem+json from a custom response modifier.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Suggested reviewers: rennf93

Merge Risk: ⚪ Minimal · up to 4059f

Blocked responses now default to plain text while custom response types and ordinary endpoint responses remain unaffected. No actionable merge-specific risk is established.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4059f

Blocked responses now identify their body as plain text. The reviewed request path still blocks before reaching the application, and a custom response modifier can still choose another content type. No new security bypass was identified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The affected exposure is the representation header on responses produced by the Starlette guard path, including the tested blocked-IP response; the reviewed change does not expand access to the application handler.

Trust Boundaries and Controls

  • observed — The blocked-request test observes nosniff alongside the new plain-text content type, while a configured modifier retains authority to override Content-Type.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: block responses from adapters now use the text/plain media type.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rennf93 rennf93 self-assigned this Sep 25, 2026
@github-actions github-actions Bot added area: middleware Touches guard/middleware.py (ASGI middleware adapter) area: adapters Touches guard/adapters.py (request/response protocol implementations) tests Test suite changes labels Sep 25, 2026
@rennf93 rennf93 moved this to In Progress in Guard Core Ecosystem Sep 25, 2026

@rennf93 rennf93 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Verified: block responses now carry text/plain under the nosniff header, custom error messages keep the content type, and the custom_response_modifier still overrides it (all three covered by the new tests; full middleware suite passes locally). Note for follow-up: tornadoapi-guard, flaskapi-guard, and djangoapi-guard response factories have the same missing Content-Type, so the same fix is needed over there.

@rennf93
rennf93 merged commit 4beb62e into rennf93:master Sep 25, 2026
15 checks passed
rennf93 added a commit that referenced this pull request Sep 26, 2026
rennf93 added a commit that referenced this pull request Sep 26, 2026
release(8.0.2): community fixes (#142, #144), exempt_ips live smoke; guard-core 4.1.0 tracking
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: adapters Touches guard/adapters.py (request/response protocol implementations) area: middleware Touches guard/middleware.py (ASGI middleware adapter) tests Test suite changes

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

[BUG] Block responses have no Content-Type while X-Content-Type-Options: nosniff is set

3 participants