"""Security headers middleware for FastAPI. Adds standard security headers to every HTTP response to mitigate common web vulnerabilities (clickjacking, XSS, MIME sniffing, etc.). """ from typing import Callable from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware from config import settings # Content Security Policy - allows CDN resources used by the client # Note: 'unsafe-eval' required for Plotly.js runtime evaluation in SPC charts CSP = ( "default-src 'self'; " "script-src 'self' 'unsafe-inline' 'unsafe-eval' " "https://cdn.tailwindcss.com https://cdn.jsdelivr.net https://cdn.plot.ly; " "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " "font-src 'self' https://fonts.gstatic.com; " "img-src 'self' data: blob:; " "connect-src 'self'" ) class SecurityHeadersMiddleware(BaseHTTPMiddleware): """Middleware that injects security headers into every response.""" async def dispatch(self, request: Request, call_next: Callable) -> Response: response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "DENY" response.headers["X-XSS-Protection"] = "1; mode=block" response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" response.headers["Content-Security-Policy"] = CSP # Add HSTS header only when running with HTTPS (SSL configured) if settings.ssl_certfile and settings.ssl_keyfile: response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" return response