From 8dcc0f86f8f184ebe6ac30be28fb5dd20e22f8b1 Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 26 May 2026 15:25:55 +0300 Subject: [PATCH] fix: conditional HSTS via FORCE_HTTPS, safe for enterprise without certs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HSTS (Strict-Transport-Security) is now opt-in via FORCE_HTTPS=true. Without it enabled, no HSTS header is sent — safe for dev environments and enterprise deployments behind HTTP-only proxies without HTTPS certs. When FORCE_HTTPS=true, sends 'max-age=31536000; includeSubDomains'. Enterprise recommendation: set HSTS at nginx/ingress level instead. .env.example documents the risk: enabling without certs breaks site access. --- backend/.env.example | 4 ++++ backend/src/app.py | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 951e0255..cf663a1b 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -15,3 +15,7 @@ SESSION_SECRET_KEY=change-me-to-a-random-secret FEATURES__DATASET_REVIEW=true FEATURES__HEALTH_MONITOR=true APP_TIMEZONE=Europe/Moscow + +# HSTS — включать только при настроенном HTTPS (nginx/ingress) +# Включение без сертификатов сломает доступ к сайту! +# FORCE_HTTPS=true diff --git a/backend/src/app.py b/backend/src/app.py index aecfaee5..60228db6 100755 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -191,11 +191,23 @@ app.add_middleware( ) # HSTS — Strict-Transport-Security header (see [SEC:L-2]) +# Only active when FORCE_HTTPS=true (enterprise deployments with proper certs). +# In dev or behind HTTP-only proxies, HSTS is disabled to avoid lockout. class HSTSMiddleware(BaseHTTPMiddleware): - """Add Strict-Transport-Security header to every response.""" + """Add Strict-Transport-Security header when FORCE_HTTPS=true. + + Enterprise note: In production, set HSTS at the nginx/ingress level + (add_header Strict-Transport-Security ...). This middleware is a + fallback for environments where nginx is not configured to do so. + """ + def __init__(self, app): + super().__init__(app) + self._enabled = os.getenv("FORCE_HTTPS", "").strip().lower() in {"1", "true", "yes"} + async def dispatch(self, request: Request, call_next): response = await call_next(request) - response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" + if self._enabled: + response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" return response app.add_middleware(HSTSMiddleware)