Esta guía es una lista de comprobación concisa y centrada en el código para reforzar la seguridad de Flask.
Lista de comprobación TL;DR
- Secretos en variables de entorno/almacén de secretos (nunca en git)
- HTTPS en todas partes + HSTS (1 año o más)
- Encabezados de seguridad (CSP, X-Frame-Options, Referrer-Policy, etc.)
- Cookies y sesiones estrictas (Secure, HttpOnly, SameSite)
- Protección CSRF en rutas que cambian el estado
- Autenticación con hashing fuerte (argon2/bcrypt) y MFA cuando sea posible
- Rate limiting en endpoints de autenticación y sensibles
- Validación de entradas y cargas de archivos seguras
- Fijación de dependencias y análisis de seguridad (pip-audit, bandit)
- Reverse proxy + WSGI adecuados (gunicorn/uwsgi) y contenedores sin privilegios de root
- Logs de auditoría estructurados + alertas
1) Configuración y secretos
# app/config.py
import os
class Settings:
secret_key = os.environ["FLASK_SECRET_KEY"]
session_cookie_secure = True
session_cookie_httponly = True
session_cookie_samesite = "Lax" # o "Strict" para apps del mismo sitio
remember_cookie_duration = 0
settings = Settings()
Usa un gestor de secretos (AWS Secrets Manager, GCP Secret Manager, 1Password Connect) o al menos .env (solo para desarrollo). Nunca hagas commit de secretos.
# .env (solo para desarrollo)
FLASK_SECRET_KEY='change-me'
DATABASE_URL='postgresql://user:pass@localhost:5432/app'
2) HTTPS + HSTS (a nivel de app)
flask-talisman es una extensión ligera de Flask que configura por ti encabezados HTTP de seguridad sensatos y puede forzar HTTPS/HSTS. Por qué es importante:
- Evita la degradación del protocolo y los problemas de contenido mixto (HSTS)
- Reduce el clickjacking y la filtración de información (X‑Frame‑Options, Referrer‑Policy)
- Te proporciona una base de CSP configurable para limitar XSS
Notas:
-
Mantén desactivado el forzado de HTTPS en el desarrollo local o establece
app.debug = True. -
Detrás de un proxy, asegúrate de que
X-Forwarded-Protoesté configurado para que Talisman detecte HTTPS. -
PyPI: Flask-Talisman
-
Instalar:
pip install flask-talisman
# app/security.py
from flask import Flask
from flask_talisman import Talisman
csp = {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-inline'"], # cambia a nonces más abajo para una CSP más estricta
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:'],
}
def apply_security(app: Flask) -> None:
Talisman(
app,
content_security_policy=csp,
force_https=True,
strict_transport_security=True,
strict_transport_security_max_age=31536000,
frame_options='SAMEORIGIN',
referrer_policy='no-referrer',
session_cookie_secure=True,
session_cookie_http_only=True,
)
3) Sesiones y cookies
Prefiere sesiones del lado del servidor (por ejemplo, Redis) en lugar de cookies firmadas del lado del cliente para apps sensibles.
Por qué:
- Las sesiones del lado del servidor permiten invalidar sesiones al instante (cierres de sesión, revocaciones)
- Evitas cookies grandes del cliente, resistentes a manipulaciones pero aún reutilizables
- Es más fácil almacenar de forma segura contexto de autenticación adicional (roles, tokens anti-repetición)
Si debes usar sesiones mediante cookies, mantén las cookies con una vida útil corta, establece Secure+HttpOnly+SameSite y rota las claves.
Biblioteca: Flask-Session
Instalar:
pip install Flask-Session
from flask import Flask
from flask_session import Session
def configure_sessions(app: Flask) -> None:
app.config.update(
SESSION_TYPE='redis',
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
PERMANENT_SESSION_LIFETIME=0,
)
Session(app)
4) Protección CSRF
Biblioteca: Flask-WTF
Instalar:
pip install flask-wtf
Por qué:
- CSRF aprovecha las cookies del navegador; los tokens vinculan una acción del usuario a un formulario/página
- SameSite=Lax ayuda, pero no es suficiente para todos los flujos (por ejemplo, algunos OAuth/POST)
from flask_wtf import CSRFProtect
csrf = CSRFProtect()
def enable_csrf(app):
app.config['WTF_CSRF_TIME_LIMIT'] = None
csrf.init_app(app)
5) Autenticación, hashing y rate limiting
Bibliotecas:
- Hashing:
werkzeug.security(PBKDF2/Scrypt integrados). Para Argon2, usa argon2-cffi - Rate limiting: Flask-Limiter
Instalar:
pip install Flask-Limiter
from werkzeug.security import generate_password_hash, check_password_hash
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(key_func=get_remote_address, default_limits=["200/min"])
def hash_password(pw: str) -> str:
return generate_password_hash(pw, method='scrypt') # o 'pbkdf2:sha256', 'bcrypt', 'argon2'
def verify_password(hash_: str, pw: str) -> bool:
return check_password_hash(hash_, pw)
def protect_auth(app):
limiter.init_app(app)
@app.post('/login')
@limiter.limit("5/minute")
def login():
...
Por qué:
- Reduce los intentos de credential stuffing y fuerza bruta (Limiter)
- Almacena únicamente hashes de contraseñas; nunca texto plano. Prefiere KDF con uso intensivo de memoria
Opcional (Argon2):
pip install argon2-cffi
from argon2 import PasswordHasher
ph = PasswordHasher()
hash_ = ph.hash(password)
ph.verify(hash_, candidate)
6) Validación de entradas y cargas de archivos
Usa pydantic/marshmallow para rechazar entradas incorrectas con antelación. Para los archivos: verifica el MIME por contenido (magic), aplica listas permitidas de tamaños/extensiones y aleatoriza los nombres de archivo.
Bibliotecas:
- pydantic o marshmallow
- pydantic: validación rápida, modelos basados en tipos
- marshmallow: control explícito de esquemas/serialización
Instala una:
pip install pydantic
# o
pip install marshmallow
from pydantic import BaseModel, EmailStr, constr
class RegisterBody(BaseModel):
email: EmailStr
password: constr(min_length=12)
7) Content Security Policy (patrón nonce)
Por qué:
- Una CSP estricta con nonces bloquea la inyección de inline/script y la mayoría de XSS reflejado
- Los nonces funcionan mejor que los hashes cuando el contenido cambia en cada solicitud
from flask import g
import secrets
@app.before_request
def set_nonce():
g.csp_nonce = secrets.token_urlsafe(16)
# Jinja: <script nonce="{{ g.csp_nonce }}">...</script>
Actualiza tu CSP para incluir script-src 'self' 'nonce-{value}' y genera un nonce nuevo por solicitud; reemplaza cualquier controlador de eventos inline por scripts adecuados.
8) Higiene de dependencias (centrada en el código)
pip install --upgrade pip pip-tools pip-audit bandit
pip-compile -o requirements.txt pyproject.toml # o fija versiones con pip-tools
pip-audit
bandit -r app/
9) Observabilidad y logs
Emite logs JSON y reenvíalos a tu SIEM. Considera Sentry u OpenTelemetry para las trazas.
import json, logging, sys
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter('%(message)s'))
logger = logging.getLogger('app')
logger.setLevel(logging.INFO)
logger.addHandler(handler)
def audit(event: str, **kwargs):
logger.info(json.dumps({'event': event, **kwargs}))
10) Valores predeterminados seguros que deberías establecer
app.config.update(
JSONIFY_PRETTYPRINT_REGULAR=False,
PROPAGATE_EXCEPTIONS=False,
MAX_CONTENT_LENGTH=10 * 1024 * 1024, # cargas de 10 MB
SEND_FILE_MAX_AGE_DEFAULT=31536000,
)
11) Fragmentos adicionales de refuerzo
Añade límites de tamaño de solicitud, desactiva las filtraciones de errores del servidor y sanea los encabezados.
from flask import Flask, request
def extra_hardening(app: Flask):
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
@app.after_request
def remove_server_header(resp):
resp.headers.pop('Server', None)
return resp
@app.before_request
def block_weird_methods():
if request.method not in { 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS' }:
return ("", 405)
Lista de comprobación final antes de salir a producción
- TLS + HSTS habilitados y verificados
- CSP aplicada (primero en modo report-only y después aplicada)
- Cookies: Secure+HttpOnly+SameSite, vida útil de la sesión mínima
- CSRF en todos los métodos inseguros
- Límites de solicitudes en rutas de autenticación/sensibles
- Dependencias fijadas,
pip-auditlimpio,banditrevisado - Logs estructurados y enviados; alertas configuradas