zenith-backend / app /middleware /security.py
teoat's picture
Upload folder using huggingface_hub
4ae946d verified
import os
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""
Comprehensive security headers middleware implementing defense-in-depth.
Protects against:
- XSS (Content-Security-Policy)
- Clickjacking (X-Frame-Options)
- MIME-type attacks (X-Content-Type-Options)
- Data leaks (Referrer-Policy)
- Unwanted features (Permissions-Policy)
"""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Content Security Policy - Strict policy to prevent XSS
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " # Adjust 'unsafe-*' as needed
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https: blob:; "
"font-src 'self' data:; "
"connect-src 'self' ws: wss:; "
"frame-ancestors 'none'; "
"base-uri 'self'; "
"form-action 'self';"
)
# Clickjacking protection
response.headers["X-Frame-Options"] = "DENY"
# Prevent MIME-type sniffing
response.headers["X-Content-Type-Options"] = "nosniff"
# XSS Protection (legacy but still useful for older browsers)
response.headers["X-XSS-Protection"] = "1; mode=block"
# Force HTTPS (only in production)
environment = os.getenv("ENVIRONMENT", "development").lower()
if environment != "development":
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains; preload"
)
# Referrer Policy - Limit information leakage
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
# Permissions Policy - Disable unnecessary browser features
response.headers["Permissions-Policy"] = (
"geolocation=(), "
"microphone=(), "
"camera=(), "
"payment=(), "
"usb=(), "
"magnetometer=(), "
"gyroscope=(), "
"accelerometer=()"
)
# Cross-Origin policies for additional isolation
response.headers["Cross-Origin-Embedder-Policy"] = "require-corp"
response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
response.headers["Cross-Origin-Resource-Policy"] = "same-origin"
# Remove server identity to prevent information disclosure
if "server" in response.headers:
del response.headers["server"]
# Add custom security header for version tracking (optional)
response.headers["X-Security-Version"] = "1.0.0"
return response
class TrustedHostMiddleware(BaseHTTPMiddleware):
"""
Middleware for mTLS and Trusted Host validation placeholders.
In real deployment, mTLS is terminated at the Load Balancer / Mesh (Istio),
but this layer validates the forwarded client certificate headers.
"""
async def dispatch(self, request: Request, call_next):
# Placeholder: Verify X-Forwarded-Client-Cert if we were in stricter mode
# cert = request.headers.get("X-Forwarded-Client-Cert")
# if not cert and request.url.path not in ["/health", "/metrics", "/docs", "/openapi.json"]:
# ...
return await call_next(request)
class ZeroTrustMiddleware(BaseHTTPMiddleware):
"""
Middleware to enforce strict API Key validation for backend-to-backend communication
between Electron (Main Process) and FastAPI.
"""
def __init__(self, app, api_key: str | None = None):
super().__init__(app)
self.api_key = api_key or os.getenv("BACKEND_API_KEY")
self.exempt_paths = [
"/health",
"/health/ready",
"/health/live",
"/metrics",
"/docs",
"/openapi.json",
"/redoc",
]
async def dispatch(self, request: Request, call_next):
# Exempt health checks and documentation
if request.url.path in self.exempt_paths:
return await call_next(request)
# Check for API Key in headers
provided_key = request.headers.get("X-API-Key")
if not self.api_key:
# If no API key is configured, allow for now but log warning
# In true Zero Trust, we would fail here.
return await call_next(request)
if provided_key != self.api_key:
from starlette.responses import JSONResponse
return JSONResponse(
status_code=403,
content={"detail": "Zero-Trust Violation: Invalid or missing API Key"},
)
return await call_next(request)