Principales conclusiones
- RequireJS es un proyecto archivado de OpenJS, por lo que ningún responsable del proyecto original se ha comprometido a corregir futuras vulnerabilidades.
- El paso a los módulos ES afecta a las definiciones de los módulos, al comportamiento de carga, a la optimización de la compilación, a las pruebas y a la implementación.
- La vulnerabilidad CVE-2024-38999 se clasifica entre «media» y «crítica» según la fuente, por lo que la accesibilidad es más importante.
- En Magento y Moodle, RequireJS está integrado en la plataforma, por lo que abandonar AMD es un proyecto a nivel de plataforma.
RequireJS contribuyó a que las grandes aplicaciones de navegador se volvieran modulares antes de que existieran los módulos ES nativos. Su desarrollo activo se redujo hace años, y eso no tendría nada de especial si no fuera porque los cargadores AMD siguen estando presentes en gran parte del código front-end empresarial, a menudo en plataformas en las que no te corresponde a ti cambiar el cargador.
En esta entrada se explica qué significa el «fin de vida» para un cargador como RequireJS, cómo gestionar una vulnerabilidad CVE destacada con puntuaciones de gravedad muy dispares y por qué abandonar AMD es un proyecto de arquitectura más que una simple actualización de dependencias.
RequireJS resolvió el problema del JavaScript modular antes de que lo hiciera el navegador
Probablemente, en algún lugar de tu organización haya una interfaz de usuario que todavía se inicie a través de un data-main atributo, y nadie del equipo actual lo ha escrito. Se ha cargado sin problemas durante aproximadamente una década, y ahí está el problema.
RequireJS surgió cuando los navegadores aún no disponían de una forma nativa de dividir el código JavaScript en módulos y cargarlos bajo demanda. Implementó AMD, el formato de definición asíncrona de módulos (Asynchronous Module Definition), para que los grandes proyectos de código pudieran declarar las dependencias de forma explícita y cargarlas en paralelo, en lugar de concatenarlo todo en un único archivo y cruzar los dedos para que se mantuviera el orden. Para los equipos que enviaban cientos de miles de líneas de código al navegador en 2013, esa estructura marcaba la diferencia entre una aplicación fácil de mantener y un caos de variables globales.
Todavía se encuentra en algunos sitios concretos. Una tienda de Magento 2. Una implementación de Moodle. Una aplicación de Backbone o Knockout de la época de 2015. Una herramienta interna de generación de informes conectada con data-main que nadie ha abierto desde que se marchó la persona que lo construyó.
No hay nada averiado, nadie se encarga de su mantenimiento activo y la arquitectura subyacente no permite una solución puntual y concreta. No se puede solucionar una decisión arquitectónica con parches.
¿Se sigue manteniendo RequireJS?
No es algo en lo que se pueda basar un plan de soporte, y la etiqueta es oficial. La Fundación OpenJS incluye a RequireJS en la lista de proyectos archivados, junto con otras bibliotecas que han dejado de recibir soporte, como jQuery Mobile, jQuery UI, Sizzle y el polyfill de eventos de puntero.
El repositorio sigue siendo público y el paquete de npm sigue instalándose, pero el estado «Archivado» no te da motivos para esperar que nadie de la comunidad de desarrolladores responda cuando se publique el próximo aviso.
¿Sabías que...?: RequireJS lanzó la versión 2.3.8 el 30 de noviembre de 2025, más de cinco años después de que OpenJS la incluyera por primera vez en su lista de proyectos en fin de vida útil. Por lo tanto, esa etiqueta no significa que el código esté congelado. Significa que nadie se ha comprometido a mantenerlo, y eso es algo muy diferente.
AMD resolvió el problema de la carga de módulos en el navegador antes de que la plataforma tuviera una solución, y los módulos ES nativos, junto con los empaquetadores modernos, acabaron asumiendo por completo la función para la que se había creado. Incluso se ha pedido públicamente al proyecto que informe de su estado en su propia página principal. Esa página sigue indicando la versión 2.3.7 como la más reciente y no menciona nada sobre la etiqueta «Archivado».
Cómo interpretar una vulnerabilidad CVE con tres niveles de gravedad diferentes
38999 Es el resultado que aparece en los análisis de RequireJS. Se trata de un error de contaminación de prototipos en s.contexts._.configure que afecta a todas las versiones hasta la 2.3.6, y cuya corrección se incluyó en la versión 2.3.7 en julio de 2024.
Ahora fíjate en cómo valoran las bases de datos el mismo error.
| Fuente | CVSS | Severidad |
|---|---|---|
| NVD (NIST) | No se ha asignado puntuación | Sin enriquecer |
| CISA-ADP | 10,0 (v3.1) | Crítica |
| Sostenible | 9.3 (v4.0) | Crítica |
| Aviso de GitHub | 8,9 (v4.0) | Alta |
| Snyk | 6,9 (v4.0) | Medio |
La diferencia parece enorme, pero se debe principalmente a la mecánica de puntuación y no a un desacuerdo real sobre el fallo. La puntuación de 10,0 de CISA-ADP corresponde al CVSS v3.1. La puntuación de 9,3 de Tenable y la de 8,9 de GitHub comparten las mismas métricas básicas de CVSS v4.0, y la puntuación más baja de GitHub se debe a la incorporación de la métrica de amenaza «Madurez del exploit» en el nivel «Prueba de concepto» (E:P). El verdadero valor atípico es la calificación «Medio» de Snyk, que se basa en una evaluación de impacto mucho menor.
La puntuación EPSS de este CVE es de aproximadamente el 0,76 % (0,0076 en la escala de 0 a 1, a fecha de 14 de septiembre de 2026), por lo que el modelo estima que la probabilidad de que se produzca un ataque en los próximos 30 días es inferior al 1 %. Aún así, se sitúa en la mitad de la tabla entre todas las CVE puntuadas (percentil 53), pero ese percentil es una clasificación respecto a otras CVE, no una probabilidad.
Parte del problema radica en cómo se gestionó el registro. El NVD sigue clasificando el CVE-2024-38999 como «Aplazado», por lo que cada base de datos posterior aplicó sus propios criterios de puntuación a un registro que el NVD nunca analizó. RequireJS también recibió un segundo informe de contaminación de prototipos en el mismo periodo. Ese, el CVE-2024-38998, afectaba a la función «config» y fue rechazado en enero de 2025 después de que su CNA concluyera que no se trataba de un problema de seguridad.
Para la clasificación, la puntuación es solo uno de los factores a tener en cuenta, y la accesibilidad merece más peso que la cifra. ¿Pueden los datos controlados por un atacante llegar al objeto que tu aplicación pasa a requirejs.config()? Esa llamada es lo que alimenta s.contexts._.configure. Si la respuesta es «no», el riesgo práctico es bajo, independientemente del color de la insignia.
Atención: una puntuación objeto de controversia no elimina el hallazgo de tu informe. «Creemos que esta puntuación está sobrevalorada» es una opinión, no una corrección.
En la práctica, si el CVE aparece en tu informe y no puedes pasar a una versión corregida, necesitas o bien una versión parcheada o bien una declaración por escrito sobre la vulnerabilidad. Un argumento basado en el CVSS no se considera como ninguna de las dos cosas.
Elegir una alternativa a RequireJS es una decisión de arquitectura
Cambiar RequireJS por algo más reciente parece un cambio en las dependencias. Se trata más bien de decidir cómo carga la aplicación el código en sí.
Hay cuatro destinos posibles, y no son equivalentes. Los módulos ES nativos permiten que el navegador o el entorno de ejecución se encarguen de la carga, y RequireJS desaparece por completo. Los módulos gestionados por un empaquetador pasan el grafo de módulos a webpack, Vite o esbuild, que lo resuelven en el momento de la compilación y generan un resultado optimizado. Los adaptadores incrementales y los shims de interoperabilidad permiten que los módulos AMD y ES coexistan durante un proceso de migración prolongado. Y la carga de scripts sin formato sigue siendo un requisito real en algunos entornos antiguos o restringidos.
Detrás de estas cuatro cuestiones se esconde una pregunta que es fácil pasar por alto: ¿la aplicación sigue necesitando la carga de módulos en tiempo de ejecución, o se trataba de una limitación de la época en la que se desarrolló? Gran parte de la arquitectura de AMD existe simplemente porque los navegadores de 2013 no ofrecían ninguna alternativa.
Además, están las suposiciones incorporadas en el propio código, y ahí es donde fallan las estimaciones. Hay que contar con que las definiciones de módulos con nombre y anónimas se mezclen en el mismo árbol, así como con prefijos de complementos como text! para cargar plantillas y alias definidos en paths, y shim entradas para variables globales que no son de AMD. Es probable que haya dependencias circulares que el cargador haya tolerado, variables globales envueltas como módulos para que parezcan modulares sin serlo, y una configuración del optimizador que genere paquetes específicos para cada implementación.
Consejo de experto: Busca en el código fuente con grep text!, shim, y paths antes de definir el alcance de nada. Esos tres patrones predicen mejor las dificultades de la migración que el simple recuento de archivos, ya que cada uno de ellos refleja un comportamiento en tiempo de ejecución que un empaquetador no puede reproducir por sí solo.
Elige el destino antes de que nadie empiece a programar. La conversión a módulos ES y la reproducción de una estructura de artefactos del optimizador son proyectos distintos con presupuestos diferentes.
Por qué la conversión automatizada solo te lleva hasta cierto punto
Los «codemods» y las transformaciones «jscodeshift» existen de verdad y realmente modifican la sintaxis. En el caso de los módulos estáticos simples, un define([...], function(){}) el bloque se convierte en import frases sin mucho alboroto. El problema es que la sintaxis nunca fue lo más difícil.
Las suposiciones en tiempo de ejecución no se transforman. El orden de carga y los efectos secundarios pueden variar en el momento en que los módulos se resuelven de forma diferente, y una aplicación que dependiera de una secuencia de carga AMD específica podría dejar de funcionar de formas que el análisis estático no detecta. Las pruebas suelen estar vinculadas directamente a la configuración de RequireJS, por lo que fallan por motivos ajenos al código que se está probando. Las implementaciones en producción pueden esperar una disposición optimizada concreta de los artefactos que un nuevo empaquetador simplemente no genera. Y los nombres de módulos dinámicos, aquellos que se construyen a partir de variables en tiempo de ejecución, se resisten por definición al análisis estático.
Luego está la «trampa de la plataforma», que es precisamente donde se encuentra una implementación de Magento o Moodle. En esas plataformas, RequireJS forma parte de la arquitectura front-end. Normalmente se puede ampliar su configuración (los módulos y temas de Magento incluyen sus propios requirejs-config.js (por ejemplo, archivos), pero la sustitución del propio cargador está limitada por lo que admite la versión de la plataforma.
Desde la primera línea: Imagina una tienda online con una instalación de Magento muy personalizada, con unos 30 módulos personalizados de RequireJS y un tema que anula partes de la configuración del cargador de la plataforma. Una actualización de la plataforma puede traer consigo una versión más reciente de RequireJS incluida en el paquete, pero la tienda seguirá arrancando a través de AMD después de ello. Dejar de usar RequireJS en ese caso depende tanto de la hoja de ruta de la plataforma como de la tuya, y ninguna herramienta de codemod lo modifica.
Así que empieza con un ejercicio de recuento antes de hacer cualquier estimación. Cuenta los archivos. Cuenta las entradas de shim. Averigua qué parte de la configuración del cargador es tuya y qué parte pertenece a la plataforma. Esas tres respuestas determinan prácticamente todo lo que viene después, y la última decide si la migración está siquiera bajo tu control.
Una ruta por etapas desde AMD hasta los módulos modernos
Cuando se plantea una migración, llevarla a cabo por etapas suele ser mejor que una reescritura radical casi siempre. El orden que suele funcionar bien en producción empieza por trazar el grafo de módulos para saber qué depende de qué. A partir de ahí, convierte primero los módulos finales y otros límites de bajo riesgo, e introduce capas de compatibilidad de forma deliberada, en lugar de hacerlo por accidente cuando algo falla. Convierte las pruebas al mismo tiempo que los módulos de producción a los que cubren, no después. Mide el tamaño del paquete y el comportamiento de carga tras cada fase, y elimina los shims solo cuando ya no quede ningún consumidor de los mismos.
Cada fase debe dejar la aplicación lista para su distribución. La ventaja del orden «leaf-first» es que un módulo de bajo riesgo puede revertirse sin problemas si cambia el comportamiento de carga, algo que no es posible con un módulo del que dependen muchos otros.
Consejo profesional: siempre que la arquitectura lo permita, ejecuta en paralelo la compilación antigua de RequireJS y la nueva del empaquetador tras un indicador de función durante la conversión, y compara la cascada de red entre ambas antes de eliminar el cargador antiguo. Un cambio en el orden de las solicitudes suele ser el primer indicio de que se ha desplazado un efecto secundario.
Sin embargo, no todas las aplicaciones encajan en este enfoque. La decisión más acertada depende de quién sea el responsable de la compilación y de hasta qué punto esté «congelado» el artefacto. Si se trata de una base de código pequeña y se tiene control total sobre la compilación, conviértela a módulos ES y listo. Cuando la plataforma sea la responsable del cargador, aplica los parches in situ e incluye la actualización de la plataforma en una hoja de ruta con plazos concretos. En el caso de un artefacto «congelado» o de requisitos de aislamiento físico, aplica los parches in situ y documenta la vulnerabilidad para el auditor.
Cuando el cargador actual sigue necesitando protección
A veces, la migración no está prevista para este trimestre, pero el cargador debe seguir funcionando de forma segura mientras tanto. Esto suele ocurrir en portales empresariales de larga duración, consolas administrativas integradas y productos con ventanas de actualización controladas. También ocurre en aplicaciones que se sustituyen en lugar de modernizarse, y en sistemas cuya cobertura de pruebas de regresión aún no permite llevar a cabo una migración del cargador.
Hay que ser realista respecto al límite. Actualizar a la versión 2.3.7 o a la actual 2.3.8 resuelve el CVE-2024-38999, pero eso es lo máximo a lo que te permite llegar el proyecto original. No hay indicios de que vaya a haber una versión 2.4. No hay ningún compromiso de mantenimiento tras el próximo aviso de seguridad, ni se garantiza ningún plazo de respuesta en caso de que se publique uno.
Una visión realista: actualizar a la última versión de RequireJS no equivale a contar con soporte técnico. El hecho de que hoy haya una versión corregida no garantiza que no vaya a surgir una vulnerabilidad el año que viene.
El servicio «Endless Lifecycle Support» (ELS) de TuxCare para RequireJS abarca las versiones 2.1.22 y 2.3.6, que se distribuyen como paquetes npm desde el registro alojado por TuxCare, con documentos SBOM y VEX, así como objetivos de respuesta vinculados a la gravedad según el CVSS. Se trata de una labor de gestión, más que de desarrollo en el proyecto original. El equipo comprueba si cada nuevo CVE afecta a las versiones cubiertas, adapta la corrección a las versiones anteriores y comprueba cada compilación en cuanto a integridad, funcionalidad y regresiones antes de su lanzamiento, de modo que el código existente siga funcionando tal y como lo hace actualmente. Esto último es fundamental para un cargador que no puede permitirse comportarse de forma diferente. Es una parte del soporte más amplio que ofrece TuxCare para bibliotecas al final de su vida útil (EOL), destinado a equipos que se han quedado estancados en componentes que los desarrolladores originales ya han dejado de mantener.
La sostenibilidad de OpenJS una vez finalizada la fase activa de un proyecto
El hecho de que RequireJS sea un proyecto archivado en OpenJS no supone el final de su historia en materia de seguridad. El Programa de Sostenibilidad del Ecosistema de la Fundación existe precisamente para que las versiones archivadas, en fin de vida útil y antiguas puedan recibir servicios de seguridad comerciales, destinándose una parte de los ingresos a la Fundación y a sus proyectos. TuxCare es miembro Gold y proveedor de dicho programa, y sus parches se mantienen en las versiones derivadas. No se convierten en versiones oficiales de RequireJS.
Así que considera RequireJS como parte de la arquitectura que mantiene unida la interfaz de usuario, porque eso es lo que es. Eliminarlo un viernes por la tarde no va a salir bien, y sustituirlo merece algo más que un simple cambio de paquete hecho a toda prisa.
