Cómo proteger una app de Flask – Paso a paso

Sat Oct 11 2025

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-Proto esté 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:

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-audit limpio, bandit revisado
  • Logs estructurados y enviados; alertas configuradas