Oliver-ai-bot_2.0/backend/app/middleware/security.py
Vadym Samoilenko 42a20659a7 Phase 4: Production hardening, Cloud Run worker, deploy pipeline
- Multi-stage Dockerfile with gunicorn, non-root user, healthcheck
- Structured JSON logging with request ID propagation
- Redis-based rate limiting middleware (sliding window)
- Security headers middleware (X-Frame-Options, CSP, XSS protection)
- Global exception handler hiding stack traces in production
- Disable /docs /redoc in production mode
- CORS hardened to explicit methods/headers
- TrustedHostMiddleware support
- Health endpoints: /health returns 503 on degraded, /health/live for liveness
- Frontend static export (output: 'export') for Apache serving
- docker-compose.prod.yml with resource limits, pinned images, celery-worker
- deploy.sh: full pipeline (git pull → build → up → frontend → copy to /var/www)
- Cloud Run worker: Dockerfile.worker, cloudbuild.yaml, deploy script (optical-414516)
- Celery hardened: task time limits, healthcheck task, configurable concurrency
- Admin panel improvements, system prompts, AD group sync

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:42:10 +00:00

30 lines
1.1 KiB
Python

"""
Security headers and Request ID middleware.
"""
import logging
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from app.core.logging_config import request_id_var, generate_request_id
logger = logging.getLogger(__name__)
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
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["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
return response
class RequestIdMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
rid = request.headers.get("x-request-id") or generate_request_id()
request_id_var.set(rid)
response = await call_next(request)
response.headers["X-Request-ID"] = rid
return response