security / backend /app /main.py
GitHub Actions
Deploy backend from GitHub c7b5c288e89c3d1884e6d556b62378976a19fef4
76964f5
"""
FastAPI main application entry point.
Configures CORS, secure headers, routes, and observability.
Env vars: All from core/config.py
Run: uvicorn backend.app.main:app --host 0.0.0.0 --port 7860
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
from backend.app.core.config import settings
from backend.app.core.logging import setup_logging, get_logger
from backend.app.api.routes import router as analysis_router
from backend.app.api.models import HealthResponse
# Sentry (optional)
if settings.SENTRY_DSN:
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
sentry_sdk.init(dsn=settings.SENTRY_DSN, integrations=[FastApiIntegration()])
setup_logging(settings.LOG_LEVEL)
logger = get_logger(__name__)
# Prometheus metrics
REQUEST_COUNT = Counter("http_requests_total", "Total HTTP requests", ["method", "endpoint", "status"])
REQUEST_LATENCY = Histogram("http_request_duration_seconds", "Request latency", ["method", "endpoint"])
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("Starting Zynera API")
yield
logger.info("Shutting down")
app = FastAPI(
title=settings.APP_NAME,
version="1.0.0",
description="Zynera – AI-powered detection of LLM misuse in information operations",
lifespan=lifespan,
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
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["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
return response
@app.middleware("http")
async def metrics_middleware(request: Request, call_next):
import time
start = time.time()
response = await call_next(request)
duration = time.time() - start
REQUEST_COUNT.labels(request.method, request.url.path, response.status_code).inc()
REQUEST_LATENCY.labels(request.method, request.url.path).observe(duration)
return response
app.include_router(analysis_router)
@app.get("/", include_in_schema=False)
async def root():
"""Root redirect – keeps HF Space health checker happy."""
return RedirectResponse(url="/health")
@app.get("/health", response_model=HealthResponse)
async def health():
return HealthResponse()
@app.get("/metrics")
async def metrics():
return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST)