Add security headers middleware and documentation
- Add SecurityHeadersMiddleware to backend/app/main.py - Implements Content-Security-Policy: default-src 'self' - Implements X-Frame-Options: DENY (clickjacking protection) - Implements X-Content-Type-Options: nosniff (MIME-sniffing protection) - Implements X-XSS-Protection: 1; mode=block (browser XSS filters) - Add CSP meta tag to frontend/index.html for defense-in-depth - Create Docs/Security.md with comprehensive security headers documentation - Add test suite (backend/tests/test_security_headers_middleware.py) with 5 tests - Tests verify headers are present on success and error responses - Tests ensure all four security headers are correctly set - All existing tests continue to pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -649,6 +649,50 @@ _PREFIX_ALLOWED: frozenset[str] = frozenset(
|
||||
)
|
||||
|
||||
|
||||
# Security headers constants
|
||||
_CSP_POLICY: str = "default-src 'self'"
|
||||
_X_FRAME_OPTIONS: str = "DENY"
|
||||
_X_CONTENT_TYPE_OPTIONS: str = "nosniff"
|
||||
_X_XSS_PROTECTION: str = "1; mode=block"
|
||||
|
||||
|
||||
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
||||
"""Add security-related HTTP response headers to prevent common attacks.
|
||||
|
||||
This middleware adds the following headers to every HTTP response:
|
||||
- Content-Security-Policy: Prevents XSS by restricting resource origins
|
||||
- X-Frame-Options: Prevents clickjacking by controlling iframe embedding
|
||||
- X-Content-Type-Options: Prevents MIME-sniffing attacks
|
||||
- X-XSS-Protection: Enables browser XSS protection (legacy header)
|
||||
|
||||
These headers implement defense-in-depth against client-side attacks
|
||||
by relying on browser security policies rather than server-side logic alone.
|
||||
"""
|
||||
|
||||
async def dispatch(
|
||||
self,
|
||||
request: Request,
|
||||
call_next: Callable[[Request], Awaitable[StarletteResponse]],
|
||||
) -> StarletteResponse:
|
||||
"""Intercept responses to inject security headers.
|
||||
|
||||
Args:
|
||||
request: The incoming HTTP request.
|
||||
call_next: The next middleware / router handler.
|
||||
|
||||
Returns:
|
||||
The response from the next middleware / router with security headers added.
|
||||
"""
|
||||
response: StarletteResponse = await call_next(request)
|
||||
|
||||
response.headers["Content-Security-Policy"] = _CSP_POLICY
|
||||
response.headers["X-Frame-Options"] = _X_FRAME_OPTIONS
|
||||
response.headers["X-Content-Type-Options"] = _X_CONTENT_TYPE_OPTIONS
|
||||
response.headers["X-XSS-Protection"] = _X_XSS_PROTECTION
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class SetupRedirectMiddleware(BaseHTTPMiddleware):
|
||||
"""Redirect all API requests to ``/api/setup`` until setup is done.
|
||||
|
||||
@@ -783,12 +827,12 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
||||
|
||||
# --- Middleware ---
|
||||
# Note: middleware is applied in reverse order of registration.
|
||||
# The setup-redirect must run *after* CSRF, so it is added last.
|
||||
# CSRF middleware protects cookie-authenticated state-mutating requests.
|
||||
# RateLimitMiddleware checks per-IP request limits and must run early.
|
||||
# SecurityHeadersMiddleware must run early but after CORS/CSRF so headers
|
||||
# are added to all responses including error responses.
|
||||
# CorrelationIdMiddleware must run first (added last) so correlation ID
|
||||
# is available to all downstream handlers and loggers.
|
||||
app.add_middleware(CorrelationIdMiddleware)
|
||||
app.add_middleware(SecurityHeadersMiddleware)
|
||||
app.add_middleware(SetupRedirectMiddleware)
|
||||
app.add_middleware(CsrfMiddleware)
|
||||
app.add_middleware(
|
||||
|
||||
Reference in New Issue
Block a user