CVE
CVE-2026-12866
CWE
CWE-94
Sistemas afectados
- All versions of the npm package `expr-eval`
- Node.js applications that call `Expression.prototype.toJSFunction()` on attacker-controlled expressions or variables
- Internal tools and backends that let users define formulas, rules, scoring logic, or automation expressions with `expr-eval`
- Developer tooling and CI helpers that precompile untrusted expressions for later reuse
expr-eval es un motor de fórmulas de JavaScript pequeño y ampliamente copiado, por lo que a menudo termina en lugares en los que los desarrolladores dejan de pensar en él como una superficie de “ejecución de código”: reglas de precios, tablas de puntuación, feature gates, condiciones de flujos de trabajo, constructores de aplicaciones de bajo código, ayudantes de CLI y herramientas internas de administración. CVE-2026-12866 importa porque demuestra que la API toJSFunction() de expr-eval no se limita a evaluar expresiones. Emite código fuente JavaScript y entrega ese código al compilador del runtime.
Ese es un límite de confianza fundamentalmente distinto. En cuanto una aplicación permite que una entrada no confiable influya en el cuerpo de la función generada, la fórmula deja de ser datos y se convierte en código.
Paquete y versiones afectadas
El paquete afectado es el paquete de npm:
expr-eval- todas las versiones
Hasta la publicación del aviso del 23 de junio de 2026, los avisos públicos no describen ninguna versión corregida de expr-eval en upstream para este CVE. Eso convierte esto en un problema de “eliminar o aislar la API peligrosa”, no en un simple incremento de versión semántica.
Los entornos más relevantes son:
- backends de Node.js que compilan fórmulas de usuario para reutilizarlas
- herramientas internas de administración que permiten a los operadores definir reglas o expresiones de puntuación
- motores de flujo de trabajo que almacenan expresiones en una base de datos y luego las convierten en funciones invocables
- scripts de ayuda de CLI o CI que convierten fórmulas suministradas por configuración en JavaScript reutilizable
El uso exclusivo en navegador es de menor riesgo que el uso en el lado del servidor con Node.js, porque la prueba de concepto pública más sólida depende de globales de Node como process.mainModule.require. El riesgo en el lado del servidor es el que los equipos de seguridad de aplicaciones deben priorizar.
Causa raíz en el código fuente del paquete
La ruta de código vulnerable es corta y explícita. Expression.prototype.toJSFunction() construye código fuente JavaScript con expressionToString() y luego lo compila con new Function():
Expression.prototype.toJSFunction = function (param, variables) {
var expr = this;
var f = new Function(
param,
'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' +
expressionToString(this.simplify(variables).tokens, true) +
'; }'
);
return function () {
return f.apply(expr, arguments);
};
};
Tres propiedades de esa implementación crean el fallo del límite de seguridad:
this.simplify(variables)incorpora datos suministrados por el llamador al flujo de tokens antes de generar el código.expressionToString(..., true)emite texto de código fuente JavaScript, no un bytecode en sandbox ni un objeto AST.new Function(...)compila la cadena generada dentro del proceso actual de Node.js.
Eso significa que la aplicación solo está segura si tanto la expresión como los valores que llegan a la simplificación son lo bastante confiables para convertirse en código fuente.
Por qué toJSFunction() es peligrosa incluso cuando la expresión “parece matemática”
La prueba de concepto pública publicada en el issue original utiliza un objeto malicioso con una implementación personalizada de toString(). En las versiones actuales de Node.js, el mismo fallo del límite se puede seguir demostrando con una pequeña adaptación que llega a módulos integrados mediante process.getBuiltinModule():
const { Parser } = require('expr-eval');
const parser = new Parser();
const expr = parser.parse('[cmd]');
const payload = {
toString: () => `
(function(){
const cp = process.getBuiltinModule('child_process');
return cp.execSync('whoami').toString().trim();
})()
`
};
const fn = expr.toJSFunction('', { cmd: payload });
console.log(fn());
En una validación local con Node.js 22.14.0, ese payload devolvió correctamente el nombre de la cuenta de usuario actual. La divulgación anterior usaba process.mainModule.require(...); las variantes modernas de Node cambian el gadget exacto, pero no el límite vulnerable. El problema central sigue siendo que la entrada controlada por el atacante se convierte en JavaScript ejecutable dentro del proceso de la aplicación.
La lección importante no es el payload exacto de whoami. La lección importante es el flujo de datos:
fórmula o variables controladas por el atacante
-> simplify(variables)
-> expressionToString(..., true)
-> new Function(...)
-> el JavaScript se ejecuta dentro del proceso de Node.js
Si ese proceso contiene credenciales de nube, tokens de CI, secretos de aplicación o alcance de red de producción, el motor de fórmulas se ha convertido en un límite de ejecución remota de código.
Patrones de exposición realistas
La condición vulnerable es más estrecha que “cualquier app con expr-eval instalado”, pero sigue siendo común:
- productos orientados al cliente que permiten crear campos calculados o fórmulas de puntuación
- sistemas empresariales internos donde analistas u operadores pueden guardar expresiones en una base de datos
- productos de flujo de trabajo que compilan una regla una vez y la invocan repetidamente por rendimiento
- herramientas para desarrolladores que cargan fórmulas desde archivos de configuración o ecosistemas de plugins
- plataformas SaaS multi-tenant donde la expresión de un cliente se ejecuta en un servicio Node.js compartido
El patrón de diseño de mayor riesgo es “tomar una cadena de una parte menos confiable, llamar a parse() y luego llamar a toJSFunction() para que pueda ejecutarse más rápido después”. Esa optimización cambia el modelo de amenaza de la evaluación de expresiones a la generación de código fuente.
Alcance y detección
Empieza por averiguar si el paquete está presente en absoluto:
npm ls expr-eval
pnpm why expr-eval
yarn why expr-eval
Luego busca en tu base de código la API peligrosa:
rg -n 'toJSFunction\\(' src app lib packages scripts
rg -n 'expr-eval' package.json package-lock.json pnpm-lock.yaml yarn.lock
La pregunta de revisión de código más importante no es “¿usamos expr-eval?”. Es:
¿Puede un atacante influir en la cadena de la expresión o en el objeto de variables
que luego llega a Expression.prototype.toJSFunction()?
Revisa detenidamente estas fuentes de entrada:
- cuerpos de solicitudes HTTP y parámetros de consulta
- fórmulas almacenadas cargadas desde una base de datos
- reglas de automatización controladas por YAML, JSON o
.env - entradas de plugins y configuraciones subidas por clientes
- jobs en segundo plano que reproducen expresiones guardadas contra datos privilegiados
Si ya sabes que se compilaron expresiones no confiables en Node.js, trata el host como si hubiera ejecutado código del atacante. Revisa los secretos a nivel de proceso, el acceso de red saliente y la actividad del sistema de archivos durante la ventana de exposición.
Remediación cuando no existe un parche
Como los avisos públicos no listan una versión corregida en upstream, la remediación debe centrarse en cambios de diseño:
- Elimina
toJSFunction()de cualquier ruta que maneje entradas no confiables. - Deja de aceptar valores no primitivos en los mapas de variables pasados a la compilación de fórmulas.
- Si la ejecución de fórmulas es un requisito estricto, muévela a un worker dedicado en sandbox con privilegios mínimos y sin secretos ambientales.
- Prefiere motores que no dependan de
new Function()para fórmulas no confiables. - Trata las fórmulas almacenadas como contenido ejecutable: versiónalas, revísalas y aplica límites de confianza antes de ejecutarlas.
La mitigación conservadora a corto plazo más segura es simple:
expresión o variables no confiables
-> no llamar a toJSFunction()
Si tu producto no puede evitar de inmediato las fórmulas dinámicas, al menos separa el motor de fórmulas del proceso de aplicación que contiene credenciales de producción, claves de firma o tokens de despliegue.
Guía de respuesta a incidentes
Si tu aplicación expuso toJSFunction() a entradas controladas por el atacante:
- Asume que el JavaScript se ejecutó con los mismos privilegios que el proceso de Node.js.
- Rota los secretos accesibles desde ese proceso, incluyendo credenciales de nube, contraseñas de base de datos, tokens de CI y claves de API.
- Revisa los registros en busca de creación o edición de fórmulas, o cadenas inusuales similares a payloads cerca de campos de expresión.
- Inspecciona las conexiones salientes y la telemetría de ejecución de shell de los hosts afectados.
- Audita cualquier fórmula persistida antes de volver a habilitar su ejecución.
Para los defensores, la conclusión clave es arquitectónica: un compilador de fórmulas que llega a new Function() forma parte de la superficie de ejecución de código de tu aplicación. En expr-eval, toJSFunction() cruza esa línea de forma explícita, y CVE-2026-12866 confirma que los equipos deben tratarla en consecuencia.
De la investigación a la remediación
Comprueba si este patrón existe en tu código
Convierte esta investigación en un flujo de remediación. Analiza dependencias y manifiestos para detectar riesgos similares en la cadena de suministro y prioriza las correcciones con contexto de alcanzabilidad.