Validación y Sanitización de Inputs en JavaScript
Aprende a validar y sanitizar las entradas de usuario para prevenir ataques de inyección, XSS, SQL Injection y otras vulnerabilidades de seguridad.
TL;DR - Resumen rápido
- typeof value !== 'string' y Number.isFinite(value) verifican tipos, !Array.isArray() evita confundir con null
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) valida emails, new URL(url) valida URLs con try/catch
- escapeHtml() convierte < > & " ' a entidades HTML (< > & " ') contra XSS
- escapeSql() reemplaza ' por '' pero SIEMPRE usa prepared statements ($1 o ?) en backend como protección real
- validateStringLength() limita maxLength para prevenir DoS, /(.)(\1){100,}/ detecta patrones de ataque
Introducción a la Validación de Inputs
La validación y sanitización de inputs es una de las prácticas de seguridad más críticas en desarrollo web. Cada entrada de usuario representa un vector potencial de ataque si no se maneja correctamente. Los atacantes pueden explotar inputs mal validados para inyectar código malicioso, acceder a bases de datos, o ejecutar acciones no autorizadas.
La validación verifica que los datos cumplan con ciertos criterios (tipo, longitud, formato), mientras que la sanitización limpia los datos eliminando o escapando caracteres peligrosos. Ambas prácticas son complementarias y necesarias para una seguridad robusta.
Regla de oro: Nunca confíes en los datos del cliente
La validación en el frontend mejora la experiencia del usuario, pero nunca debes confiar en ella para seguridad. Un atacante puede fácilmente bypassar la validación del cliente usando herramientas de desarrollo o curl. SIEMPRE valida y sanitiza en el servidor, sin importar cuánta validación tengas en el frontend.
Validación de Tipos de Datos
La primera línea de defensa es verificar que los datos sean del tipo esperado. JavaScript es un lenguaje débilmente tipado, lo que significa que los datos pueden cambiar de tipo implícitamente. Esto puede causar comportamientos inesperados si no validas explícitamente los tipos antes de procesar los datos.
Validación de Tipos Básicos
JavaScript proporciona operadores y métodos para verificar el tipo de datos. Es importante usar los métodos correctos según el tipo de dato que esperas recibir.
typeof value === 'string' valida strings, pero typeof value !== 'number' || !Number.isFinite(value) valida números descartando NaN e Infinity. Array.isArray(value) verifica arrays porque typeof [] devuelve 'object'. Para objetos, typeof value === 'object' && value !== null && !Array.isArray(value) evita que null y arrays pasen la validación. value instanceof Date && !isNaN(value.getTime()) valida fechas excluyendo new Date('invalid').
Diferencia entre typeof e instanceof
typeof devuelve el tipo primitivo de un valor, mientras que instanceof verifica si un objeto es una instancia de una clase específica. Usa typeof para strings, numbers, booleans, undefined, y symbols. Usa instanceof para arrays, fechas, objetos personalizados y otras instancias de clases.
Validación de Números y Rangos
Validar números correctamente es crucial para prevenir ataques como desbordamiento de enteros o valores extremos que pueden causar errores en tu aplicación. Además de verificar el tipo, debes validar que los números estén dentro de rangos aceptables.
Number.isFinite(value) retorna true solo para números finitos, false para NaN, Infinity, -Infinity y strings. Number.isInteger(value) verifica enteros. validateRange(edad, 0, 120) lanza error si edad < 0 || edad > 120. Para validación completa usa validateNumberComplete(value, {min: 0, max: 100, integer: true, positive: false}) que combina todas las verificaciones en una sola función configurablefundamental.
Validación de Formatos y Patrones
Además del tipo, muchos datos deben cumplir con formatos específicos: emails, URLs, fechas, números de teléfono, etc. Las expresiones regulares (regex) son la herramienta más poderosa para validar patrones complejos en JavaScript.
Validación de Emails y URLs
Validar emails y URLs correctamente es más complejo de lo que parece. Un email válido debe seguir ciertas reglas de formato, pero las regex simples pueden rechazar emails válidos o aceptar emails inválidos. Lo mismo aplica para URLs.
/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) valida formato básico de email (usuario@dominio.ext). [^\\s@]+ significa "uno o más caracteres que no sean espacios ni @". Para URLs, /^https?:\\/\\/.+$/.test(url) valida protocolo HTTP/HTTPS, pero new URL(url) en try/catch valida estructura completa. validatePassword() usa !/[A-Z]/.test(password) para verificar mayúsculas, !/[a-z]/ para minúsculas, !/[0-9]/ para números.
Advertencia: La validación regex no garantiza que un email sea real
La validación de formato solo verifica que el email tenga la estructura correcta. No verifica que el email exista o que el usuario tenga acceso a él. Para confirmar la validez de un email, siempre envía un email de verificación con un token de confirmación.
Validación de Longitud y Tamaño
Validar la longitud de strings y el tamaño de arrays es esencial para prevenir ataques de denegación de servicio (DoS) por memoria y para asegurar que los datos encajen en tus esquemas de base de datos.
value.trim().length valida longitud después de eliminar espacios. if (length < minLength || length > maxLength) lanza error con límites específicos. arr.length valida tamaño de arrays. /(.)(\\1){100,}/ detecta más de 100 caracteres repetidos consecutivos (patrón de ataque DoS). SECURITY_LIMITS define límites estándar: username {min: 3, max: 20}, password {min: 8, max: 128}, description {min: 10, max: 5000}.
Sanitización de Inputs
La sanitización es el proceso de limpiar los datos eliminando o escapando caracteres peligrosos. A diferencia de la validación, que rechaza datos inválidos, la sanitización transforma datos potencialmente peligrosos en datos seguros.
Sanitización contra XSS
Para prevenir ataques XSS, debes escapar caracteres especiales HTML cuando renderices datos no confiables en el DOM. Esto convierte caracteres como los símbolos de menor y mayor que, el ampersand, comillas dobles y simples en sus entidades HTML correspondientes.
escapeHtml() hace .replace(/</g, '<').replace(/>/g, '>') para convertir <script> a <script> evitando ejecución. También escapa .replace(/&/g, '&'), .replace(/"/g, '"'), .replace(/'/g, '''). Para objetos, sanitizeObject() itera recursivamente con for (const key in obj) aplicando escapeHtml() a strings. container.innerHTML = escapeHtml(content) renderiza seguro porque el navegador muestra el código como texto plano en lugar de ejecutarlo.
Mejor práctica: Usa DOMPurify para sanitización HTML compleja
Para sanitización de HTML más compleja, usa DOMPurify, una librería especializada que elimina scripts maliciosos mientras preserva el HTML seguro. DOMPurify es más robusto que funciones manuales y se mantiene actualizado contra nuevas técnicas de ataque.
Sanitización para SQL Injection
Aunque la sanitización de SQL es principalmente responsabilidad del backend, el frontend puede ayudar validando y sanitizando datos antes de enviarlos. Sin embargo, la mejor protección contra SQL Injection es usar prepared statements o parameterized queries en el backend.
escapeSql() hace .replace(/'/g, "''") convirtiendo admin' -- en admin'' -- (comillas dobles escapadas). Sin embargo, nunca concatenes strings para construir consultas SQL. Una consulta vulnerable sería concatenar el username directamente en el string SQL. En su lugar, siempre usa prepared statements: query = 'SELECT * FROM users WHERE username = $1', values = [username] (PostgreSQL) o query = 'SELECT * FROM users WHERE username = ?', values = [username] (MySQL). La sanitización frontend es solo defensa adicional, los prepared statements son la protección real porque separan lógica SQL de datos.
Importante: La sanitización NO reemplaza prepared statements
Nunca confíes solo en la sanitización para prevenir SQL Injection. Siempre usa prepared statements o parameterized queries en tu backend. La sanitización en el frontend es solo una capa adicional de defensa y puede ser bypassada fácilmente por un atacante.
Validación en el Backend
La validación en el frontend mejora la experiencia del usuario, pero la validación en el backend es obligatoria para la seguridad. Un atacante puede fácilmente bypassar la validación del cliente usando herramientas de desarrollo, curl, o scripts personalizados.
validateRegistration(req.body) retorna objeto errors = {}. if (!data.nombre || typeof data.nombre !== 'string') errors.nombre = 'requerido'. if (Object.keys(errors).length > 0) return res.status(400).json({success: false, errors}). validateBody(validationFn) es middleware que ejecuta validación antes del handler: app.post('/api/register', validateBody(validateRegistration), handler). rateLimit({windowMs: 15*60*1000, max: 5}) limita a 5 intentos cada 15 minutos previniendo fuerza bruta.
- Valida todos los inputs en el servidor, sin excepción
- Usa códigos de estado HTTP apropiados (400 para validación fallida)
- Devuelve mensajes de error claros pero sin exponer información sensible
- Implementa rate limiting para prevenir ataques de fuerza bruta
- Registra intentos de validación fallida para detectar patrones de ataque
Resumen: Validación y Sanitización de Inputs
Conceptos principales:
- •typeof value !== 'string', Number.isFinite(value), Array.isArray(value) validan tipos primitivos y objetos
- •/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) valida emails, new URL(url) en try/catch valida URLs
- •escapeHtml() convierte < > & " ' a < > & " ' previniendo XSS en innerHTML
- •validateStringLength(value, min, max) y /(.)(\1){100,}/ previenen ataques DoS por memoria
- •if (Object.keys(errors).length > 0) res.status(400).json({errors}) retorna validación fallida con código HTTP apropiado
Mejores prácticas:
- •Backend: query = 'SELECT * WHERE id = $1', values = [id] usa prepared statements (NO concatenación)
- •validateBody(validationFn) middleware valida antes del handler, rateLimit({max: 5}) previene brute force
- •SECURITY_LIMITS: username {min: 3, max: 20}, password {min: 8, max: 128}, description {max: 5000}
- •validateNumberComplete(value, {min, max, integer, positive}) combina todas las validaciones numéricas
- •DOMPurify.sanitize(html) para HTML complejo, escapeHtml() solo para texto simple en producción