"""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