Checklist de Revisión de Seguridad para PRs
Checklist para revisiones de seguridad durante pull requests: validación de entrada, autenticación, autorización, secretos, dependencias, inyección, XSS, CSRF, logging y herramientas automatizadas con ejemplos de código.
Resumen
Este checklist te da pautas de revisión de seguridad para pull requests. Cada PR que modifique código de aplicación, configuración o infraestructura debe pasar una revisión de seguridad antes de fusionarse. Este documento cubre validación de entrada, autenticación, autorización, gestión de secretos, cambios en dependencias, prevención de inyección, XSS, CSRF, logging y herramientas automatizadas. Para la referencia autoritativa, consulta la Guía de Revisión de Código de OWASP.
Flujo de Revisión de Seguridad de PR
1. Validación de Entrada
1.1 Checklist
- [ ] Toda entrada de usuario se valida en el servidor
- [ ] La validación usa listas de permitidos (no listas de bloqueados)
- [ ] La longitud de entrada está acotada (máximo aplicado)
- [ ] El tipo de entrada se verifica (string, int, UUID, etc.)
- [ ] El formato de entrada se valida (regex para emails, URLs, fechas)
- [ ] Las subidas de archivos verifican tipo MIME y extensión
- [ ] Las subidas de archivos limitan el tamaño máximo
- [ ] Las consultas SQL usan sentencias parametrizadas (sin concatenación)
- [ ] Las consultas NoSQL previenen inyección de operadores ($where, $expr)
- [ ] La ejecución de comandos usa shell=False (no shell=True)
- [ ] El parser XML desactiva entidades externas (XXE)
- [ ] El parser JSON limita profundidad y número de claves
1.2 Ejemplo de Código: Validación de Entrada
from pydantic import BaseModel, validator, constr, conint
from typing import Optional
import uuid
class CreateUserRequest(BaseModel):
username: constr(min_length=3, max_length=32, pattern=r'^[a-zA-Z0-9_]+$')
email: constr(min_length=5, max_length=254, pattern=r'^[^@]+@[^@]+\.[^@]+$')
age: conint(ge=13, le=120)
bio: Optional[constr(max_length=500)] = None
@validator('username')
def no_sql_keywords(cls, v):
# Verificación adicional para palabras SQL (defensa en profundidad)
blocked = {'select', 'insert', 'update', 'delete', 'drop', 'union'}
if any(kw in v.lower() for kw in blocked):
raise ValueError('Username contains reserved word')
return v
# Uso: la entrada inválida lanza ValidationError
try:
user = CreateUserRequest(**request.json())
except ValidationError as e:
return jsonify({'error': 'Invalid input', 'details': e.errors()}), 400
2. Autenticación y Autorización
2.1 Checklist
- [ ] Los nuevos endpoints requieren autenticación (sin acceso sin autenticar a menos que sea explícito)
- [ ] Las verificaciones de autorización se aplican en el servidor (no solo en el cliente)
- [ ] La propiedad del recurso se verifica (verificación IDOR)
- [ ] Los permisos usan denegar por defecto
- [ ] Los cambios de rol requieren autorización de admin
- [ ] Los tokens de sesión se rotan tras un cambio de privilegios
- [ ] Los cambios de contraseña requieren verificación de la contraseña actual
- [ ] Los endpoints de API aplican rate limiting
- [ ] Las operaciones sensibles requieren re-verificación MFA
- [ ] Los tokens JWT se validan (firma, expiración, emisor, audiencia)
2.2 Ejemplo de Código: Verificación de Autorización
from functools import wraps
def authorize(action: str, resource_type: str = None):
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
user = g.current_user
resource_id = kwargs.get('id')
# Verificar permiso
if not user.can(action, resource_type, resource_id):
log_denial(user, action, resource_type, resource_id)
abort(403)
return f(*args, **kwargs)
return wrapped
return decorator
@app.route('/api/orders/<uuid:order_id>', methods=['PUT'])
@authenticate
@authorize('update', 'order')
def update_order(order_id):
order = Order.query.get_or_404(order_id)
# Verificación IDOR: confirmar propiedad
if order.user_id != g.current_user.id and not g.current_user.has_role('admin'):
abort(403)
order.update(request.json())
return jsonify(order.to_dict())
3. Gestión de Secretos
3.1 Checklist
Para una guía detallada sobre rotación de secretos, consulta el runbook de rotación de secretos.
- [ ] No hay secretos, API keys ni contraseñas en el código fuente
- [ ] No hay secretos en archivos de configuración commiteados a git
- [ ] Los secretos se cargan desde variables de entorno o un gestor de secretos
- [ ] Los archivos .env están en .gitignore
- [ ] No hay secretos en logs ni mensajes de error
- [ ] No hay secretos en parámetros URL ni query strings
- [ ] El pipeline CI/CD usa variables secretas (no hardcodeadas)
- [ ] Las imágenes Docker no contienen secretos (usa build args o env en runtime)
- [ ] La herramienta de escaneo de secretos (GitLeaks, TruffleHog) está en el hook pre-commit
- [ ] No hay secretos en comentarios ni documentación
3.2 Escaneo de Secretos en Pre-Commit
La documentación de GitLeaks cubre configuración y reglas personalizadas. La guía de OWASP sobre gestión de secretos recomienda hooks pre-commit como primera línea de defensa.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
args: ['--config', '.gitleaks.toml']
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
4. Cambios en Dependencias
4.1 Checklist
- [ ] Las nuevas dependencias provienen de fuentes confiables (registros oficiales)
- [ ] Las nuevas dependencias no tienen vulnerabilidades críticas conocidas
- [ ] Las versiones de dependencias están fijadas (sin wildcards ni rangos)
- [ ] La compatibilidad de licencia se verifica (sin GPL en proyectos comerciales)
- [ ] La dependencia se mantiene activamente (último commit < 1 año)
- [ ] No se añaden dependencias innecesarias (justificar cada nueva dependencia)
- [ ] El escaneo de dependencias (Dependabot, Snyk) pasa
- [ ] Las dependencias transitivas se revisan por problemas conocidos
- [ ] El lock file está actualizado (package-lock.json, poetry.lock, etc.)
- [ ] No se mueven dependencias de dev a producción
4.2 Verificación Automatizada de Dependencias en CI
# GitHub Actions — dependency security check
name: Dependency Security
on: [pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: npm audit
run: npm audit --audit-level=high --omit=dev
continue-on-error: false
- name: Snyk scan
uses: snyk/actions/node@master
with:
command: test
args: --severity-threshold=high --fail-on=issues
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: License check
run: npx license-checker --production --failOn 'GPL-3.0;AGPL-3.0'
5. Prevención de Inyección
5.1 Checklist
- [ ] Las consultas SQL usan sentencias parametrizadas u ORM
- [ ] Sin concatenación de strings para consultas SQL
- [ ] Sin uso de eval() o Function() con entrada de usuario
- [ ] Sin uso de innerHTML con entrada de usuario (usar textContent)
- [ ] Sin uso de document.write() con entrada de usuario
- [ ] Los motores de plantillas usan auto-escaping (Jinja2, Twig, etc.)
- [ ] Los comandos shell usan arrays de argumentos (sin concatenación)
- [ ] Las consultas LDAP escapan caracteres especiales
- [ ] Las consultas XPath usan expresiones parametrizadas
- [ ] Las consultas NoSQL sanitizan operadores $
5.2 Code Examples
# BAD: SQL injection
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# GOOD: parameterized query
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
# BAD: command injection
import subprocess
subprocess.run(f"ls {user_dir}", shell=True)
# GOOD: argument array
subprocess.run(["ls", user_dir], shell=False)
# BAD: XSS en JavaScript
document.getElementById('output').innerHTML = userInput;
# GOOD: textContent
document.getElementById('output').textContent = userInput;
6. Prevención de XSS y CSRF
6.1 Checklist XSS
- [ ] La codificación de salida se aplica a todo el contenido generado por el usuario
- [ ] El header Content-Security-Policy está configurado (sin unsafe-inline)
- [ ] El header X-XSS-Protection está configurado
- [ ] X-Content-Type-Options: nosniff está configurado
- [ ] Las cookies son HttpOnly y Secure
- [ ] El auto-escaping del framework está activado
- [ ] Sin uso de dangerouslySetInnerHTML (React) o [innerHTML] (Angular)
- [ ] La entrada de usuario en URLs se codifica (encodeURIComponent)
- [ ] Las subidas SVG se sanitizan (eliminar script tags)
- [ ] El renderizado Markdown sanitiza HTML (sin HTML crudo)
6.2 Checklist CSRF
- [ ] El token CSRF se incluye en todos los formularios que cambian estado
- [ ] El token CSRF se valida en el servidor
- [ ] Las cookies usan SameSite=Strict o SameSite=Lax
- [ ] Los endpoints de API usan headers personalizados (no solo cookies)
- [ ] El header Origin/Referer se valida en solicitudes que cambian estado
- [ ] La política CORS es restrictiva (sin orígenes wildcard)
- [ ] El endpoint de login está protegido contra CSRF (login CSRF)
- [ ] El endpoint de logout está protegido contra CSRF
6.3 Ejemplo de Código: Protección CSRF
from flask import session
import secrets
def generate_csrf_token():
if 'csrf_token' not in session:
session['csrf_token'] = secrets.token_hex(32)
return session['csrf_token']
@app.before_request
def validate_csrf():
if request.method in ('POST', 'PUT', 'PATCH', 'DELETE'):
token = request.headers.get('X-CSRF-Token') or request.form.get('csrf_token')
if not token or token != session.get('csrf_token'):
abort(403, description='CSRF token validation failed')
7. Logging y Manejo de Errores
7.1 Checklist
- [ ] Sin datos sensibles en logs (contraseñas, tokens, PII)
- [ ] Los mensajes de error no exponen detalles internos a los usuarios
- [ ] Los stack traces se registran en el servidor, no se muestran a los clientes
- [ ] Los eventos de seguridad se registran (login, acceso denegado, validación de entrada)
- [ ] Los logs incluyen user ID, timestamp, IP de origen y acción
- [ ] La inyección de logs se previene (sanitizar entrada de logs)
- [ ] El manejo de errores no silencia excepciones
- [ ] El modo debug está desactivado en producción
- [ ] Las páginas de error personalizadas no filtran información del servidor
- [ ] Pista de auditoría para todas las operaciones que cambian estado
7.2 Ejemplo de Código: Logging Seguro
import logging
import re
logger = logging.getLogger(__name__)
# Sanitizar entrada de logs para prevenir inyección de logs
def sanitize_for_log(value: str) -> str:
# Eliminar newlines y carriage returns
return re.sub(r'[\r\n]', '', str(value))[:200]
def log_security_event(user_id: str, action: str, resource: str, ip: str):
logger.info(
"Security event: user=%s action=%s resource=%s ip=%s",
sanitize_for_log(user_id),
sanitize_for_log(action),
sanitize_for_log(resource),
sanitize_for_log(ip),
)
# MAL: registrar datos sensibles
logger.info(f"User logged in with password: {password}") # NUNCA HAGAS ESTO
# BIEN: registrar sin datos sensibles
logger.info(f"User {user_id} logged in from {ip}")
8. Herramientas Automatizadas
8.1 Verificaciones de CI Requeridas
Para un pipeline completo de seguridad CI/CD, consulta la plantilla de seguridad para pipelines CI/CD.
Tool | Purpose | When to run | Block merge on
──────────────────┼────────────────────────────┼────────────────────┼──────────────────
SAST (Semgrep) | Static security analysis | Every PR | High severity
Snyk / Dependabot | Dependency vulnerabilities | Every PR | High severity
GitLeaks | Secret scanning | Every commit | Any finding
ESLint security | JS/TS security rules | Every PR | Error level
Bandit (Python) | Python security linter | Every PR | High severity
Trivy | Container image scan | Image build | High severity
OWASP ZAP | DAST (dynamic scan) | Nightly on staging | Critical
8.2 Configuración de Semgrep
La documentación de Semgrep cubre escritura de reglas personalizadas y patrones específicos por lenguaje.
# semgrep.yml — custom security rules
rules:
- id: no-hardcoded-secrets
pattern: password = "..."
message: "Hardcoded password detected"
severity: ERROR
languages: [python, javascript, java]
- id: no-sql-injection
pattern: cursor.execute(f"...")
message: "Possible SQL injection — use parameterized queries"
severity: ERROR
languages: [python]
- id: no-shell-true
pattern: subprocess.run(..., shell=True)
message: "shell=True is dangerous with user input"
severity: WARNING
languages: [python]
- id: no-innerhtml
pattern: $EL.innerHTML = $INPUT
message: "innerHTML with user input causes XSS — use textContent"
severity: ERROR
languages: [javascript]
See Also
Preguntas frecuentes
¿Cuándo debería solicitar una revisión de seguridad?
Solicita una revisión de seguridad para cualquier PR que: añada lógica de autenticación o autorización, maneje entrada de usuario, modifique configuración de seguridad, añada nuevas dependencias, cambie patrones de acceso a datos, use crypto o manejo de tokens, modifique pipelines CI/CD o toque configuración de infraestructura. Para cambios rutinarios (estilos de UI, documentación, pruebas), una auto-revisión contra este checklist es suficiente. Cuando dudes, solicita una revisión: es más barato que un incidente de seguridad.
¿Cuánto debería tomar una revisión de seguridad?
Una revisión de seguridad para un PR típico (100-500 líneas cambiadas) debería tomar 30-60 minutos. Para PRs más grandes o cambios sensibles a seguridad (auth, crypto, pagos), permite 2-4 horas. El revisor debería ejecutar las herramientas automatizadas primero (SAST, escaneo de dependencias), luego revisar manualmente los items del checklist. Si el PR introduce un riesgo de seguridad significativo, programa una sesión de revisión dedicada con el equipo de seguridad en lugar de hacerlo en línea.
¿Qué debería hacer si encuentro un problema de seguridad durante la revisión?
Si el problema es crítico (ejecución remota de código, bypass de autenticación, exposición de datos), bloquea el PR y notifica al equipo de seguridad inmediatamente. No fusiones hasta que el problema se corrija. Si el problema es de severidad alta (inyección SQL, XSS, control de acceso), solicita cambios y explica la corrección necesaria. Si el problema es medio o bajo, añade un comentario con la recomendación y deja que el autor decida si corregirlo en este PR o en un follow-up. Nunca apruebes un PR con problemas de seguridad críticos o altos conocidos.
¿Cómo balanceo la revisión de seguridad con la velocidad de desarrollo?
Automatiza tanto como sea posible: SAST, escaneo de dependencias y escaneo de secretos deberían ejecutarse automáticamente en CI y bloquear PRs con hallazgos críticos. Esto captura los problemas más comunes sin revisión manual. Para la revisión manual, enfócate en las áreas de alto riesgo: autenticación, autorización, validación de entrada y secretos. Usa este checklist como referencia rápida; no revises cada línea para cada item. Prioriza basándote en el perfil de riesgo del PR. Un PR que añade un nuevo endpoint de API necesita más revisión de seguridad que uno que cambia colores de botones.
¿Qué herramientas deberían estar en mi pipeline de seguridad CI?
Como mínimo: SAST (Semgrep o SonarQube), escaneo de dependencias (Snyk o Dependabot) y escaneo de secretos (GitLeaks). Añade DAST (OWASP ZAP) para entornos de staging. Añade escaneo de contenedores (Trivy) si construyes imágenes Docker. Añade verificación de licencias (license-checker) para compliance. Configura todas las herramientas para fallar el build de CI en hallazgos de severidad alta. Ejecuta SAST y escaneo de secretos en cada PR. Ejecuta escaneo de dependencias en cada PR y nocturnamente. Ejecuta DAST nocturnamente contra staging. Revisa los hallazgos de herramientas semanalmente para ajustar falsos positivos.
Recursos Relacionados
Checklist de Remediacion OWASP Top 10
Checklist para trackear remediacion de OWASP Top 10 vulnerabilities per application: risk assessment, fix priority, code-level remediation steps, verification testing y compliance reporting con ejemplos para cada OWASP category.
DocRunbook de Rotación de Secretos
Runbook para rotar secretos sin downtime: inventario, rotación dual-key, AWS Secrets Manager, Vault, claves JWT y emergencias.
DocPlantilla de Diseno de API Authentication
Plantilla para documentar API authentication flows y token lifecycle: auth scheme selection, token types, issuance, validation, refresh, revocation, MFA, OAuth2 flows, JWT configuration y security best practices con ejemplos de codigo.
DocPlantilla de Seguridad de Pipeline CI/CD
Una plantilla para asegurar pipelines de compilación y despliegue contra filtraciones de credenciales, manipulación, ataques a la cadena de suministro y despliegues no autorizados.
GuideChecklist de Seguridad API: Autenticación a Encriptación
Checklist de seguridad esencial para APIs: autenticación, autorización, validación de entrada, rate limiting, encriptación, logging y endurecimiento de despliegue.
DocPlantilla de Auditoría de Dependencias de Terceros
Plantilla para auditar dependencias de terceros: cumplimiento de licencias, vulnerabilidades de seguridad, salud de mantenimiento y riesgo de supply chain.