Neutron, nuestro motor de IA, obtuvo un 97.1% en el benchmark CyberGym de UC Berkeley. Más información

Análisis a fondo

Análisis a fondo

RCE en Flowise explotada por el framework Greeter (CVE-2024-58351)

Flowise anterior a 2.1.4 permite que overrideConfig sustituya el JavaScript de un nodo Custom JS Function; Greeter lo explotó y 2.1.4 lo corrige.

Resumen

  • CVE-2024-58351 permite que una petición de predicción sustituya el JavaScript de un nodo Custom JS Function de Flowise, y el servidor ejecuta ese código. El fallo es la falta de una lista de permitidos en el campo overrideConfig de la petición.
  • Todas las versiones de Flowise anteriores a 2.1.4 están afectadas. Era contó 4,372 instancias expuestas en internet, y 703 de las 3,277 cuya versión pudo leer no tenían autenticación.
  • VulnCheck puntúa el fallo como CVSS v3.1 9.8 Critical. Greeter lo explotó en honeypots de julio a octubre de 2026, y el KEV de VulnCheck lo incluye. El de CISA no lo incluye a 8 de noviembre de 2026.
  • Actualice a 2.1.4 o posterior, preferiblemente a 3.1.4, que también incluye el endurecimiento de 2026 de la misma ruta de código. Consulte «¿Cómo lo corrijo?».

¿Estoy afectado?

Está afectado si ejecuta una versión de Flowise anterior a 2.1.4 y usuarios no confiables pueden alcanzar su API de predicción. Deben cumplirse todas estas condiciones:

  • Ejecuta Flowise anterior a 2.1.4, desde el paquete npm flowise, una imagen Docker oficial o un producto que lo incluya.
  • El endpoint de predicción POST /api/v1/prediction/<chatflow-id> es accesible para usuarios no confiables, directamente o a través de un widget de chat integrado.
  • Un chatflow al que esos usuarios pueden llegar contiene un nodo Custom JS Function, o pueden crear uno porque la autenticación a nivel de aplicación está desactivada.

La autenticación a nivel de aplicación en las versiones 1.x y 2.0.x afectadas es el par FLOWISE_USERNAME y FLOWISE_PASSWORD. Está desactivada cuando ambas variables no están definidas. Los honeypots de Era ejecutaban Flowise 1.8.2 sin esas dos variables, y los ataques registrados usaron un chatflow con un nodo Custom JS Function.

El caso sin autenticación es habitual en la población expuesta. El censo de Era encontró 4,372 instancias de Flowise en internet y leyó una versión en 3,277. De ellas, 703 no tenían autenticación, y 46 de las 703 coincidían con la configuración vulnerable que había recreado.

Gráfico de barras de 4,372 instancias de Flowise observadas, 3,277 con una versión legible, 703 sin autenticación y 46 que coinciden con la configuración vulnerable
Figura 1: El parque de Flowise expuesto que observó Era, y el subconjunto sin autenticación que coincide con este fallo. Fuente: Era Security, 3 de noviembre de 2026

Rama Versiones afectadas Versión corregida
1.x todas las versiones 2.1.4 (2024-11-13)
2.0.x todas las versiones 2.1.4 (2024-11-13)
2.1.x 2.1.0 a 2.1.3 2.1.4 (2024-11-13)
2.1.4 y posteriores no afectadas por CVE-2024-58351 ejecute 3.1.4 o posterior para el endurecimiento de 2026

Compruebe la versión del paquete en cada instalación, incluidos los contenedores y las copias incluidas en otro producto:

# Global npm install
npm ls -g flowise

# Project install
npm ls flowise

# Docker: read the version inside the running container
docker exec <container> cat /usr/local/lib/node_modules/flowise/package.json | grep '"version"'

# Running instance
curl -s http://localhost:3000/api/v1/version

El endpoint de versión lee el package.json más cercano del servidor, así que indica el paquete que el proceso ejecuta realmente. La etiqueta de una imagen de contenedor no basta cuando la imagen se construyó con un paquete más antiguo.

¿Cómo lo corrijo?

Actualice a 2.1.4 o posterior, y use la versión actual 3.1.4 porque incluye dos commits de endurecimiento posteriores en la misma función. Después confirme que la sustitución de configuración está desactivada en cada chatflow.

  1. Actualice el paquete. Para una instalación global, ejecute npm install -g flowise@3.1.4. Para una instalación de proyecto, ejecute npm install flowise@3.1.4. Para Docker, descargue la imagen actual de flowiseai/flowise y recree el contenedor conservando su volumen de datos.
  2. Abra cada chatflow y revise Ajustes, luego Configuración y luego la pestaña Security. La sustitución de configuración está desactivada por defecto después de 2.1.4. Active solo los parámetros de nodo que deban poder sustituirse, porque la lista de permitidos se guarda por chatflow y por tipo de nodo.
  3. Coloque autenticación delante de la instancia. Defina FLOWISE_USERNAME y FLOWISE_PASSWORD en las ramas afectadas, o cree la cuenta de administrador en 3.x, y limite la API de predicción a las redes que la necesiten.
  4. Si no puede actualizar todavía, bloquee POST /api/v1/prediction para clientes no confiables en el proxy inverso. Desactive los chatflows públicos y retire los nodos Custom JS Function de cualquier flujo al que esos clientes puedan llegar. Estas medidas eliminan los puntos de entrada; no corrigen el código.

La solución temporal tiene límites. La autenticación por sí sola no elimina el fallo. Cualquier cuenta que pueda crear un chatflow puede añadir un nodo Custom JS Function. En las ramas 1.x y 2.0.x afectadas, la autenticación de aplicación es una única cuenta de administrador. Por tanto, una contraseña de administrador filtrada basta, y por eso la actualización es la corrección y el bloqueo en el proxy es temporal.

¿Cuál es la causa raíz?

La API de predicción trata un campo de la petición como configuración de confianza, y esa configuración puede sustituir código ejecutable. Los chatflows de Flowise son grafos de nodos, y el nodo Custom JS Function tiene una entrada llamada javascriptFunction cuyo valor es código JavaScript. Antes de 2.1.4, el objeto overrideConfig del cuerpo de la petición se aplicaba a todas las entradas de nodo sin lista de permitidos. Este código está citado de packages/server/src/utils/index.ts en Flowise 2.1.3:

export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: ICommonObject) => {
    const types = 'inputs'

    const getParamValues = (inputsObj: ICommonObject) => {
        for (const config in overrideConfig) {
            // If overrideConfig[key] is object
            if (overrideConfig[config] && typeof overrideConfig[config] === 'object') {
                const nodeIds = Object.keys(overrideConfig[config])
                if (nodeIds.includes(flowNodeData.id)) {
                    inputsObj[config] = overrideConfig[config][flowNodeData.id]
                    continue
                } else if (nodeIds.some((nodeId) => nodeId.includes(flowNodeData.name))) {
                    /*
                     * "systemMessagePrompt": {
                     *   "chatPromptTemplate_0": "You are an assistant" <---- continue for loop if current node is chatPromptTemplate_1
                     * }
                     */
                    continue
                }
            }

            let paramValue = inputsObj[config]
            const overrideConfigValue = overrideConfig[config]
            if (overrideConfigValue) {
                if (typeof overrideConfigValue === 'object') {
                    switch (typeof paramValue) {
                        case 'string':
                            if (paramValue.startsWith('{') && paramValue.endsWith('}')) {
                                try {
                                    paramValue = Object.assign({}, JSON.parse(paramValue), overrideConfigValue)
                                    break
                                } catch (e) {
                                    // ignore
                                }
                            }
                            paramValue = overrideConfigValue
                            break
                        case 'object':
                            paramValue = Object.assign({}, paramValue, overrideConfigValue)
                            break
                        default:
                            paramValue = overrideConfigValue
                            break
                    }
                } else {
                    paramValue = overrideConfigValue
                }
            }
            // Check if boolean
            if (paramValue === 'true') paramValue = true
            else if (paramValue === 'false') paramValue = false
            inputsObj[config] = paramValue
        }
    }

    const inputsObj = flowNodeData[types] ?? {}

    getParamValues(inputsObj)

    return flowNodeData
}

El bucle lee todas las claves que envía el llamador. Cuando la clave es javascriptFunction, la asignación final sustituye el JavaScript del nodo por la cadena del llamador. El flujo se construye desde el chatflow almacenado, la sustitución se aplica a los datos del nodo en memoria y luego el nodo se inicializa con el código del atacante. Nada se escribe de vuelta en el chatflow, y por eso la definición almacenada puede parecer inofensiva.

El nodo Custom JS Function envuelve esa cadena en una función asíncrona y la ejecuta en un NodeVM de vm2, citado del código del nodo en Flowise 1.8.2:

const nodeVMOptions = {
    console: 'inherit',
    sandbox,
    require: {
        external: { modules: deps },
        builtin: builtinDeps
    }
} as any

const vm = new NodeVM(nodeVMOptions)
const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname)

La lista de permitidos integrada cubre módulos como http, net y path, no child_process. El aviso del proveedor incluye la evasión del sandbox como impacto de la funcionalidad. La reconstrucción de Era muestra el payload ejecutando comandos de shell mediante child_process.execSync(). vm2 no es una frontera de seguridad frente a JavaScript hostil. Sus propios mantenedores lo abandonaron en 2023 porque seguían apareciendo evasiones.

¿Cómo se explota?

Un atacante envía el exploit como una petición de predicción normal, así que el chatflow almacenado nunca necesita contener el código malicioso. El cuerpo de la petición lleva una pregunta inocua y un objeto overrideConfig cuya clave javascriptFunction contiene la etapa que se debe ejecutar:

{
  "question": "hello",
  "overrideConfig": {
    "javascriptFunction": "<JavaScript that returns the command output>"
  }
}

Los pasos, tal como Era los reconstruyó a partir del tráfico de los honeypots:

  1. Alcanzar la instancia y, cuando la autenticación de aplicación está desactivada, usar o crear un chatflow que contenga un nodo Custom JS Function. El flujo del framework se llamaba ghsa-5cph-wvm9-45gj-poc.
  2. Enviar POST /api/v1/prediction/<chatflow-id> con el cuerpo anterior, usando hello como pregunta.
  3. El servidor aplica la sustitución a la entrada javascriptFunction del nodo antes de inicializarlo.
  4. El nodo ejecuta el JavaScript del llamador en el sandbox vm2. La etapa escapa del sandbox y alcanza child_process.execSync().
  5. La salida del comando vuelve en la respuesta de predicción. El framework envía el siguiente comando unos 3.5 segundos después, de nuevo como hello, y repite el ciclo.
  6. Las etapas posteriores enumeran el host y leen variables de entorno y archivos de credenciales. También consultan los metadatos cloud, comprueban si existe el socket de Docker y preparan persistencia y payloads solo en memoria.

Era contó 4,972 de estos mensajes durante dos meses y medio, de julio a octubre de 2026, contra siete honeypots de Flowise 1.8.2 en cuatro regiones. La dirección de origen que registró fue el nodo de salida TOR 185.246.188[.]74.

La corrección estaba disponible desde noviembre de 2024, 20 meses antes de que empezara la campaña. La diferencia es la historia: los despliegues que nunca aplicaron la actualización 2.1.4 siguieron siendo explotables durante toda la campaña.

Cronología desde la corrección 2.1.4 en noviembre de 2024 hasta los ataques de Greeter en 2026 y el informe de Era
Figura 2: La corrección se publicó en noviembre de 2024, 20 meses antes de que empezaran los ataques de Greeter. Fuente: Flowise, VulnCheck y Era Security, verificado el 8 de noviembre de 2026

¿Cómo se desarrolla el ataque?

Una sola petición de predicción transporta el JavaScript del atacante, y el servidor lo sustituye en el nodo sin lista de permitidos. El código escapa después del sandbox y ejecuta comandos como el usuario de Flowise.

flowchart LR A["POST /api/v1/prediction
con overrideConfig.javascriptFunction"] --> B["replaceInputsWithConfig aplica
la sustitución a la entrada del nodo
sin lista de permitidos"] B --> C["El nodo Custom JS Function
ejecuta el JavaScript del llamador
dentro de vm2"] C --> D["Evasión del sandbox vm2"] D --> E["child_process.execSync
se ejecuta como el usuario de Flowise"] E --> F["Reconocimiento, .env y claves SSH,
credenciales de metadatos cloud"] F --> G["Persistencia y preparación
de un payload en memoria"]

¿Qué puede hacer un atacante?

Un atacante que alcanza la API de predicción ejecuta comandos como el usuario del proceso de Flowise y puede leer todos los secretos a los que ese proceso llega. Las pruebas de los honeypots de Era muestran:

  • Ejecución de comandos como la cuenta de servicio. En el honeypot de Era el proceso se ejecutaba como root con sudo sin restricciones, lo que es una decisión de despliegue y no una propiedad del fallo.
  • Reconocimiento del host: identidad, núcleo, distribución, procesos, puertos en escucha, rutas, entorno, identificadores de contenedor y capacidades de Linux.
  • Descubrimiento de credenciales: archivos .env, claves privadas SSH y authorized_keys, certificados de cliente TLS y archivos SQL y SQLite. También busca la clave de cifrado de Flowise en /root/.flowise/encryption.key.
  • Robo de credenciales cloud mediante el servicio de metadatos de la instancia, y después uso de lo que la identidad cloud de la carga de trabajo tiene permitido hacer.
  • Uso de un socket de Docker montado, que puede llevar al control del host y de otros contenedores.
  • Persistencia mediante cron y unidades systemd, y preparación de payloads que se ejecutan desde memoria mediante memfd_create().

Las condiciones son las de «¿Estoy afectado?». El impacto depende después del usuario del proceso, los volúmenes montados, el socket de Docker y la identidad cloud asociada a la carga de trabajo.

El informe de Era no publica ninguna compromisión exitosa de un despliegue de Flowise en producción. Las pruebas de explotación vienen de sus honeypots.

¿Por qué funciona la corrección?

La corrección hace que cada sustitución sea opt-in por chatflow y por parámetro de nodo. Una petición ya no puede sustituir javascriptFunction salvo que el operador lo haya activado. Flowise 2.1.4 añade getAPIOverrideConfig(), que lee la configuración de API del chatflow:

export const getAPIOverrideConfig = (chatflow: IChatFlow) => {
    try {
        const apiConfig = chatflow.apiConfig ? JSON.parse(chatflow.apiConfig) : {}
        const nodeOverrides = apiConfig.overrideConfig && apiConfig.overrideConfig.nodes ? apiConfig.overrideConfig.nodes : {}
        const variableOverrides = apiConfig.overrideConfig && apiConfig.overrideConfig.variables ? apiConfig.overrideConfig.variables : []
        const apiOverrideStatus = apiConfig.overrideConfig && apiConfig.overrideConfig.status ? apiConfig.overrideConfig.status : false

        return { nodeOverrides, variableOverrides, apiOverrideStatus }
    } catch (error) {
        return { nodeOverrides: {}, variableOverrides: [], apiOverrideStatus: false }
    }
}

replaceInputsWithConfig() ahora recibe la lista de permitidos por nodo y la comprueba antes de escribir cualquier entrada:

const isParameterEnabled = (nodeType: string, paramName: string): boolean => {
    if (!nodeOverrides[nodeType]) return false
    const parameter = nodeOverrides[nodeType].find((param: any) => param.name === paramName)
    return parameter?.enabled ?? false
}

// ...
if (!isParameterEnabled(flowNodeData.label, config)) {
    continue
}

Una sustitución necesita ahora dos autorizaciones: overrideConfig.status debe ser true en el chatflow, y el tipo de nodo debe listar el parámetro como activado. El valor por defecto es false, y javascriptFunction no está en la lista salvo que un operador lo añada. La misma protección se aplica en las rutas de predicción, grafo de agentes y upsert.

La primera lista de permitidos no cerró el tema. El commit c8282c97fb (2026-02-06) retiró una excepción FILE-STORAGE:: que dejaba que un valor se saltara la comprobación de activación. También añadió NODE_OPTIONS a las variables de entorno que la herramienta MCP rechaza. El commit 23b997ee5e (2026-05-07) dejó de propagar el objeto overrideConfig sin filtrar a los datos del flujo, lo que dejaba una segunda ruta para claves controladas por el llamador. Flowise 3.1.4, publicado el 2026-07-29, contiene ambos, y por eso es la versión que debe ejecutar.

¿Cómo compruebo si estuve comprometido?

Busque los marcadores del framework en los registros de peticiones de Flowise y en el host; la campaña deja cadenas distintivas aunque sus mensajes de chat solo contengan hello. Era publicó estos indicadores:

  • Peticiones de predicción cuyo cuerpo JSON contiene overrideConfig y javascriptFunction.
  • Un chatflow llamado ghsa-5cph-wvm9-45gj-poc.
  • Mensajes de chat que contienen VP_DOCKER, VP_MEMFD, VP_W_OK o VP_INIT_OK, respuestas de baliza que coinciden con vp-alive-[0-9a-f]{8}, e informes de salida que coinciden con __VP_EXIT_[0-9]+.
  • Nombres de proceso polld-keeper-{8-hex} y varrun-watcher-{8-hex}.
  • Un archivo cron en /etc/cron.d/syslog-cronrun-{8-hex} o una unidad systemd en /etc/systemd/system/taskd-sysrun-{8-hex}.service.
  • El hash de configuración 07f8464533b3446e918feff1ea14c4ae, compartido por los dos componentes de persistencia.
  • Conexiones desde el nodo de salida TOR 185.246.188[.]74.

Ejecute estas comprobaciones en el host de Flowise y en cualquier host cuyo socket de Docker sea accesible desde el contenedor:

# Flowise request logs (Docker example; adjust the container name)
docker logs <container> 2>&1 | grep -Ei 'overrideConfig|javascriptFunction|ghsa-5cph-wvm9-45gj'

# Persistence artifacts
sudo find /etc/cron.d /etc/systemd/system -maxdepth 1 \( -name 'syslog-cronrun-*' -o -name 'taskd-sysrun-*.service' \)

# Framework processes
ps -eo pid,user,args | grep -E 'polld-keeper-|varrun-watcher-'

Un hallazgo es una pista, no una prueba. Flowise usa overrideConfig en su documentación y sus SDK, así que una petición que menciona el campo puede ser legítima. La combinación del campo con javascriptFunction, o con el nombre del chatflow y los marcadores VP_, es la señal más fuerte. Las pruebas de Era vienen de honeypots; no se publican indicadores de una víctima de producción.

Cronología

  • 2024-11-13: Se publica Flowise 2.1.4 con la lista de permitidos de sustitución por chatflow.
  • 2024-11-21: Se publica el aviso de Flowise GHSA-5cph-wvm9-45gj, con severidad High y una puntuación CVSS v4.0 de 8.6.
  • 2026-02-06: El commit c8282c97fb retira una evasión FILE-STORAGE:: de la lista de permitidos.
  • 2026-05-07: El commit 23b997ee5e deja de propagar el objeto overrideConfig sin filtrar a los datos del flujo.
  • 2026-06-20: VulnCheck publica CVE-2024-58351 con una puntuación CVSS v3.1 de 9.8.
  • 2026-11-03: Era publica su análisis del framework Greeter, cuyos honeypots registraron 4,972 peticiones de predicción de julio a octubre de 2026.
  • 2026-11-08: Se publica este artículo.

Preguntas frecuentes

¿Basta con Flowise 2.1.4?

Sí para CVE-2024-58351, porque 2.1.4 añade la lista de permitidos que el fallo evita. Ejecute 3.1.4 o posterior de todos modos. Dos commits de 2026 cerraron evasiones de la primera lista y retiraron una segunda ruta para claves controladas por el llamador.

¿La autenticación detiene el ataque?

No por sí sola. La autenticación elimina el caso sin autenticación, pero cualquier cuenta que pueda crear un chatflow puede añadir un nodo Custom JS Function y enviar la sustitución. En las ramas 1.x y 2.0.x afectadas la autenticación de aplicación es una única cuenta de administrador, así que una contraseña filtrada basta.

¿Hace falta un nodo Custom JS Function en el chatflow?

Sí. La sustitución reemplaza el valor de una entrada, y javascriptFunction es la entrada del nodo Custom JS Function. Un atacante sin autenticación puede crear un flujo con ese nodo; el flujo observado en el honeypot de Era se llamaba ghsa-5cph-wvm9-45gj-poc.

¿Se explotó contra organizaciones reales?

Era observó el framework Greeter contra sus propios honeypots, no contra víctimas identificadas. También encontró 4,372 instancias de Flowise expuestas en internet y 46 instancias sin autenticación que coincidían con la configuración vulnerable que había recreado. Son pruebas de exposición, no de compromiso.

¿Por qué Flowise puntúa el problema como High y VulnCheck como Critical?

El aviso de Flowise está clasificado como High, y su vector CVSS v4.0 usa PR:H, que supone que el atacante ya tiene privilegios altos, con una puntuación de 8.6. VulnCheck, la autoridad de numeración CVE, usa PR:N y lo puntúa 9.8 en CVSS v3.1 y 9.3 en CVSS v4.0. La ruta de explotación de este artículo solo necesita acceso de red cuando la autenticación de aplicación está desactivada, lo que coincide con el vector de VulnCheck.

¿Qué hago si no puedo actualizar esta semana?

Bloquee POST /api/v1/prediction para clientes no confiables en el proxy inverso. Desactive los chatflows públicos y retire los nodos Custom JS Function de cualquier flujo al que esos clientes puedan llegar. Después planifique la actualización y rote las credenciales que el proceso de Flowise puede leer si el host estuvo expuesto.

Encuentre las instancias de Flowise expuestas con Ostorlab

El descubrimiento de superficie de ataque de Ostorlab inventaría los servicios expuestos en internet de su organización, para encontrar los hosts expuestos que ejecutan Flowise y priorizar la actualización.

Referencias

  • Aviso de Flowise GHSA-5cph-wvm9-45gj, publicado el 2024-11-21, con la lista de impactos de overrideConfig y la corrección 2.1.4: https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-5cph-wvm9-45gj (verificado el 8 de noviembre de 2026)
  • Registro de CVE-2024-58351, publicado el 2026-06-20, con las puntuaciones CVSS v3.1 9.8 y v4.0 9.3 y el bloque SSVC de CISA ADP: https://www.cve.org/CVERecord?id=CVE-2024-58351 (verificado el 8 de noviembre de 2026)
  • Aviso de VulnCheck, «Flowise - Remote Code Execution via overrideConfig Parameter», publicado el 2026-06-20: https://www.vulncheck.com/advisories/flowise-remote-code-execution-via-overrideconfig-parameter (verificado el 8 de noviembre de 2026)
  • Commit de corrección de Flowise 2.1.4, «Enable/disable variables in override configuration», 2024-11-13: https://github.com/FlowiseAI/Flowise/commit/537aa51ef8 (verificado el 8 de noviembre de 2026)
  • Paquete npm de Flowise, versión 2.1.4 publicada el 2024-11-13 y versión actual 3.1.4 publicada el 2026-07-29: https://www.npmjs.com/package/flowise (verificado el 8 de noviembre de 2026)
  • Commit de Flowise c8282c97fb, «Fix Paramater Override Bypass», 2026-02-06: https://github.com/FlowiseAI/Flowise/commit/c8282c97fb (verificado el 8 de noviembre de 2026)
  • Commit de Flowise 23b997ee5e, «remove ungated overrideConfig spread», 2026-05-07: https://github.com/FlowiseAI/Flowise/commit/23b997ee5e (verificado el 8 de noviembre de 2026)
  • Código del nodo Custom JS Function de Flowise 1.8.2, con el envoltorio NodeVM: https://github.com/FlowiseAI/Flowise/blob/5e4d640ed7/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts (verificado el 8 de noviembre de 2026)
  • Era Security, «You Had Me at Hello: Inside The Greeter, A Chinese-Language Framework Targeting AI Infrastructure», 2026-11-03, con las etapas del ataque, las cifras de exposición y los indicadores: https://era.security/blog/greeter-flowise-ai-infrastructure-attack (verificado el 8 de noviembre de 2026)
  • Documentación de Flowise, «Prediction», con el endpoint, el campo overrideConfig y la sustitución de configuración desactivada por defecto: https://docs.flowiseai.com/using-flowise/prediction (verificado el 8 de noviembre de 2026)
  • Documentación de Flowise, autenticación «Application», con las variables FLOWISE_USERNAME y FLOWISE_PASSWORD: https://docs.flowiseai.com/configuration/authorization/app-level (verificado el 8 de noviembre de 2026)
  • Catálogo Known Exploited Vulnerabilities de CISA, comprobado para CVE-2024-58351: https://www.cisa.gov/known-exploited-vulnerabilities-catalog (verificado el 8 de noviembre de 2026)