Spaces:
Running
Running
File size: 1,713 Bytes
70e641d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | """Middleware de cabeceras de seguridad.
Añade CSP, HSTS (en prod), X-Content-Type-Options, Referrer-Policy y X-Frame-Options,
ausentes en la versión PHP.
"""
from __future__ import annotations
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from ..config import obtener_config
# CSP estricta: sólo mismo origen. El frontend inlinea lo mínimo; ajustar si se
# externalizan scripts. 'unsafe-inline' se evita salvo para estilos si fuese necesario.
# worker-src incluye blob: porque PDF.js crea su worker desde un blob URL
# (URL.createObjectURL + new Worker); sin ello el parseo de PDF en cliente falla.
_CSP = (
"default-src 'self'; "
"img-src 'self' data: blob:; "
"script-src 'self'; "
"worker-src 'self' blob:; "
"child-src 'self' blob:; "
"style-src 'self' 'unsafe-inline'; "
"font-src 'self'; "
"connect-src 'self'; "
"frame-ancestors 'none'; "
"base-uri 'self'"
)
class CabecerasSeguridad(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
resp = await call_next(request)
cfg = obtener_config()
resp.headers.setdefault("Content-Security-Policy", _CSP)
resp.headers.setdefault("X-Content-Type-Options", "nosniff")
resp.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
resp.headers.setdefault("X-Frame-Options", "DENY")
resp.headers.setdefault("Permissions-Policy", "geolocation=(), microphone=(), camera=(self)")
if cfg.entorno == "prod":
resp.headers.setdefault(
"Strict-Transport-Security", "max-age=31536000; includeSubDomains"
)
return resp
|