"""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 src.backend.config import settings # Content Security Policy - same-origin only: every third-party library now ships with # the app (see frontend static/vendor/), so no CDN host needs allowing. The install runs # on an isolated shop-floor network where an outbound fetch would simply fail. # Note: 'unsafe-eval' required for Plotly.js runtime evaluation in SPC charts CSP = ( "default-src 'self'; " "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " "style-src 'self' 'unsafe-inline'; " "font-src 'self'; " "img-src 'self' data: blob:; " "connect-src 'self'; " "worker-src 'self' blob:" ) 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