Por qué la seguridad es responsabilidad de todos
La seguridad web no es un asunto especializado que alguien más maneja. Cada formulario, cada comentario renderizado, cada cookie y cada endpoint de una API es una oportunidad para cometer un error que exponga a tus usuarios. El coste de un bug de seguridad es inusualmente alto: pérdida de datos, ruptura de la confianza y riesgos legales.
La buena noticia es que la mayoría de los ataques siguen un número reducido de patrones y la mayoría de las defensas están bien documentadas. Esta guía cubre lo esencial: cross-site scripting, cross-site request forgery, CORS, autenticación segura y los headers del navegador que hacen cumplir tus reglas.
La regla de oro
Nunca confíes en el cliente. Cualquier dato que provenga del navegador puede ser falsificado: valores de formularios, headers, campos ocultos, precios, IDs de usuario y el estado de JavaScript. La validación en el lado del cliente es para mejorar la experiencia de usuario; el servidor debe validar y autorizar todo nuevamente.
Este único principio es la base de casi cualquier defensa. Si asumes que la entrada de datos es hostil y que los permisos deben verificarse en el servidor, la mayoría de las vulnerabilidades desaparecen antes siquiera de ser escritas.
Cross-site scripting (XSS)
El XSS es la vulnerabilidad web grave más común. Ocurre cuando datos controlados por un atacante son tratados como código. Existen tres tipos principales: almacenado (guardado en el servidor y servido a otros usuarios), reflejado (devuelto en una respuesta) y basado en el DOM (introducido por JavaScript en el lado del cliente).
La defensa principal es la codificación de salida (output encoding): codifica los datos según el contexto donde aparezcan. En el DOM, utiliza textContent en lugar de innerHTML.
// safe.js
const el = document.createElement("p");
el.textContent = userComment; // escaped
// If you truly need HTML, sanitise it
import DOMPurify from "dompurify";
el.innerHTML = DOMPurify.sanitize(userHtml);
En el servidor, utiliza motores de plantillas que escapen los datos por defecto y nunca construyas SQL o HTML mediante concatenación de strings. Para las bases de datos, utiliza siempre consultas parametrizadas o un ORM para que la entrada nunca pueda convertirse en SQL.
Content Security Policy
Una Content Security Policy es un encabezado de respuesta que le indica al navegador qué fuentes puede utilizar una página. Una política estricta convierte muchos errores de XSS en simples errores inofensivos en la consola.
Content-Security-Policy:
default-src 'self';
script-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
Comienza con Content-Security-Policy-Report-Only para recopilar violaciones sin romper el sitio y, posteriormente, aplícala. Evita unsafe-inline y unsafe-eval siempre que sea posible, y utiliza nonces o hashes para los scripts inline que no puedas eliminar.
Cross-site request forgery (CSRF)
El CSRF engaña al navegador para que envíe una solicitud autenticada utilizando las cookies de la víctima. Si tu sitio utiliza sesiones basadas en cookies y un endpoint que cambia el estado acepta una solicitud simple, la página de un atacante puede activarlo.
Defensas, por capas:
- Cookies SameSite.
SameSite=LaxoStrictevita que las cookies se envíen en solicitudes cross-site en los navegadores modernos. - Tokens Anti-CSRF. Un token por sesión incluido en los formularios y verificado en el servidor.
- Verificar el origen. Rechazar las solicitudes que cambien el estado cuyo encabezado
Originno sea tu sitio. - Nunca mutar en GET. Utiliza POST, PUT, PATCH o DELETE para realizar cambios.
CORS
CORS es una regla del navegador sobre qué orígenes pueden leer las respuestas de tu API. Por defecto, una página no puede leer una respuesta de un origen distinto a menos que el servidor lo permita mediante Access-Control-Allow-Origin.
Hay dos puntos que suelen malinterpretarse:
- CORS protege el navegador del usuario, no tu servidor. Otros servidores pueden llamar a tu API libremente; solo la autorización en el lado del servidor puede detenerlos.
- Un
Access-Control-Allow-Origin: *permisivo combinado con credenciales no está permitido, y reflejar orígenes arbitrarios es peligroso.
Configura CORS explícitamente para los orígenes en los que confíes, y mantén la autenticación y autorización en el servidor.
Autenticación segura
La autenticación es el área donde los errores resultan más costosos.
- Hashea las contraseñas con bcrypt, scrypt o Argon2; nunca uses texto plano ni cifrado reversible.
- Utiliza cookies HttpOnly, Secure y SameSite para las sesiones, de modo que JavaScript no pueda leer el token.
- Prioriza los tokens de corta duración con refresh y rotalos periódicamente.
- Añade autenticación de múltiples factores para cuentas sensibles.
- Implementa rate-limit en el login y bloquea el acceso tras fallos repetidos.
- Nunca pongas secretos en el código del cliente: cualquier cosa que se envíe al navegador es pública.
La comparación anterior muestra el balance entre el uso de cookies frente a localStorage. Los tokens localStorage son convenientes pero pueden ser leídos por cualquier script, por lo que un solo XSS puede robar la sesión. Las cookies HttpOnly eliminan ese riesgo.
Otros aspectos esenciales
- HTTPS en todas partes, con HSTS para prevenir ataques de degradación (downgrade attacks).
- Valida y codifica la entrada en el servidor para cada campo.
- Aplica el principio de menor privilegio a los usuarios de la base de datos, API keys y roles de la nube.
- Configura cabeceras de seguridad: CSP, HSTS,
X-Content-Type-Options: nosniff,Referrer-Policyyframe-ancestors. - Mantén las dependencias actualizadas y audítalas regularmente.
- No reveles detalles en los mensajes de error o stack traces en producción.
- Registra y monitorea los eventos de autenticación y los fallos de permisos.
Mejores prácticas
- Trata todas las entradas del cliente como no confiables y valídalas en el servidor.
- Codifica la salida según su contexto; utiliza
textContenty sanitiza cualquier HTML. - Usa consultas parametrizadas para todo acceso a la base de datos.
- Almacena las sesiones en cookies HttpOnly, Secure y SameSite.
- Añade una Content Security Policy y comienza con el modo report-only.
- Usa SameSite junto con tokens anti-CSRF para solicitudes que modifiquen el estado.
- Mantén los secretos en el servidor y rotalos periódicamente.
Errores comunes
- Usar
innerHTMLcon datos de usuario. - Colocar tokens de sesión en
localStorage. - Confiar en la validación del lado del cliente o en campos ocultos de formularios.
- Construir SQL mediante concatenación de strings.
- Asumir que CORS es un mecanismo de control de acceso.
- Incluir API keys en el código del front-end.
Próximos pasos
La seguridad es una práctica, no una lista de tareas que se termina. Fundaméntala en el protocolo HTTP, aplícala en el servidor Node.js y gestiona los secretos de forma segura al desplegar con Docker. Lee el OWASP Top 10 y luego audita de principio a fin un formulario de tu propia aplicación; casi siempre encontrarás algo que valga la pena corregir.