Seguridad
Buenas prácticas de seguridad y guía de endurecimiento para Duckling.
Resumen de la auditoría de seguridad
Última auditoría: diciembre de 2025
Estado de las vulnerabilidades
| Categoría | Estado | Notas |
|---|---|---|
| Vulnerabilidades en dependencias | ✅ Corregido | Actualizados flask-cors, gunicorn, werkzeug |
| Modo depuración de Flask | ✅ Corregido | Ahora usa variables de entorno |
| Recorrido de rutas | ✅ Corregido | Añadida validación de rutas |
| Inyección SQL | ✅ Protegido | ORM SQLAlchemy con consultas parametrizadas |
| XSS (Cross-Site Scripting) | ⚠️ Mitigado | Usa dangerouslySetInnerHTML solo para documentos de confianza |
| CORS | ✅ Configurado | Restringido a orígenes localhost en desarrollo |
Lista de comprobación para producción
Antes de desplegar en producción, asegúrese de:
- Establecer la variable de entorno
FLASK_DEBUG=false - Establecer una variable de entorno
SECRET_KEYfuerte - Configurar
FLASK_HOSTde forma adecuada (no 0.0.0.0 salvo detrás de un proxy inverso) - Actualizar los orígenes CORS en
backend/duckling.pypara que coincidan con su dominio - Usar HTTPS en producción (configurar mediante el proxy inverso)
- Establecer
MAX_CONTENT_LENGTHadecuado a su caso de uso - Revisar y restringir las extensiones de subida si es necesario
- Habilitar limitación de tasa (mediante proxy inverso o middleware)
- Configurar supervisión de registros para eventos de seguridad
Variables de entorno
| Variable | Predeterminado | Descripción |
|---|---|---|
FLASK_DEBUG | false | Activar modo depuración (nunca en producción) |
FLASK_HOST | 127.0.0.1 | Host de escucha |
FLASK_PORT | 5001 | Puerto de escucha |
SECRET_KEY | dev-secret-key... | Clave secreta de Flask (DEBE cambiarse en producción) |
MAX_CONTENT_LENGTH | 104857600 | Tamaño máximo de subida en bytes (100 MB) |
Clave secreta
Genere una clave secreta segura para producción:
Medidas de seguridad
Seguridad del backend
1. Configuración basada en el entorno
- Modo depuración desactivado por defecto
- Claves secretas cargadas desde variables de entorno
- Enlace por defecto a localhost (127.0.0.1)
2. Validación de entradas
- Validación de subidas (lista blanca de extensiones)
- Límites de tamaño de archivo (100 MB por defecto)
- Límites de longitud y saneamiento de consultas de búsqueda
3. Protección frente a recorrido de rutas
- Los puntos de entrega de archivos validan las rutas
- Las rutas resueltas se comprueban frente a directorios permitidos
- Se bloquean secuencias de recorrido de directorios
def validate_path(path: str, allowed_dir: str) -> bool:
"""Garantizar que la ruta no sale del directorio permitido."""
resolved = os.path.realpath(path)
return resolved.startswith(os.path.realpath(allowed_dir))
4. Seguridad de la base de datos
- El ORM SQLAlchemy evita la inyección SQL
- Consultas parametrizadas para todas las operaciones
- Comodines LIKE escapados en búsquedas
5. Configuración CORS
- Orígenes restringidos a localhost en desarrollo
- Configurable para despliegues en producción
Seguridad del frontend
1. Seguridad del contenido
- La documentación se renderiza como HTML de confianza generado en el backend
- No se renderiza contenido generado por el usuario como HTML
2. Comunicación con la API
- Todas las llamadas a la API usan interfaces tipadas
- Las respuestas de error se gestionan correctamente
Configuración HTTPS
Let's Encrypt con Certbot
# Instalar certbot
sudo apt install certbot python3-certbot-nginx
# Obtener certificado
sudo certbot --nginx -d docling.example.com
# Renovación automática (suele configurarse sola)
sudo certbot renew --dry-run
Configuración SSL en Nginx
server {
listen 443 ssl http2;
server_name docling.example.com;
ssl_certificate /etc/letsencrypt/live/docling.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/docling.example.com/privkey.pem;
# Configuración SSL moderna
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
}
Limitación de tasa
Limitación de tasa en Nginx
# Definir zona de limitación
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://localhost:5001;
}
}
Flask-Limiter
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route("/api/convert", methods=["POST"])
@limiter.limit("10 per minute")
def convert():
pass
Seguridad en subidas de archivos
Extensiones permitidas
ALLOWED_EXTENSIONS = {
'pdf', 'docx', 'pptx', 'xlsx',
'html', 'htm', 'md', 'markdown',
'png', 'jpg', 'jpeg', 'tiff', 'gif', 'webp', 'bmp',
'asciidoc', 'adoc', 'xml'
}
def allowed_file(filename: str) -> bool:
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
Saneamiento de nombres de archivo
from werkzeug.utils import secure_filename
def sanitize_filename(filename: str) -> str:
"""Sanea el nombre de archivo para un almacenamiento seguro."""
return secure_filename(filename)
Cabeceras de seguridad
Cabeceras en Nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
Cabeceras en Flask
@app.after_request
def add_security_headers(response):
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-XSS-Protection'] = '1; mode=block'
return response
Seguridad de dependencias
Dependencias de Python
# Instalar pip-audit
pip install pip-audit
# Ejecutar auditoría
cd backend
source venv/bin/activate
pip-audit
Dependencias de Node.js
Informar vulnerabilidades
Si descubre una vulnerabilidad de seguridad:
- No abra un issue público
- Escriba directamente a los mantenedores con:
- Descripción de la vulnerabilidad
- Pasos para reproducirla
- Impacto potencial
- Posible solución (si la tiene)
Responderemos en un plazo de 48 horas y colaboraremos con usted para:
- Confirmar la vulnerabilidad
- Desarrollar una corrección
- Coordinar la divulgación
Limitaciones conocidas
- XSS en el visor de documentación: el panel de documentación usa
dangerouslySetInnerHTMLpara renderizar HTML convertido desde markdown. Es aceptable porque: - La documentación solo se sirve desde archivos locales
- No se renderiza contenido generado por el usuario
-
El contenido se convierte en el servidor con una biblioteca markdown de confianza
-
Acceso a archivos locales: la aplicación lee y escribe en directorios configurados. Asegure permisos adecuados en el sistema de archivos.
-
Sin autenticación: la aplicación está pensada para uso local/personal y no incluye autenticación de usuario. En despliegues multiusuario, añada autenticación mediante proxy inverso o middleware.