Codificación Segura para Desarrolladores de JavaScript
JavaScript se ejecuta en cualquier lugar donde haya un navegador, lo que lo convierte en una de las superficies más atacadas en el desarrollo de software. La mayoría de las vulnerabilidades no provienen de exploits exóticos, sino de patrones predecibles en el código cotidiano. Esta guía cubre las prácticas de codificación segura en JavaScript que más importan cuando tu código se ejecuta directamente en el navegador.
Puntos Clave
- El XSS basado en DOM es una de las vulnerabilidades de JavaScript más comunes: evita pasar datos no confiables a sumideros como
innerHTML,eval()odocument.write() - Usa
textContenten lugar deinnerHTMLpara datos proporcionados por el usuario, y sanitiza con DOMPurify cuando realmente necesites renderizar HTML - Nunca uses
eval(),Function()osetTimeout/setIntervalbasados en cadenas con entrada dinámica - Implementa Content Security Policy (CSP) con nonces o hashes y combínalo con Trusted Types para defensa en profundidad
- Evita almacenar tokens de autenticación o secretos en
localStorageosessionStorage: prefiere cookiesHttpOnlypara identificadores de sesión - Siempre valida
event.original manejar eventospostMessage - Audita tu árbol de dependencias regularmente y usa Subresource Integrity para scripts cargados desde CDN
XSS Basado en DOM: Una de las Vulnerabilidades de JavaScript Más Comunes
Prevenir XSS en JavaScript comienza con entender dónde se origina realmente. El XSS basado en DOM ocurre cuando tu código lee desde una fuente controlada por el atacante—como location.hash, document.referrer o URLSearchParams—y la escribe en la página de forma insegura.
La regla fundamental: nunca pases datos no confiables a un sumidero que interprete HTML o ejecute código.
Sumideros inseguros que debes evitar con datos dinámicos:
// ❌ Todos estos pueden ejecutar scripts inyectados
element.innerHTML = userInput
element.outerHTML = userInput
document.write(userInput)
eval(userInput)
setTimeout(userInput, 0) // solo en forma de cadena
new Function(userInput)()
Alternativas seguras:
// ✅ Trata el contenido como texto, nunca como marcado
element.textContent = userInput
element.innerText = userInput
Cuando realmente necesites renderizar HTML—por ejemplo, un editor de texto enriquecido—sanitiza primero con DOMPurify:
// ✅ Renderizado seguro de texto enriquecido
element.innerHTML = DOMPurify.sanitize(userInput)
La misma lógica se aplica a setAttribute. Los atributos dinámicos como href, src y manejadores de eventos (onclick, onload) pueden ejecutar JavaScript. Adhiérete a atributos estáticos y no ejecutables cuando trabajes con valores proporcionados por el usuario.
La Ejecución Dinámica de Código Siempre Es Insegura
eval(), Function() y setTimeout/setInterval basados en cadenas no son solo malas prácticas—son vectores directos de inyección de código. No hay forma segura de usarlos con entrada no confiable.
Si estás analizando datos, usa JSON.parse(). Si necesitas comportamiento dinámico, usa estructuras de datos y lógica explícita en lugar de generación de código en tiempo de ejecución.
Content Security Policy como Capa de Defensa
Content Security Policy (CSP) limita qué scripts pueden ejecutarse y desde dónde pueden cargarse. Un CSP estricto que use nonces o hashes—en lugar de 'unsafe-inline'—reduce significativamente el radio de explosión de cualquier XSS que se filtre.
Combina CSP con Trusted Types en navegadores compatibles para hacer cumplir escrituras seguras del DOM a nivel de API. El soporte de navegadores para Trusted Types continúa expandiéndose y puede rastrearse en webstatus.dev.
Discover how at OpenReplay.com.
APIs de Navegador Seguras: Cookies y Almacenamiento del Lado del Cliente
Las cookies que contienen identificadores de sesión siempre deben llevar HttpOnly (bloquea el acceso de JavaScript), Secure (solo HTTPS) y SameSite=Strict o Lax (ayuda a mitigar CSRF). Configurarlas del lado del servidor es más confiable que hacerlo desde JavaScript.
localStorage y sessionStorage son accesibles para cualquier script en la página. Evita almacenar tokens de autenticación, secretos de sesión o datos sensibles del usuario allí—una vulnerabilidad XSS expone inmediatamente todo lo que hay en el almacenamiento.
Mensajería entre Orígenes con postMessage
postMessage es útil pero fácil de usar incorrectamente. Siempre valida el origin de los mensajes entrantes antes de actuar sobre sus datos:
window.addEventListener('message', (event) => {
// ✅ Siempre valida el origen antes de procesar
if (event.origin !== 'https://trusted-origin.com') return
handleMessage(event.data)
})
Al enviar mensajes, evita usar '*' como targetOrigin a menos que realmente no tengas un destino fijo. En el lado receptor, siempre valida event.origin para asegurar que el mensaje provino de un sitio confiable. Más detalles sobre el uso seguro se cubren en la documentación de postMessage.
Seguridad de la Cadena de Suministro de JavaScript
La seguridad de la cadena de suministro de JavaScript es una preocupación creciente. Un solo paquete comprometido o malicioso puede afectar a miles de aplicaciones. Pasos prácticos:
- Ejecuta
npm audito usa Snyk para detectar vulnerabilidades conocidas en las dependencias - Confirma tu archivo de bloqueo (
package-lock.jsonoyarn.lock) y trata los cambios inesperados como una señal de alerta - Usa hashes de Subresource Integrity (SRI) para cualquier script cargado desde un CDN
- Audita nuevos paquetes antes de agregarlos—verifica el número de descargas, actividad de mantenimiento y si el nombre del paquete podría ser un typosquat
Referencia Rápida: Patrones Seguros vs. Inseguros
| Inseguro | Alternativa Segura |
|---|---|
innerHTML = userInput | textContent = userInput |
eval(str) | JSON.parse(str) |
setTimeout(str, n) | setTimeout(fn, n) |
Token en localStorage | Cookie HttpOnly |
message sin verificar origen | Validar event.origin primero |
Conclusión
La mayoría de las vulnerabilidades de JavaScript siguen el mismo patrón: datos no confiables llegan a una API poderosa sin validación. Desarrolla el hábito de preguntar “¿de dónde viene este valor y qué puede hacer esta API con él?” Esa pregunta, aplicada consistentemente, detecta la mayoría de los problemas antes de que se publiquen.
Preguntas Frecuentes
No siempre, pero es inseguro siempre que le pases datos que se originen de entrada del usuario o cualquier fuente externa. Si debes renderizar HTML dinámico, sanitízalo primero con una biblioteca como DOMPurify. Para contenido de texto plano, usa textContent en su lugar, que nunca interpreta marcado ni ejecuta scripts.
localStorage es accesible para cualquier JavaScript que se ejecute en la página. Si un atacante explota una vulnerabilidad XSS, puede leer todo lo que hay en el almacenamiento, incluidos tus tokens. Las cookies HttpOnly son una opción más segura porque JavaScript no puede acceder a ellas en absoluto, lo que limita el daño de ataques del lado del cliente.
El XSS reflejado implica una carga maliciosa enviada al servidor y devuelta en la respuesta. El XSS basado en DOM nunca llega al servidor. En su lugar, JavaScript del lado del cliente lee desde una fuente controlada por el atacante como el fragmento de URL y lo escribe en la página de forma insegura. Ambos son peligrosos, pero el XSS basado en DOM es más difícil de detectar del lado del servidor.
CSP le indica al navegador qué fuentes de scripts están permitidas para ejecutarse. Una política estricta que use nonces o hashes bloquea la ejecución de scripts en línea y scripts externos no autorizados. Incluso si un atacante inyecta marcado malicioso en la página, el navegador se niega a ejecutarlo porque no coincide con la política.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.