Files
Gondulf/src/gondulf/middleware/security_headers.py
Phil Skentelbery d3c3e8dc6b feat(security): merge Phase 4b security hardening
Complete security hardening implementation including HTTPS enforcement,
security headers, rate limiting, and comprehensive security test suite.

Key features:
- HTTPS enforcement with HSTS support
- Security headers (CSP, X-Frame-Options, X-Content-Type-Options)
- Rate limiting for all critical endpoints
- Enhanced email template security
- 87% test coverage with security-specific tests

Architect approval: 9.5/10

Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 18:28:50 -07:00

76 lines
2.4 KiB
Python

"""Security headers middleware for Gondulf IndieAuth server."""
import logging
from typing import Callable
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
logger = logging.getLogger("gondulf.middleware.security_headers")
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""
Add security-related HTTP headers to all responses.
Headers protect against clickjacking, XSS, MIME sniffing, and other
client-side attacks. HSTS is only added in production mode (non-DEBUG).
References:
- OWASP Secure Headers Project
- Mozilla Web Security Guidelines
"""
def __init__(self, app, debug: bool = False):
"""
Initialize security headers middleware.
Args:
app: FastAPI application
debug: If True, skip HSTS header (development mode)
"""
super().__init__(app)
self.debug = debug
async def dispatch(self, request: Request, call_next: Callable) -> Response:
"""
Process request and add security headers to response.
Args:
request: Incoming HTTP request
call_next: Next middleware/handler in chain
Returns:
Response with security headers added
"""
# Process request
response = await call_next(request)
# Add security headers
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
# CSP: Allow self, inline styles (for templates), and HTTPS images (for h-app logos)
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' https:; "
"frame-ancestors 'none'"
)
# Permissions Policy: Disable unnecessary browser features
response.headers["Permissions-Policy"] = (
"geolocation=(), microphone=(), camera=()"
)
# HSTS: Only in production (not development)
if not self.debug:
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains"
)
logger.debug("Added HSTS header (production mode)")
return response