Tema
Checklist antes de entregar
Llegó el momento de entregar el proyecto al cliente. Antes de pasar a producción, un profesional hace una revisión de seguridad sistemática. Esta lección reúne todo el curso en una checklist exhaustiva que puedes copiar a cada proyecto, más scripts y herramientas para comprobar los puntos más importantes en minutos.
Concepto
Los pilotos de avión, con miles de horas de vuelo, siguen usando una checklist antes de despegar. No porque no sepan volar, sino porque la memoria falla y un solo olvido puede ser catastrófico. En seguridad web pasa lo mismo: casi todas las filtraciones vienen de un descuido simple (un display_errors encendido, una carpeta de respaldo pública, una contraseña por defecto), no de un ataque sofisticado.
Úsala así:
- Copia la lista a un archivo
SEGURIDAD.mddel proyecto o a tu gestor de tareas. - Revísala en el entorno de producción (o uno idéntico), no solo en local.
- Lo que no aplique, márcalo como "N/A" con una razón. Lo que no se pueda cumplir, documéntalo y explícaselo al cliente.
1. Configuración del servidor y entorno
- [ ] HTTPS activo con certificado válido y renovación automática.
- [ ] Todo HTTP redirige a HTTPS con 301.
- [ ] Modo debug apagado:
display_errors = Off,APP_DEBUG=false,NODE_ENV=production. - [ ] Los errores se registran en logs, no se muestran al usuario.
- [ ] Páginas de error personalizadas (404, 500) sin trazas ni rutas internas.
- [ ]
expose_php = Off,server_tokens off, sin cabeceraX-Powered-By. - [ ] Listado de directorios desactivado (
Options -Indexes). - [ ] El document root apunta a
public/; el código,vendor/y.envestán fuera. - [ ] Versiones soportadas de PHP (8.2+) / Node (LTS) y del sistema operativo.
- [ ] Permisos de archivos correctos: carpetas 755, archivos 644,
.env600 o 640; nada en 777. - [ ] Solo los puertos necesarios abiertos (80, 443 y SSH); MySQL no expuesto a internet.
- [ ] SSH con llaves, sin login de root por contraseña (en VPS).
2. Archivos que no deben ser públicos
Comprueba que estas URLs devuelven 403 o 404:
- [ ]
/.env - [ ]
/.git/config - [ ]
/composer.json,/composer.lock,/package.json - [ ]
/vendor/y/node_modules/ - [ ]
/phpinfo.php,/info.php,/test.php(bórralos) - [ ] Respaldos:
/backup.sql,/backup.zip,/sitio.tar.gz,/config.php.bak,/config.php~ - [ ]
/storage/logs/,/logs/,/error_log - [ ] Herramientas de administración olvidadas:
/phpmyadmin,/adminer.php
3. Autenticación
- [ ] Contraseñas con
password_hash/ bcrypt / Argon2id. Ninguna en texto plano, MD5 o SHA. - [ ] Longitud mínima de 10-12 caracteres.
- [ ] Límite de intentos de login (rate limiting) y bloqueo temporal.
- [ ] Mensajes genéricos en login, registro y recuperación ("correo o contraseña incorrectos").
- [ ] Recuperación de contraseña con token aleatorio, de un solo uso, que expira y se guarda como hash.
- [ ] Se eliminaron usuarios de prueba y cuentas
admin/admin. - [ ] La cuenta del cliente usa una contraseña generada y entregada por canal seguro.
- [ ] 2FA disponible al menos para administradores (si el proyecto lo justifica).
- [ ] Reautenticación antes de acciones críticas (cambiar correo, contraseña, borrar cuenta).
4. Sesiones y cookies
- [ ] Cookies de sesión con
HttpOnly,SecureySameSite=Lax(oStrict). - [ ]
session_regenerate_id(true)/req.session.regenerate()al iniciar sesión. - [ ] Expiración por inactividad configurada.
- [ ] El logout destruye la sesión en el servidor y borra la cookie.
- [ ] En Express, almacén de sesiones persistente (Redis/BD), no
MemoryStore. - [ ] Tokens JWT (si se usan) fuera de
localStorage, con expiración corta.
5. Control de acceso
- [ ] Todas las rutas privadas verifican la sesión en el servidor.
- [ ] Cada consulta de datos privados filtra por el dueño (
WHERE usuario_id = ?). - [ ] Roles y permisos verificados en cada acción, no solo ocultando botones.
- [ ] Probado: cambiar IDs en URLs y formularios no da acceso a datos ajenos (IDOR).
- [ ] Las rutas de administración no son accesibles para usuarios normales.
- [ ] El panel de administración tiene una URL no obvia o restricción por IP (opcional, como capa extra).
6. Entrada de datos e inyecciones
- [ ] Todas las consultas SQL usan consultas preparadas; cero concatenación.
- [ ]
ORDER BYy nombres de columnas dinámicos usan listas blancas. - [ ] Toda salida de datos del usuario se escapa (
htmlspecialchars, plantillas con autoescape,textContent). - [ ] Sin
innerHTML,v-htmlodangerouslySetInnerHTMLcon datos del usuario. - [ ] HTML enriquecido sanitizado con HTML Purifier / sanitize-html.
- [ ] Validación en el servidor de todos los campos (tipo, longitud, formato, listas blancas).
- [ ] Protección contra mass assignment (campos permitidos explícitos /
$fillable). - [ ] Sin
eval,exec/systemcon datos del usuario; si es imprescindible,escapeshellarg/execFile. - [ ] Rutas de archivos protegidas contra
../(basename,realpath). - [ ] Peticiones a URLs externas indicadas por el usuario validadas contra SSRF.
7. CSRF
- [ ] Todos los formularios que modifican datos llevan token CSRF.
- [ ] Peticiones AJAX envían el token en una cabecera.
- [ ] Ninguna acción que modifica datos se ejecuta con GET (borrar, cerrar sesión, cambiar estado).
8. Subida de archivos
- [ ] Lista blanca de tipos verificando el contenido real (
finfo,file-type). - [ ] Archivos renombrados con nombres aleatorios.
- [ ] Guardados fuera de la carpeta pública o en carpeta sin ejecución de scripts.
- [ ] Límite de tamaño en el servidor (PHP/Nginx/multer).
- [ ] SVG y HTML de usuarios bloqueados o servidos como descarga.
- [ ] Probado: subir un
.phprenombrado a.jpges rechazado.
9. Cabeceras HTTP
- [ ]
Strict-Transport-Security - [ ]
Content-Security-Policy(al menosframe-ancestors,object-src 'none',base-uri 'self') - [ ]
X-Content-Type-Options: nosniff - [ ]
X-Frame-Options: DENYoSAMEORIGIN - [ ]
Referrer-Policy: strict-origin-when-cross-origin - [ ]
Permissions-Policy - [ ] Calificación A o superior en securityheaders.com.
10. APIs y CORS
- [ ] CORS con lista blanca exacta de orígenes; sin reflejar
Origincon credenciales. - [ ] Autenticación en todas las rutas privadas de la API.
- [ ] Rate limiting en la API, especialmente en login, registro, recuperación y formularios de contacto.
- [ ] Respuestas de error sin detalles internos.
- [ ] La API no devuelve campos sensibles (
password_hash, tokens, datos de otros usuarios). - [ ] Límite de tamaño del cuerpo (
express.json({ limit }),post_max_size).
11. Secretos
- [ ] Ningún secreto en el código ni en el frontend.
- [ ]
.enven.gitignorey no accesible por web. - [ ] El historial de Git no contiene secretos (
gitleaks detect). - [ ] Claves de producción distintas a las de desarrollo; claves de prueba de Stripe/PayPal sustituidas por las reales.
- [ ] Usuario de base de datos propio de la app con permisos mínimos (no
root). - [ ] Contraseña de BD larga y aleatoria.
- [ ] Credenciales entregadas al cliente por un gestor de contraseñas o enlace de un solo uso.
12. Dependencias
- [ ]
composer auditsin vulnerabilidades altas/críticas. - [ ]
npm audit --omit=devsin vulnerabilidades altas/críticas. - [ ] CMS, temas y plugins (WordPress, etc.) actualizados; eliminados los que no se usan.
- [ ] Archivos de bloqueo (
composer.lock,package-lock.json) incluidos en el repositorio. - [ ] Scripts de CDN externos con versión fija (y
integritycuando sea posible).
13. Respaldos, logs y monitoreo
- [ ] Respaldos automáticos de base de datos y archivos subidos, fuera del servidor.
- [ ] Se probó restaurar un respaldo (un respaldo no probado no es un respaldo).
- [ ] Logs de errores y de accesos activos y rotados.
- [ ] Registro de eventos de seguridad: logins fallidos, cambios de contraseña, acciones de admin.
- [ ] Monitoreo de disponibilidad (UptimeRobot o similar) con aviso por correo.
- [ ] Alertas de vencimiento de dominio y certificado.
14. Privacidad y aspectos legales
- [ ] Aviso de privacidad publicado (en México, conforme a la ley de protección de datos personales; en la UE, RGPD).
- [ ] Solo se recolectan los datos personales necesarios.
- [ ] Banner de cookies si se usan cookies de analítica o publicidad (según el país).
- [ ] Nunca se guardan datos completos de tarjetas: el pago lo procesa la pasarela (Stripe, Mercado Pago, PayPal).
Scripts de verificación rápida
bash
#!/usr/bin/env bash
# Uso: ./revisar-sitio.sh https://tusitio.com
SITIO="$1"
echo "== Cabeceras de seguridad =="
curl -sI "$SITIO" | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy|x-powered-by|^server:'
echo
echo "== Redirección HTTP → HTTPS =="
curl -sI "${SITIO/https:/http:}" | grep -iE '^HTTP|^location'
echo
echo "== Archivos que deberían dar 403/404 =="
for ruta in .env .git/config composer.json composer.lock package.json phpinfo.php info.php \
backup.sql backup.zip error_log vendor/ storage/logs/ adminer.php phpmyadmin/; do
codigo=$(curl -s -o /dev/null -w "%{http_code}" "$SITIO/$ruta")
if [[ "$codigo" == "200" ]]; then
echo "❌ $codigo /$ruta ← ¡expuesto!"
else
echo "✅ $codigo /$ruta"
fi
donebash
# PHP
composer audit
composer outdated --direct
# Node
npm audit --omit=dev
npm outdated
# Secretos en el repositorio
gitleaks detect --source . -vbash
# Consultas con variables concatenadas (PHP)
grep -rnE "(query|exec)\(.*\\\$" --include=*.php . | grep -v vendor/
# Salida sin escapar en vistas PHP
grep -rn "<?= \$" --include=*.php . | grep -v "e(" | grep -v vendor/
# innerHTML y eval en JavaScript
grep -rnE "innerHTML|eval\(|document\.write" --include=*.js src/
# Restos de depuración
grep -rnE "var_dump|print_r|console\.log|dd\(" --include=*.{php,js} . | grep -vE "vendor/|node_modules/"Herramientas gratuitas útiles
- securityheaders.com y SSL Labs: cabeceras y TLS.
- OWASP ZAP: escáner automático de vulnerabilidades (úsalo solo en tus sitios).
- Mozilla Observatory: revisión general de configuración.
- gitleaks: secretos en el repositorio.
- WPScan: si el proyecto es WordPress.
Documento de entrega al cliente
Además del código, entrega un documento breve con:
txt
ENTREGA DE PROYECTO — SEGURIDAD
================================
Proyecto: Tienda Ejemplo Fecha: 18/10/2026
1. Accesos entregados (vía gestor de contraseñas):
- Panel de administración: https://tienda.com/admin
- Hosting / cPanel, base de datos, correo SMTP, pasarela de pago
2. Medidas implementadas:
- HTTPS con renovación automática y HSTS
- Contraseñas con bcrypt, límite de intentos de login
- Protección CSRF, XSS e inyección SQL
- Cabeceras de seguridad (calificación A en securityheaders.com)
- Respaldos diarios automáticos (retención 30 días) en almacenamiento externo
3. Responsabilidades del cliente:
- No compartir contraseñas; activar 2FA en el panel
- Contratar mantenimiento para actualizaciones mensuales
- Avisar de inmediato ante actividad sospechosa
4. Pendientes / riesgos aceptados:
- (ej.) El hosting no permite cambiar el document root: se protegió con .htaccessError común
Entregar y desaparecer. Las vulnerabilidades se descubren todo el tiempo en librerías que hoy son seguras. Ofrece un plan de mantenimiento (actualizaciones, respaldos, monitoreo): protege al cliente y te da un ingreso recurrente.
Ejercicios
- Ejecuta el script
revisar-sitio.shcontra un proyecto tuyo publicado y corrige al menos tres hallazgos. - Recorre las secciones 3 a 7 de la checklist sobre un proyecto PHP que hayas hecho en el curso de PHP y anota qué falta.
- Redacta el apartado "Responsabilidades del cliente" para una tienda en línea pequeña.
Ver solución
- Respuesta libre. Los hallazgos más comunes y su solución:
apache
# .htaccess: soluciona varios de una vez
Options -Indexes
<FilesMatch "^\.">
Require all denied
</FilesMatch>
RedirectMatch 404 ^/(vendor|storage|\.git)(/|$)
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header unset X-Powered-By
</IfModule>Respuesta libre. En proyectos de práctica suele faltar: límite de intentos de login,
session_regenerate_idtras el login, configuraciónSecure/SameSitede la cookie, tokens CSRF en formularios de borrado y verificación de dueño en las consultas de edición.Ejemplo:
txt
- Usar contraseñas únicas y activar la verificación en dos pasos en el panel y en el correo.
- No crear cuentas de administrador para personas que no las necesitan; dar de baja a empleados que se van.
- No instalar plugins ni modificar el código sin consultar al desarrollador.
- Mantener vigente el plan de hosting, el dominio y el contrato de mantenimiento.
- Revisar periódicamente los pedidos y avisar de cualquier actividad extraña.Resumen
- Una checklist evita los descuidos simples que causan la mayoría de las filtraciones.
- Revisa en producción: configuración, archivos expuestos, autenticación, sesiones, control de acceso, inyecciones, CSRF, archivos, cabeceras, CORS, secretos, dependencias y respaldos.
- Automatiza lo que puedas con scripts,
composer audit,npm audity gitleaks. - Entrega un documento de seguridad y credenciales por canal seguro.
- Ofrece mantenimiento: la seguridad es un proceso continuo, no un evento.