| """ |
| Security headers middleware. |
| |
| Adds a conservative set of HTTP security headers to every response. |
| |
| Scope: backend is only reached through the Vercel rewrite proxy, so the |
| browser never loads a document from the backend origin directly. Most |
| browser-enforced headers (CSP, X-Frame-Options) ultimately need to be |
| set by Vercel for the app shell — we set them here too as a second |
| layer, because: |
| |
| - Direct calls to the HF Spaces origin (e.g., during local dev) still |
| benefit from these headers. |
| - Resend webhook endpoint + PDF downloads serve from the API origin |
| and should be framed / sniffed / embedded defensively. |
| - Defence-in-depth: if the Vercel config is ever wrong, these headers |
| still limit the blast radius. |
| |
| The CSP is a strict API-appropriate policy — the backend never serves |
| HTML pages with interactive scripts, so `default-src 'none'` is safe. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from starlette.middleware.base import BaseHTTPMiddleware |
| from starlette.requests import Request |
| from starlette.responses import Response |
|
|
| _BACKEND_CSP = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'" |
|
|
|
|
| class SecurityHeadersMiddleware(BaseHTTPMiddleware): |
| """Attach security headers to every outgoing response.""" |
|
|
| async def dispatch(self, request: Request, call_next) -> Response: |
| response = await call_next(request) |
|
|
| headers = response.headers |
|
|
| |
| |
| headers.setdefault("X-Content-Type-Options", "nosniff") |
|
|
| |
| headers.setdefault("X-Frame-Options", "DENY") |
|
|
| |
| |
| |
| headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin") |
|
|
| |
| headers.setdefault( |
| "Permissions-Policy", |
| "camera=(), microphone=(), geolocation=(), payment=(), usb=()", |
| ) |
|
|
| |
| |
| |
| headers.setdefault( |
| "Strict-Transport-Security", |
| "max-age=15552000; includeSubDomains", |
| ) |
|
|
| |
| |
| headers.setdefault("Content-Security-Policy", _BACKEND_CSP) |
|
|
| return response |
|
|