← Blog
·5 min read

10 Vulnerabilidades Comunes en JavaScript

El 45% del código JavaScript tiene al menos una vulnerabilidad. Estas 10 son las más frecuentes — con código inseguro vs. seguro para cada una. Fix incluido.

Rod

Founder & Developer

El 45% del código generado por IA tiene vulnerabilidades documentadas (Veracode, 2025). Y si escribes JavaScript — con IA o sin ella — las vulnerabilidades JavaScript comunes de esta lista son las que aparecen una y otra vez en repos reales. Están en el OWASP Top 10, en los CVEs que se publican cada semana, y en el historial de commits de proyectos que pensabas que estaban bien. Si ya quieres saber cuáles tiene tu repo, escanéalo gratis en 60 segundos.


# Vulnerabilidad Riesgo principal
1 XSS (Cross-Site Scripting) Robo de sesión, ejecución de código en el cliente
2 Inyección SQL Acceso o destrucción de la base de datos
3 eval() con input del usuario Ejecución de código arbitrario en el servidor
4 Secretos hardcodeados API keys expuestas en el repo
5 Dependencias con CVEs Vulnerabilidades en paquetes de npm
6 Prototype Pollution Corrupción del objeto base de toda la app
7 SSRF Acceso a servicios internos del servidor
8 Autenticación rota Tokens robados válidos para siempre
9 Path Traversal Lectura de archivos fuera del directorio permitido
10 ReDoS Denegación de servicio vía regex maliciosa

1. XSS (Cross-Site Scripting)

XSS ocurre cuando insertas HTML sin sanitizar directamente en el DOM. El browser lo ejecuta como código. Un atacante puede inyectar un <script> que roba cookies de sesión o redirige al usuario a un sitio de phishing.

La IA genera innerHTML = userInput porque es el patrón más corto para renderizar contenido dinámico.

// MAL
element.innerHTML = userInput;
 
// BIEN
import DOMPurify from "dompurify";
element.innerHTML = DOMPurify.sanitize(userInput);

DOMPurify elimina los tags y atributos peligrosos antes de que lleguen al DOM. En React, JSX escapa el contenido automáticamente — el riesgo aparece cuando usas dangerouslySetInnerHTML.


2. Inyección SQL — la vulnerabilidad JavaScript más costosa de resolver tarde

El input del usuario termina directamente en la query. Un atacante escribe ' OR '1'='1 y obtiene todos los registros. O peor: '; DROP TABLE users; --.

La IA concatena strings en queries porque así aparece en la mayoría de tutoriales de su training data.

// MAL
const result = await db.query(
  `SELECT * FROM users WHERE name = '${req.body.name}'`
);
 
// BIEN
const result = await db.query(
  "SELECT * FROM users WHERE name = $1",
  [req.body.name]
);

Con queries parametrizadas, el input del usuario nunca se interpreta como código SQL. Si usas Prisma, las queries normales son seguras — el riesgo está en $queryRaw.


3. eval() con input del usuario

eval() convierte un string en código JavaScript y lo ejecuta con acceso completo al scope actual. Si ese string viene del usuario, puede hacer literalmente cualquier cosa: leer variables de entorno, hacer requests, borrar archivos.

La IA genera eval(req.body.formula) cuando el prompt pide "evaluar una expresión matemática del usuario".

// MAL
const resultado = eval(req.body.formula);
 
// BIEN
import { Parser } from "expr-eval";
const parser = new Parser();
const resultado = parser.evaluate(req.body.formula);

expr-eval evalúa expresiones matemáticas de forma segura, sin darle acceso al scope de Node. Aplica lo mismo a new Function() y setTimeout("string").


4. Secretos hardcodeados — la vulnerabilidad JavaScript más fácil de evitar

Una API key en el código fuente es una cuenta comprometida esperando el momento. Los bots escanean GitHub en tiempo real buscando patrones como sk-proj- o AKIA. Hay casos documentados de facturas de AWS de $50,000 en un día por una key filtrada.

La IA hardcodea keys porque el prompt decía "integra OpenAI" y el modelo completó el ejemplo más corto que funciona.

// MAL
const openai = new OpenAI({ apiKey: "sk-proj-xxxxxxx" });
 
// BIEN
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

Si ya hiciste commit de un secreto, cambiar el .env no es suficiente — la key sigue en el historial de Git. Rótala en el dashboard del proveedor de inmediato. Puedes verificar si tu archivo .env es accesible públicamente desde tu URL de producción.


5. Dependencias con vulnerabilidades conocidas

Cada npm install es código de terceros. Las versiones que Cursor o Copilot sugieren vienen de su training data — que puede tener meses o años de antigüedad. Para cuando deployas, varios paquetes pueden tener CVEs publicados.

# Revisar qué tiene tu proyecto
npm audit
 
# Arreglar las que tienen fix disponible
npm audit fix

npm audit consulta la base de datos de vulnerabilidades de npm. Data Hogo también consulta OSV de Google en cada escaneo, que incluye CVEs adicionales que npm audit no siempre detecta. Corre npm audit al menos una vez al mes — y antes de cada deploy a producción.


6. Prototype Pollution — la vulnerabilidad JavaScript más difícil de detectar

JavaScript tiene un objeto base: Object.prototype. Todos los objetos de tu app lo heredan. Si alguien puede inyectar propiedades en él — a través de un merge de objetos mal escrito — esas propiedades aparecen en todos los objetos de la aplicación. Eso puede romper checks de autorización, cambiar comportamiento de funciones, y causar crashes difíciles de rastrear.

La IA genera funciones merge sin validar las claves porque el caso de __proto__ no es obvio.

// MAL
function merge(target, source) {
  for (const key in source) {
    target[key] = source[key];
  }
}
 
// BIEN
function merge(target, source) {
  for (const key in source) {
    if (key === "__proto__" || key === "constructor") continue;
    target[key] = source[key];
  }
}

El fix bloquea explícitamente las claves que apuntan al prototype. También considera usar Object.assign({}, ...) o structuredClone() para merges simples.


7. SSRF (Server-Side Request Forgery)

Tu servidor tiene un endpoint que hace fetch() hacia una URL que el usuario provee — para previsualizar links, importar imágenes, o llamar webhooks. Sin validación, un atacante puede pasar http://169.254.169.254/latest/meta-data/ y obtener las credenciales de AWS de tu instancia EC2. Es el OWASP #10 por una razón.

// MAL
app.post("/preview", async (req, res) => {
  const response = await fetch(req.body.url);
  res.json(await response.json());
});
 
// BIEN — bloquear rangos de IP internos
import { URL } from "url";
app.post("/preview", async (req, res) => {
  const parsed = new URL(req.body.url);
  const blocked = ["169.254.", "10.", "172.16.", "192.168.", "127."];
  if (blocked.some((ip) => parsed.hostname.startsWith(ip))) {
    return res.status(400).json({ error: "URL no permitida" });
  }
  const response = await fetch(req.body.url);
  res.json(await response.json());
});

El fix valida que el hostname no apunte a rangos de IP privados antes de hacer el request. Para producción, agrega también resolución de DNS — un hostname puede resolver a una IP privada.


8. Autenticación rota — JWT sin expiración y tokens en localStorage

Dos patrones que la IA genera seguido: JWT sin expiresIn (un token robado es válido para siempre) y tokens guardados en localStorage (accesibles desde cualquier script en la página, incluyendo los inyectados por XSS).

// MAL
const token = jwt.sign({ userId: user.id }, SECRET);
localStorage.setItem("token", token);
 
// BIEN
const token = jwt.sign({ userId: user.id }, SECRET, { expiresIn: "15m" });
res.cookie("token", token, { httpOnly: true, secure: true, sameSite: "strict" });

Las cookies httpOnly no son accesibles desde JavaScript — un script inyectado por XSS no puede leerlas. expiresIn: "15m" limita la ventana de daño si un token es comprometido.


9. Path Traversal

Tu app sirve archivos desde un directorio de uploads. El usuario pasa el nombre del archivo como parámetro. Sin validación, puede pasar ../../etc/passwd y leer archivos del sistema operativo fuera del directorio permitido.

// MAL
app.get("/archivo", (req, res) => {
  const filePath = path.join(__dirname, "uploads", req.query.nombre);
  res.sendFile(filePath);
});
 
// BIEN
app.get("/archivo", (req, res) => {
  const base = path.resolve(__dirname, "uploads");
  const filePath = path.resolve(base, req.query.nombre);
  if (!filePath.startsWith(base)) {
    return res.status(403).send("Acceso prohibido");
  }
  res.sendFile(filePath);
});

path.resolve() normaliza el path completo, eliminando ../. Después verificas que el resultado siga dentro del directorio base. Si no, rechazas el request.


10. ReDoS (Regular Expression Denial of Service)

Ciertos patrones de regex tienen backtracking exponencial. Con un input cuidadosamente construido, una sola request puede dejar tu servidor colgado por segundos — o para siempre. Esto se llama ReDoS y es una vulnerabilidad de JavaScript y Node.js que aparece seguido en código que valida emails, URLs o teléfonos.

// MAL — regex con backtracking potencialmente exponencial
const emailRegex = /^([a-zA-Z0-9]+)+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/;
emailRegex.test(req.body.email);
 
// BIEN — usar una librería probada
import { isEmail } from "validator";
if (!isEmail(req.body.email)) {
  return res.status(400).json({ error: "Email inválido" });
}

La librería validator tiene regex optimizadas para evitar ReDoS. Si necesitas escribir tu propia regex de validación, usa herramientas como safe-regex para verificar que no tenga backtracking peligroso.


¿Tu repo tiene alguna de estas vulnerabilidades JavaScript?

De los repos que hemos escaneado en Data Hogo, los más frecuentes son secretos hardcodeados, dependencias con CVEs, inyección SQL en raw queries, y JWT sin expiración. Las cuatro aparecen seguido en código generado con IA — los riesgos de seguridad del vibe coding son un patrón documentado — y las cuatro son detectables en segundos con un scanner automatizado. Puedes ver el desglose completo de hallazgos por categoría en la guía de ciberseguridad para programadores.

Data Hogo detecta la mayoría de estas automáticamente: secretos con Gitleaks, dependencias con CVEs via OSV, patrones de código inseguro con más de 250 reglas Semgrep, y configuración de headers HTTP. También puedes revisar tu security score gratis sin necesitar conectar tu repo primero.

El plan gratuito incluye 3 escaneos al mes en 1 repositorio público, sin tarjeta de crédito.

Escanea tu repo gratis →


Preguntas frecuentes

¿Cuáles son las vulnerabilidades más comunes en JavaScript?

XSS, inyección SQL, eval() con input del usuario, secretos hardcodeados, dependencias con CVEs, prototype pollution, SSRF, autenticación rota, path traversal y ReDoS. Las más frecuentes en repos construidos con IA son secretos hardcodeados, dependencias desactualizadas e inyección SQL en raw queries.

¿Cómo prevenir XSS en JavaScript?

Nunca insertes HTML del usuario con innerHTML o dangerouslySetInnerHTML sin sanitizar. Usa DOMPurify para limpiar el contenido antes de renderizarlo. En React, evita dangerouslySetInnerHTML siempre que sea posible — React escapa el contenido automáticamente con JSX.

¿Qué es prototype pollution en JavaScript?

Un atacante modifica Object.prototype inyectando claves como __proto__ en un merge de objetos. Todos los objetos de tu app heredan esas propiedades inyectadas, lo que puede romper checks de autorización en toda la aplicación.

¿Es seguro usar eval() en JavaScript?

No para input del usuario. eval() ejecuta código JavaScript con acceso completo al scope donde se llama. Si el input viene del usuario, puede ejecutar cualquier código en tu servidor. Usa librerías como expr-eval para evaluar expresiones de forma segura.

¿Cómo sé si mis dependencias de npm tienen vulnerabilidades?

Corre npm audit en la raíz de tu proyecto. Para arreglar automáticamente las que tienen fix disponible: npm audit fix. Data Hogo también consulta la base de datos OSV de Google para detectar CVEs adicionales que npm audit no siempre incluye.

¿Cómo proteger una app Node.js de inyección SQL?

Usa queries parametrizadas en lugar de concatenar el input del usuario. Con pg: db.query("SELECT * FROM users WHERE email = $1", [req.body.email]). Si usas Prisma, las queries del ORM son seguras — el riesgo está en $queryRaw donde hay que parametrizar manualmente.

javascriptseguridadvulnerabilidadesnode.jsxssinyección sqlprototype pollution