Cada semana vemos el mismo patrón: un sitio sale a producción con un formulario público, una clave de API pegada directo en el código, y cero validación del lado del servidor. Nada de esto es exótico — son los mismos 20 puntos, una y otra vez.
Lo que casi siempre falta
No es un problema de presupuesto ni de que "no dio tiempo". Es que nadie se sentó a revisar la lista completa antes de decir "listo, ya está en línea". Por eso la convertimos en un checklist fijo — la misma que ahora corremos en cada oficina antes de dar cualquier web por lanzada.
"Ocultar una clave API no es opcional — es el primer punto de la lista, y el más ignorado."
Si tu sitio es una landing estática sin backend, el bloque de "Autenticación y acceso a datos" todavía no aplica — pero repasa igual el resto. En cuanto sumas un formulario con base de datos, un login o un webhook público, la lista completa entra en juego. Aquí están los 20, con la razón concreta por la que cada uno importa.
Secretos y claves
- Oculta claves API — nunca hardcodeadas en el frontend ni en el repo. Cualquiera puede abrir las devtools del navegador y leer el código fuente; una clave de Stripe o de OpenAI ahí queda expuesta a cualquiera, y factura a nombre tuyo.
- Elimina secretos de Git — revisa el historial, rota cualquier clave que se haya filtrado. Borrar el archivo no basta: el historial de commits la guarda para siempre a menos que reescribas el historial o rotes la clave. Hay bots que escanean GitHub buscando exactamente esto.
- Clave pública vs. privada de la base de datos — solo la key "anon/public" va en el cliente (ej. Supabase/Firebase); la "service_role"/secreta nunca sale del backend. Confundir estas dos es el error #1 que vemos, y deja la base de datos completa editable por cualquier visitante.
Autenticación y acceso a datos
- Row-Level Security (RLS) activo en la base de datos, no solo checks en el frontend. Un "solo el dueño puede ver esto" en React no protege nada — cualquiera puede llamar la API directo. RLS lo hace cumplir en la base de datos misma.
- Cifrado de datos sensibles — HTTPS en tránsito, cifrado en reposo cuando aplique. Sin esto, los datos de tus clientes viajan o se guardan legibles; un backup robado o una red pública comprometen todo.
- Autenticación forzada server-side en cada endpoint/ruta protegida, no solo ocultar el botón. Ocultar un botón de "admin" en el frontend no bloquea nada — cualquiera puede llamar esa ruta directo con la URL.
- Acceso a registros restringido por dueño/tenant — un usuario no debe poder pedir el ID de otro. Cambiar un número en la URL (
?id=124→125) no debería mostrarte los datos de otra persona; es de los bugs más comunes y más caros en apps con varios usuarios. - Bloquea la manipulación de campos (mass assignment) — el cliente no debe poder setear role/precio/admin vía payload. Si tu API acepta cualquier campo del body, un usuario puede mandar
"role":"admin"y el backend lo acepta sin preguntar. - Cookies con flags HttpOnly, Secure, SameSite. Sin estos flags, un script inyectado (XSS) puede robar la sesión de cualquier usuario leyendo su cookie directamente.
- Contraseñas hasheadas (bcrypt/argon2), nunca en texto plano ni cifradas reversibles. Si tu base de datos se filtra algún día — pasa más de lo que crees — contraseñas en texto plano exponen también las cuentas de tus usuarios en otros sitios, porque casi nadie usa una contraseña distinta por sitio.
- Límite de intentos de login (rate-limit / bloqueo tras varios fallos). Sin esto, un bot puede probar miles de contraseñas por minuto contra un solo usuario hasta acertar.
"Ocultar una clave API no es opcional — es el primer punto de la lista, y el más ignorado."
Entradas y contenido
- Consultas parametrizadas siempre (nunca concatenar SQL). Meter un input de usuario directo en una query es la puerta de entrada del ataque de inyección SQL más viejo — y todavía más común — de internet.
- Valida toda entrada en el servidor, no confíes solo en la validación del frontend. La validación de un formulario en JavaScript se salta con una llamada directa a la API; el servidor es el único lugar donde de verdad cuenta.
- Escapa contenido generado por el usuario antes de renderizarlo (previene XSS). Un comentario o nombre de usuario con código adentro, si no se escapa, se ejecuta en el navegador de cualquiera que lo vea.
- Restringe subida de archivos (tipo, tamaño, sin ejecutables, servidos sin permiso de ejecución). Un formulario que acepta "cualquier archivo" puede terminar sirviendo un script disfrazado de imagen desde tu propio dominio.
Red y abuso
- Protección anti-bots en formularios públicos (captcha/rate-limit). Sin esto, un formulario de contacto se vuelve un generador de spam gratis, y tu bandeja de leads reales se llena de basura.
- Rate-limit / cuotas en cualquier API pública. Una API sin límite es una invitación a que alguien la sature — a propósito o no — y tumbe el servicio para todos los demás.
- Cabeceras de seguridad: CSP, X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, Referrer-Policy. Cada una cierra una puerta distinta — desde que tu sitio se pueda incrustar en un iframe ajeno (clickjacking) hasta que un navegador viejo malinterprete un archivo.
- Fuerza HTTPS en todo (redirect + HSTS). Sin HSTS, la primera visita de un usuario puede pasar por HTTP sin cifrar antes del redirect — una ventana real para interceptar la conexión.
Esta es la misma lista que corremos internamente antes de dar cualquier sitio por lanzado — punto por punto, sin saltarnos ninguno aunque "probablemente no aplique".
