seguridad·12 min de lectura

Checklist de Seguridad Antes de Hacer Deploy

Checklist completo de seguridad para verificar antes de deployar tu aplicación web. Variables de entorno, headers HTTP, OWASP Top 10, RLS y más con herramientas gratuitas.

Checklist de Seguridad Antes de Hacer Deploy

La mayoría de los desarrolladores se enfocan en features y performance antes de hacer deploy. La seguridad queda para después, o directamente se ignora. El resultado: aplicaciones en producción con archivos .env expuestos, headers sin configurar, bases de datos sin políticas de acceso y secrets hardcodeados en el código fuente.

Este checklist cubre lo mínimo que deberías verificar antes de ir a producción. No es una auditoría de seguridad completa, pero sí es suficiente para cerrar las puertas que la mayoría deja abiertas. Si quieres una guía más profunda sobre seguridad en el stack de NextJS, revisa la guía completa de seguridad en aplicaciones NextJS.

Esto no reemplaza una auditoría profesional

Un checklist cubre los errores más comunes. Si tu aplicación maneja datos sensibles de usuarios, información financiera o datos de salud, necesitas una auditoría de seguridad profesional además de este checklist.

1. Variables de entorno y archivos sensibles

Las variables de entorno son el vector de ataque más simple y más común. Un .env commiteado a GitHub o accesible públicamente expone todas las credenciales de tu aplicación de un solo golpe.

Lo que debes verificar

El archivo .env debe estar en .gitignore. Parece obvio, pero sigue siendo una de las causas más frecuentes de filtración de credenciales. Verifica que .env, .env.local, .env.production y cualquier variante estén listados:

bash
# Verificar que .env está en .gitignore
grep -r "\.env" .gitignore

Si no ves las entradas, agrégalas:

gitignore
# .gitignore
.env
.env.local
.env.production
.env.development
.env*.local

No hay secrets hardcodeados en el código. Busca en tu codebase cualquier string que parezca un token, API key o password:

bash
# Buscar posibles secrets hardcodeados
grep -rn "sk_live\|sk_test\|AKIA\|ghp_\|password\s*=" --include="*.ts" --include="*.tsx" --include="*.js" src/

Si encuentras algo, mueve ese valor a una variable de entorno inmediatamente. Para una revisión más completa de tu repositorio, revisa la guía sobre cómo detectar secrets expuestos en GitHub.

El prefijo NEXT_PUBLIC_ solo está en variables realmente públicas. En NextJS, cualquier variable con este prefijo se incluye en el bundle del cliente y es visible para cualquier usuario. Revisa que no tengas algo como NEXT_PUBLIC_DATABASE_URL o NEXT_PUBLIC_STRIPE_SECRET_KEY:

bash
# Listar todas las variables NEXT_PUBLIC_ en tu proyecto
grep -rn "NEXT_PUBLIC_" --include="*.ts" --include="*.tsx" --include="*.env*" .

Si necesitas repasar cómo funcionan las variables de entorno en NextJS y Vercel, la guía de variables de entorno cubre el tema en detalle.

Verificación manual en producción

Después del deploy, verifica que tu servidor no esté sirviendo archivos sensibles públicamente:

bash
# Verificar que tu sitio no expone archivos sensibles
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.env
# Debería devolver 404, no 200
 
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.env.local
# También debería ser 404
 
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.git/HEAD
# También debería ser 404
 
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.git/config
# También debería ser 404

Si cualquiera de estos devuelve un código 200, tienes un problema serio. Significa que tu servidor está sirviendo esos archivos como contenido estático.

Automatiza esta verificación

Verificar estas rutas manualmente es tedioso y fácil de olvidar. El escáner de .env de datahogo revisa automáticamente docenas de rutas comunes como /.env, /.git/HEAD, /.npmrc, /wp-config.php y más. Funciona sin registro y te da resultados en segundos.

2. Headers de seguridad

Los headers de seguridad HTTP son instrucciones que tu servidor le envía al navegador indicándole cómo debe comportarse. Sin ellos, tu sitio queda expuesto a ataques que son completamente prevenibles. La guía completa de headers de seguridad cubre cada header en detalle, pero aquí va el resumen de lo que necesitas para el checklist.

Headers que debes tener

HeaderQué haceValor recomendado
Strict-Transport-SecurityFuerza HTTPS siempremax-age=63072000; includeSubDomains; preload
Content-Security-PolicyControla qué recursos puede cargar la páginaDepende de tu app
X-Frame-OptionsPreviene clickjackingDENY o SAMEORIGIN
X-Content-Type-OptionsPreviene MIME sniffingnosniff
Referrer-PolicyControla qué info de referrer se envíastrict-origin-when-cross-origin
Permissions-PolicyControla acceso a APIs del navegadorSegún lo que uses

Verificación rápida con curl

bash
# Ver los headers de respuesta de tu sitio
curl -I https://tu-sitio.com

Busca los headers en la respuesta. Si no ves Strict-Transport-Security, X-Content-Type-Options o X-Frame-Options, tu sitio no los tiene configurados.

Configuración en next.config.ts

Si usas NextJS, la forma más directa de agregar headers de seguridad es desde next.config.ts:

ts
// next.config.ts
import type { NextConfig } from 'next'
 
const securityHeaders = [
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload',
  },
  {
    key: 'X-Frame-Options',
    value: 'DENY',
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff',
  },
  {
    key: 'Referrer-Policy',
    value: 'strict-origin-when-cross-origin',
  },
  {
    key: 'Permissions-Policy',
    value: 'camera=(), microphone=(), geolocation=()',
  },
  {
    key: 'X-DNS-Prefetch-Control',
    value: 'on',
  },
]
 
const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: securityHeaders,
      },
    ]
  },
}
 
export default nextConfig
CSP requiere más trabajo

Content-Security-Policy no está en el ejemplo de arriba porque necesita configuración específica para tu aplicación. Un CSP incorrecto puede romper tu sitio. Empieza con Content-Security-Policy-Report-Only para ver qué bloquea sin afectar a los usuarios, y ajusta las directivas una por una.

Verificación automatizada

Después de configurar los headers, verifica que estén presentes. Puedes usar curl -I como se mostró arriba, o el verificador de headers de datahogo que analiza tu URL y te dice exactamente cuáles faltan y cuáles están mal configurados.

3. Vulnerabilidades OWASP Top 10

El OWASP Top 10 es el estándar de referencia para vulnerabilidades en aplicaciones web. Cada categoría representa un tipo de ataque que deberías estar previniendo activamente. Aquí va una autoevaluación rápida para cada una.

A01: Broken Access Control

La categoría número uno. Se refiere a cuando un usuario puede hacer algo que no debería poder hacer: ver datos de otro usuario, acceder a rutas de admin sin serlo, modificar registros que no le pertenecen.

Pregúntate:

  • ¿Verificas permisos en cada API Route y Server Action, no solo en el frontend?
  • ¿Las rutas de admin están protegidas por middleware?
  • ¿Un usuario puede acceder a datos de otro usuario cambiando un ID en la URL?
ts
// MAL: confiar solo en que el frontend no muestra el botón
export async function DELETE(req: Request) {
  const { id } = await req.json()
  await db.delete(posts).where(eq(posts.id, id))
}
 
// BIEN: verificar que el usuario tiene permiso
export async function DELETE(req: Request) {
  const session = await auth()
  if (!session?.user) return Response.json({ error: 'No autorizado' }, { status: 401 })
 
  const { id } = await req.json()
  const post = await db.query.posts.findFirst({ where: eq(posts.id, id) })
 
  if (post?.userId !== session.user.id) {
    return Response.json({ error: 'Prohibido' }, { status: 403 })
  }
 
  await db.delete(posts).where(eq(posts.id, id))
}

A02: Cryptographic Failures

Datos sensibles que no están encriptados o están encriptados de forma incorrecta.

Pregúntate:

  • ¿Tu sitio usa HTTPS en todas las rutas?
  • ¿Los passwords se hashean con bcrypt o argon2, no con MD5 o SHA-1?
  • ¿Las cookies de sesión tienen los flags Secure, HttpOnly y SameSite?

A03: Injection

Datos de usuario que se ejecutan como código. Incluye SQL injection, NoSQL injection y command injection.

Pregúntate:

  • ¿Usas un ORM (Prisma, Drizzle) o queries parametrizadas en lugar de concatenar strings SQL?
  • ¿Validas y sanitizas todo input del usuario con algo como Zod antes de usarlo?
  • ¿Evitas ejecutar comandos del sistema con input de usuario?
ts
// MAL: SQL injection directo
const query = `SELECT * FROM users WHERE email = '${email}'`
 
// BIEN: query parametrizada
const user = await db.query.users.findFirst({
  where: eq(users.email, email),
})

A04: Insecure Design

Problemas de arquitectura que no se resuelven con un parche. Se refiere a decisiones de diseño fundamentalmente inseguras.

Pregúntate:

  • ¿Tu sistema de recuperación de contraseña tiene rate limiting?
  • ¿Las operaciones sensibles requieren re-autenticación?
  • ¿Tu API tiene límites de datos razonables para prevenir scraping masivo?

A05: Security Misconfiguration

Configuraciones por defecto que no se cambiaron, features de debug activas en producción, permisos demasiado amplios.

Pregúntate:

  • ¿Deshabilitaste los mensajes de error detallados en producción?
  • ¿Los headers de seguridad están configurados? (Sección 2 de este checklist)
  • ¿El directorio .git no es accesible públicamente?
  • ¿Removiste todas las rutas de debug y endpoints de prueba?

A06: Vulnerable and Outdated Components

Dependencias con vulnerabilidades conocidas que no se han actualizado.

Pregúntate:

  • ¿Corriste npm audit recientemente?
  • ¿Tienes dependencias con más de un año sin actualización?
  • ¿Usas lockfiles (package-lock.json o pnpm-lock.yaml) para fijar versiones?

A07: Identification and Authentication Failures

Sistemas de autenticación que se pueden saltar o explotar.

Pregúntate:

  • ¿Tus tokens de sesión expiran?
  • ¿Implementaste rate limiting en el endpoint de login?
  • ¿Invalidas sesiones al cambiar password?
  • ¿Usas una librería probada como Auth.js en lugar de implementar auth desde cero?

A08: Software and Data Integrity Failures

Código o datos que se modifican sin verificación. Incluye pipelines de CI/CD inseguros y dependencias no verificadas.

Pregúntate:

  • ¿Verificas la integridad de las dependencias que instalas?
  • ¿Tu pipeline de CI/CD usa secrets de forma segura?
  • ¿Tienes protección contra ataques de supply chain (lockfiles, auditoría)?

A09: Security Logging and Monitoring Failures

No tener visibilidad de lo que pasa en tu aplicación. Si no monitoreas, no sabes cuándo te atacan.

Pregúntate:

  • ¿Tienes logs de intentos de login fallidos?
  • ¿Monitoreas errores 401 y 403 frecuentes?
  • ¿Tienes alertas para comportamiento anómalo?

A10: Server-Side Request Forgery (SSRF)

Cuando tu servidor hace requests a URLs proporcionadas por el usuario sin validación.

Pregúntate:

  • ¿Validas las URLs que tu servidor usa para hacer requests?
  • ¿Bloqueas requests a IPs internas y localhost?
  • ¿Limitas los protocolos permitidos a http y https?
Evaluación automatizada

Responder estas preguntas honestamente te da una idea de tu postura de seguridad. Si quieres una evaluación más estructurada, la auditoría OWASP de datahogo te asigna un grade por cada categoría del Top 10 y te indica exactamente dónde tienes gaps. Toma menos de 2 minutos y es gratuita.

Si quieres profundizar en cómo cada categoría de OWASP aplica específicamente a NextJS, la guía de seguridad en aplicaciones NextJS cubre las más relevantes con código concreto.

4. Base de datos y Row Level Security

Si usas Supabase, Row Level Security (RLS) es lo que realmente protege tus datos. Sin RLS, cualquier persona con tu anon key (que es pública) puede leer o modificar toda tu base de datos.

Lo que debes verificar

RLS está habilitado en todas las tablas. Esto es lo más crítico. Una tabla sin RLS es una tabla accesible para cualquiera.

sql
-- Verificar qué tablas tienen RLS habilitado
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';

Si rowsecurity es false en cualquier tabla que contenga datos de usuarios, necesitas habilitarlo inmediatamente:

sql
ALTER TABLE nombre_tabla ENABLE ROW LEVEL SECURITY;

Las políticas cubren todas las operaciones. No basta con tener RLS habilitado si no tienes políticas definidas. Sin políticas, RLS bloquea todo por defecto (lo cual es seguro, pero rompe tu aplicación).

sql
-- Ver las políticas activas por tabla
SELECT tablename, policyname, permissive, roles, cmd, qual
FROM pg_policies
WHERE schemaname = 'public';

Las políticas no son demasiado permisivas. Esta es la trampa más común. Poner USING (true) para que "funcione rápido" anula completamente el propósito de RLS.

sql
-- PELIGROSO: cualquiera puede leer todo
CREATE POLICY "open" ON datos FOR SELECT USING (true);
 
-- CORRECTO: solo el dueño ve sus datos
CREATE POLICY "own_data" ON datos FOR SELECT
  USING (auth.uid() = user_id);
sql
-- PELIGROSO: cualquiera puede insertar con cualquier user_id
CREATE POLICY "insert_open" ON datos FOR INSERT
  WITH CHECK (true);
 
-- CORRECTO: solo puedes insertar registros con tu propio user_id
CREATE POLICY "insert_own" ON datos FOR INSERT
  WITH CHECK (auth.uid() = user_id);

La service_role key solo se usa en el servidor. Nunca en componentes de cliente, nunca en el frontend. Esta key bypasea RLS completamente.

ts
// MAL: service_role en el cliente
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY! // NUNCA hagas esto
)
 
// BIEN: service_role solo en Server Components o API Routes
import { createClient } from '@supabase/supabase-js'
 
const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // Sin NEXT_PUBLIC_
)

Para una guía completa de cómo integrar Supabase con NextJS incluyendo la configuración de RLS paso a paso, revisa la guía de Supabase con NextJS.

Verifica tus políticas antes de deploy

Si tienes dudas sobre si tus políticas de RLS son correctas, el verificador de RLS de datahogo analiza tus políticas SQL y detecta configuraciones inseguras como USING(true), tablas sin políticas o políticas que no cubren todas las operaciones. Funciona sin registro.

5. Dependencias y código

Las dependencias son código de terceros que corre con los mismos permisos que tu aplicación. Una vulnerabilidad en una dependencia es una vulnerabilidad en tu aplicación.

Auditoría de dependencias

bash
# Verificar vulnerabilidades conocidas en tus dependencias
npm audit
 
# Si usas pnpm
pnpm audit
 
# Ver paquetes desactualizados
npm outdated

Si npm audit reporta vulnerabilidades críticas o altas, resuélvelas antes de deployar. Algunas se arreglan con:

bash
# Intentar resolver automáticamente
npm audit fix
 
# Si hay breaking changes necesarios
npm audit fix --force
# Cuidado: --force puede actualizar major versions

Verificación de código

Además de las dependencias, revisa tu propio código:

No hay console.log con datos sensibles. Es común dejar logs de debug que imprimen tokens, passwords o datos de usuario.

bash
# Buscar console.log sospechosos
grep -rn "console.log" --include="*.ts" --include="*.tsx" src/ | grep -i "token\|password\|secret\|key\|auth"

No hay comentarios TODO con credenciales. Suena absurdo, pero pasa:

bash
# Buscar TODOs que mencionen credenciales
grep -rn "TODO\|FIXME\|HACK" --include="*.ts" --include="*.tsx" src/ | grep -i "password\|key\|token\|secret"

No hay archivos de respaldo o temporales en el build. Archivos como .env.bak, database.sql, dump.sql no deberían estar en tu directorio de deploy.

bash
# Verificar archivos que no deberían existir en el proyecto
find . -name "*.bak" -o -name "*.dump" -o -name "*.sql" -o -name "*.log" | grep -v node_modules
npm audit no detecta todo

npm audit solo encuentra vulnerabilidades reportadas en el registro de npm. No detecta secrets en tu código, configuraciones inseguras o problemas lógicos. Es un paso necesario pero no suficiente.

6. Score general de seguridad

Después de revisar cada sección individual, vale la pena dar un paso atrás y evaluar tu postura de seguridad general. Es fácil pasar todos los checks individuales y aun así tener una configuración débil por la combinación de factores.

Pregúntate:

  • ¿Si alguien obtiene acceso a tu repositorio, cuánto daño puede hacer?
  • ¿Si un token se filtra, cuánto tiempo tardarías en detectarlo?
  • ¿Tienes un plan para rotar credenciales si se comprometen?
  • ¿Tus errores de producción muestran stack traces completos al usuario?

Si tu aplicación ya está en producción, revisa los logs de las últimas semanas. Busca patrones de requests sospechosos: intentos de acceso a rutas de admin, requests a /.env, brute force en endpoints de login.

Para una evaluación rápida y numérica, el score de seguridad de datahogo consolida los resultados de headers, .env, OWASP y RLS en un solo número. No reemplaza una auditoría manual, pero te da una referencia rápida de dónde estás.

El checklist completo

Esta es la tabla consolidada. Imprímela, cópiala a Notion, o agrégala como issue en tu repo. Revísala antes de cada deploy a producción.

CategoríaVerificaciónEstado
Variables de entorno.env está en .gitignore[ ]
Variables de entornoSin secrets hardcodeados en el código[ ]
Variables de entornoNEXT_PUBLIC_ solo en variables realmente públicas[ ]
Variables de entorno/.env devuelve 404 en producción[ ]
Variables de entorno/.git/HEAD devuelve 404 en producción[ ]
HeadersStrict-Transport-Security configurado[ ]
HeadersContent-Security-Policy configurado[ ]
HeadersX-Frame-Options configurado[ ]
HeadersX-Content-Type-Options: nosniff configurado[ ]
HeadersReferrer-Policy configurado[ ]
HeadersPermissions-Policy configurado[ ]
OWASPPermisos verificados en cada API Route[ ]
OWASPQueries parametrizadas o usando ORM[ ]
OWASPInput validado con Zod u otra librería[ ]
OWASPRate limiting en endpoints sensibles[ ]
OWASPMensajes de error genéricos en producción[ ]
Base de datosRLS habilitado en todas las tablas[ ]
Base de datosPolíticas cubren SELECT, INSERT, UPDATE, DELETE[ ]
Base de datosSin USING(true) en políticas de producción[ ]
Base de datosservice_role key solo en el servidor[ ]
Dependenciasnpm audit sin vulnerabilidades críticas[ ]
DependenciasSin paquetes mayores desactualizados[ ]
CódigoSin console.log con datos sensibles[ ]
CódigoSin TODOs con credenciales[ ]
CódigoSin archivos temporales o de respaldo[ ]
GeneralHTTPS activo en todas las rutas[ ]
GeneralCookies con flags Secure, HttpOnly, SameSite[ ]
GeneralTokens de sesión con expiración configurada[ ]
Si fallas en cualquier item de variables de entorno, detente

No importa qué tan bien estén el resto de las categorías. Si tus variables de entorno están expuestas o hay secrets en el código, tu aplicación no está lista para producción. Arregla eso primero.

Herramientas gratuitas para verificar

No necesitas revisar cada punto de este checklist manualmente. Para el código y las dependencias, npm audit y grep son suficientes. Para el resto, hay herramientas online que automatizan la verificación.

Verifica cada punto de este checklist

datahogo tiene herramientas gratuitas para verificar los puntos clave de este checklist. Todas sin registro:

Si tu aplicación usa NextJS y Vercel, la guía de deploy a Vercel con NextJS cubre la configuración de variables de entorno en el contexto específico de deploy.

Preguntas frecuentes

¿Qué debo verificar antes de hacer deploy de mi aplicación?

Lo mínimo: que tus variables de entorno no estén expuestas, que los headers de seguridad estén configurados (CSP, HSTS, X-Frame-Options), que tu base de datos tenga RLS habilitado si usas Supabase, que no tengas secrets en el código, y que tus dependencias no tengan vulnerabilidades conocidas.

Cada uno de estos puntos está cubierto en las secciones de arriba con comandos para verificar y código para implementar.

¿Los headers de seguridad son obligatorios?

No son obligatorios para que tu sitio funcione, pero sin ellos tu aplicación es vulnerable a ataques como clickjacking, XSS y MIME sniffing. Configurarlos toma minutos y la protección que dan es significativa.

La sección 2 de este checklist tiene el código de next.config.ts listo para copiar y pegar. Si quieres entender cada header en detalle, la guía de headers de seguridad los cubre uno por uno.

¿Cómo verifico si mi sitio expone archivos .env?

Puedes hacer un curl a rutas comunes como tu-dominio.com/.env, tu-dominio.com/.env.local y tu-dominio.com/.git/HEAD. Si alguna devuelve contenido en lugar de un 404, tienes un problema serio. También hay escáneres automáticos que verifican múltiples rutas de una vez.

bash
# Verificación rápida
curl -s -o /dev/null -w "%{http_code}" https://tu-sitio.com/.env

¿Qué es OWASP Top 10 y por qué importa?

OWASP Top 10 es una lista de las 10 categorías de vulnerabilidades más críticas en aplicaciones web, mantenida por la Open Web Application Security Project. Incluye inyección SQL, autenticación rota, XSS y más. Es el estándar de la industria para evaluar la seguridad de una aplicación.

La sección 3 de este checklist incluye una autoevaluación rápida por cada categoría con preguntas concretas que puedes responder sobre tu aplicación.

¿Un checklist de seguridad garantiza que mi app es segura?

No. Un checklist cubre los errores más comunes y las configuraciones básicas, pero la seguridad es un proceso continuo. Sirve para asegurarte de que no estás dejando las puertas abiertas antes de ir a producción.

Lo que sí garantiza es que estás cubriendo el mínimo. La mayoría de brechas de seguridad no son ataques sofisticados: son archivos .env expuestos, headers sin configurar y bases de datos sin políticas de acceso. Exactamente lo que este checklist te ayuda a detectar.

#seguridad#deploy#checklist#owasp#headers

Preguntas frecuentes

¿Qué debo verificar antes de hacer deploy de mi aplicación?

Lo mínimo: que tus variables de entorno no estén expuestas, que los headers de seguridad estén configurados (CSP, HSTS, X-Frame-Options), que tu base de datos tenga RLS habilitado si usas Supabase, que no tengas secrets en el código, y que tus dependencias no tengan vulnerabilidades conocidas.

¿Los headers de seguridad son obligatorios?

No son obligatorios para que tu sitio funcione, pero sin ellos tu aplicación es vulnerable a ataques como clickjacking, XSS y MIME sniffing. Configurarlos toma minutos y la protección que dan es significativa.

¿Cómo verifico si mi sitio expone archivos .env?

Puedes hacer un curl a rutas comunes como tu-dominio.com/.env, tu-dominio.com/.env.local y tu-dominio.com/.git/HEAD. Si alguna devuelve contenido en lugar de un 404, tienes un problema serio. También hay escáneres automáticos que verifican múltiples rutas de una vez.

¿Qué es OWASP Top 10 y por qué importa?

OWASP Top 10 es una lista de las 10 categorías de vulnerabilidades más críticas en aplicaciones web, mantenida por la Open Web Application Security Project. Incluye inyección SQL, autenticación rota, XSS y más. Es el estándar de la industria para evaluar la seguridad de una aplicación.

¿Un checklist de seguridad garantiza que mi app es segura?

No. Un checklist cubre los errores más comunes y las configuraciones básicas, pero la seguridad es un proceso continuo. Sirve para asegurarte de que no estás dejando las puertas abiertas antes de ir a producción.