El 24 de octubre alguien de fuera de Microsoft se leyó el descodificador empaquetado en .NET 9.0.21 y 10.0.13, vio que conservaba el comportamiento previo a la corrección y abrió una incidencia titulada con una pregunta: «¿Afecta CVE-2026-111497 a System.IO.Compression.Brotli en .NET 9 y 10?». Microsoft contestó al día siguiente y el 25 ya había compilaciones corregidas. Un tercero tuvo que leer el código del entorno de ejecución para preguntarle al fabricante si su propio producto estaba roto, y acertó.
El fallo, con el número delante
CVE-2026-111497. Escritura fuera de límites en el decodificador Brotli de Google, el que descomprime el Content-Encoding: br que viaja por media web. Google, como entidad asignadora, le puso un 9,2 sobre 10 en CVSS 4.0, con el vector AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H: por red, sin privilegios, sin interacción del usuario. La clasificación técnica es CWE-787 —escritura fuera de límites— arraigada en CWE-252: un valor de retorno que nadie comprobó.
El aviso de Node.js lo cuenta así: «un cambio de bloque inválido se reportaba como una necesidad temporal de más entrada en lugar de como un error permanente de formato, de modo que el decodificador en streaming seguía añadiendo bytes de entrada a un búfer interno de ocho bytes». Ocho bytes. El decodificador cree que le falta material, pide más, y sigue escribiendo pasado el final. De ahí salen, por este orden: corrupción de memoria, fuga del contenido del proceso, caída y —en las condiciones de montón adecuadas— ejecución de código.
Lo encontró el equipo de seguridad de OpenAI Codex —Khai Tran y Sylvie Mayer— y lo arreglaron en Node Matteo Collina y Antoine du Hamel. Río arriba, el proyecto Brotli lo corrigió en las versiones 1.0.10, 1.1.1 y 1.2.1; afectaba a todo lo que hubiera entre 1.0.3 y 1.2.0. Esa horquilla cubre tres ramas mayores y todo lo que hay entre medias.
Si quieres ver la causa raíz sin intermediarios, el commit de corrección río arriba se llama «Detect and report invalid stream earlier» y lo que hace es revelador: añade un código de error que no existía, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, y cambia tres funciones de cambio de bloque —literal, comando y distancia— para que devuelvan códigos de error en lugar de un simple sí o no. Esa es la historia completa en una línea de diff: «cambio de bloque malformado» no tenía manera de decirse, así que el decodificador decía lo único que sabía decir, que era «dame más datos».
Y ahora el dato que reordena todo lo demás. Ese commit está fechado el 27 de febrero de 2026. Ocho meses antes de que el fallo se hiciera público, la corrección ya estaba en la rama principal de Brotli, a la vista de cualquiera. Lo que se publicó el 21 de octubre no fue el arreglo: fue el aviso. Durante esos ocho meses, que tú tuvieras el fallo o no dependía exclusivamente de cuándo había actualizado su dependencia el proyecto que te lo sirve.
Cinco nombres en cuatro días
El 21 de octubre, cuando Node publicó la corrección, el fallo se llamaba Node2Shell. El 23, Aikido lo bautizó primero Python2Shell y lo rebautizó HTTP2Shell ese mismo día, al ver que no era cosa de un lenguaje sino de cualquiera que descomprimiese una respuesta HTTP. Y el 25 se publicaron dos análisis el mismo día con dos nombres más: JFrog lo llamó Brotli2Shell, subtitulado sin ninguna sutileza «más allá de Node(2Shell) y HTTP(2Shell)», y Wiz lo llamó Broccoli2Shell, recogiendo la propuesta del investigador Juliano Rizzo.
La justificación del último es la frase más limpia que se ha escrito sobre todo esto: el fallo «se llamó primero Node2Shell, pero el defecto está en Brotli mismo y llega bastante más allá de Node.js». Cinco nombres en cuatro días, y lo que mueve cada uno es siempre lo mismo: alguien descubriendo que el radio de alcance era mayor que su modelo mental. Primero se pensó en términos de lenguaje, luego de producto, luego de protocolo. Los dos últimos nombres, publicados el mismo día por dos equipos distintos, nombran por fin la biblioteca.
Tu inventario está escrito en el primer idioma. Tienes una línea para Node, una para .NET, una para tu plataforma de datos. Ninguna para Brotli. Nosotros llevamos el inventario de red de nuestros clientes en NetBox, como fuente única de verdad, y conviene decir lo que eso no resuelve: NetBox sabe qué máquina hay en qué armario, con qué dirección y en qué circuito. No sabe qué bibliotecas van dentro de los binarios que corren encima. Ninguna herramienta de inventario de infraestructura lo sabe, porque no es la pregunta para la que se diseñaron.
La palabra que se repite en todos los avisos: «empaquetado»
La palabra aparece en los cuatro avisos. Node habla del «decodificador Brotli empaquetado con Node.js». La incidencia de Apache Arrow se titula, literalmente, «[C++] El Brotli 1.0.9 empaquetado está afectado por CVE-2026-111497». En el repositorio de .NET, quien abrió la pregunta se refiere al «decodificador Brotli incorporado en .NET 9.0.21 y 10.0.13». Y ClickHouse va un paso más allá: no empaqueta el Brotli de Google, empaqueta una bifurcación propia —con un parche para arquitectura loongarch64— y lo que hizo el 25 de octubre fue actualizar su fork.
El número de Arrow es 1.0.9. No es la última de la rama afectada —esa es la 1.2.0—, ni la penúltima: está varias versiones por detrás, empaquetada dentro del propio proyecto, mientras la biblioteca seguía su camino por fuera. Nadie la instaló, nadie la eligió y nadie la va a ver con un apt. El rastreador de seguridad de Debian, de hecho, lista para este CVE exactamente tres paquetes fuente: brotli, nodejs y python-brotlicffi. Tu gestor de paquetes te hablará de esos tres y te dirá la verdad. También te dirá cero sobre el ejemplar que lleva dentro Arrow, el que lleva .NET y el fork de ClickHouse.
Por eso los investigadores de JFrog —Aviv Engelberg, Eddy Tsalolikhin y Ofri Ouzan— no publicaron solo un análisis: publicaron un escáner, y lo justificaron diciendo que encuentra rutas de descompresión Brotli que los escáneres basados únicamente en versión no ven. Busca llamadas de JavaScript (fetch, Express, body-parser), clientes de Python (Requests, HTTPX, aiohttp, urllib3), referencias al decodificador dentro de binarios ELF y uso por línea de órdenes. Es decir: dejaron de preguntar «¿qué versión tienes instalada?» y pasaron a preguntar «¿qué código tuyo descomprime esto?». No es la primera vez que esa distinción decide el resultado; escribimos sobre ella cuando contamos por qué tu escáner dice cero críticas y el exploit da root.
La petición la haces tú (y por eso tu perímetro no mira ahí)
El reflejo es pensar en el servidor: alguien me manda un cuerpo comprimido con br y me revienta el proceso. Pasa. Envoy e Istio —los proxies que muchos tienen delante de todo— aparecen en las listas de corregidos, aunque con una particularidad que conviene mirar de cerca más abajo. Pero el camino más ancho va en la otra dirección.
Los paquetes Brotli y brotlicffi de PyPI traen el decodificador vulnerable, y requests, urllib3, aiohttp y httpx usan cualquiera de los dos automáticamente para decodificar respuestas con Content-Encoding: br. Automáticamente quiere decir: sin que tú escribas una línea, sin que aparezca en tu código, sin que lo hayas decidido. Tu trabajador en segundo plano llama a una API de un tercero, el tercero responde comprimido, y el descompresor vulnerable se ejecuta dentro de tu proceso, detrás de tu cortafuegos.
El perímetro no te cubre de esto porque el perímetro está dibujado alrededor de una pregunta distinta: quién entra. Aquí nadie entra. Sales tú, y vuelves con algo en las manos. Es la versión moderna de un clásico de la resiliencia. La pieza que te rompe es la que dabas por descontada. El fallo de un decodificador de compresión es inevitable a escala de todo el ecosistema; que eso se convierta en avería depende de si puedes responder, en horas, dónde está tu copia y quién la actualiza.
«¿Está parcheado?» no tiene una respuesta: tiene ocho relojes
Esta es la tabla que importa, con el estado a 26 de octubre de 2026. Lo que cambia de fila a fila son tres cosas: la versión, qué consigue el atacante y si existe algo que instalar.
Las dos filas de los extremos son las que importan. En Node, el 21 de octubre había binario que instalar; cinco días después, en Apache Arrow sigue habiendo una incidencia abierta y un Brotli 1.0.9 dentro. Si tu plataforma de datos lee Parquet a través de Arrow, «actualiza» no es un plan: no hay nada que instalar todavía. Lo que queda ahí es operativo, no de parcheo: no aceptar br en las entradas que controlas y no anunciar Accept-Encoding: br en las salidas que haces, hasta que exista el binario. Es incómodo y es más lento, y es la diferencia entre tener un plan y tener una frase.
Y hay un noveno reloj, que no está en la tabla porque no es de nadie en concreto. Las versiones corregidas de Envoy salieron entre el 6 y el 13 de octubre; las de Istio, el 15 y el 16. Antes de que el CVE fuera público. No reaccionaron rápido: habían actualizado su dependencia de Brotli en algún momento posterior a febrero y se encontraron corregidos sin saberlo. Esa es la otra mitad de la respuesta a «¿nos afecta?», y es la que no controlas: la frecuencia con la que tus proveedores actualizan lo que llevan dentro.
El 9,2 y el «ninguno»: las dos columnas que no se resuelven
Columna uno. 9,2 crítico asignado por Google, red, sin privilegios, sin interacción. Wiz publicó el 25 de octubre que el 91 % de los entornos cloud que ve tiene recursos vulnerables y el 82 % tiene instancias de Node.js expuestas públicamente. E Imperva ejecutó código ajeno dentro de aplicaciones que usaban la imagen oficial de Node.js 24.22.0 para Linux arm64, sin modificar, con Express 5.0.0–5.3.0 y body-parser 2.0.0–2.3.0; las mismas entradas fueron rechazadas por la 24.22.1.
Columna dos. La ficha SSVC de CISA del 23 de octubre registra la explotación como «none» y lo automatizable como «no». Red Hat lo puntúa 8,1 en CVSS 3.1 en vez de 9,2 y escribe que «no hay una prueba de concepto de ejecución remota de código pública y fiable». El propio Node.js lo clasificó como High, no como Critical. Y el exploit de Imperva no está publicado: ellos mismos avisan de que la ejecución les costó varios intentos y de que no probaron todas las compilaciones.
Las dos columnas son verdad y no se resuelven una contra la otra. De lo que depende el resultado es de un dato que no está en ninguna de las dos: cuánto tardas tú en pasar de «hay parche» a «está instalado». Si ese número es de días, la columna dos te da margen de sobra. Si es de meses, la columna dos es irrelevante, porque para cuando aparezca el primer ataque de verdad seguirás donde estás ahora. Ya contamos el caso en el que esa distancia estaba medida: el parche de Redis llevaba 164 días publicado cuando empezaron a entrar.
El censo de cinco preguntas (se hace esta tarde)
Cinco preguntas con respuesta concreta. Nosotros las hacemos en este orden cuando entramos en una plataforma que no conocemos, y si alguna deja en blanco a todo el mundo en la sala, esa es la que hay que trabajar. Empieza por aquí:
- ¿Qué cosas tuyas descomprimen Brotli sin que lo hayas pedido? No «qué tienes instalado»: qué ejecuta el decodificador. Cada trabajador que llama a una API externa, cada proxy, cada proceso que lee Parquet. Si la lista te sale corta a la primera, es que no has mirado dentro de los contenedores.
- De cada copia: ¿es del sistema o va empaquetada? La distinción decide quién la arregla. La del sistema la arregla tu distribución. La empaquetada la arregla el proyecto que la metió ahí, a su ritmo, y tú te enteras leyendo su repositorio.
- Para las que no tienen versión corregida: ¿cuál es la mitigación operativa, escrita? Rechazar
Content-Encoding: bren las entradas que controlas, dejar de anunciarAccept-Encoding: bren las llamadas salientes. Con fecha de revisión, porque es temporal y lo temporal sin fecha se queda. - ¿Quién mira esto dentro de dos semanas? Un aviso que en cinco días tuvo cuatro nombres y ocho estados de parcheo distintos no se cierra con un correo el viernes. Necesita dueño y fecha de revisión. Si la respuesta es «ya lo vio alguien», no hay respuesta.
- ¿Qué haces con lo que está fuera de soporte? Node 18, 20, 23 y 25 no van a recibir este parche nunca. Eso ya no cabe en un ticket de seguridad. Es una decisión de arquitectura, y alguien tiene que tomarla con una fecha delante. Cada CVE que llegue a partir de ahora va a repetirte la misma pregunta.
Cuándo NO hay que correr
Y la parte contraria. Si nada tuyo descomprime Brotli que venga de fuera, esto es una actualización de rutina y no un sábado. Una aplicación interna que solo habla consigo misma, en una red cerrada, sin llamadas salientes a terceros, no está en la primera fila de esto por mucho que su versión de Node aparezca en rojo en un informe. Ponla en el ciclo normal y sigue con tu semana.
El riesgo contrario al pánico es el trato uniforme, y hace más daño del que parece: tratar todos los avisos del mes con la misma urgencia es exactamente cómo se gasta el crédito que vas a necesitar el día que uno de ellos sí se esté explotando. La escala de gravedad no la pone el CVSS, la pone la respuesta a la pregunta uno del censo.
Nosotros operamos infraestructura ajena desde 1996 y el inventario es, de lejos, la parte menos vistosa y más decisiva del trabajo. No se gana con una herramienta: se gana sabiendo qué corre dentro de cada cosa, quién la actualiza y qué se hace cuando el proyecto de río arriba todavía no ha publicado nada. Si tu equipo lleva tres días intentando responder a «¿nos afecta?» y la respuesta sigue siendo un hilo de correo, el problema no es este CVE: es que el censo no existe. Eso es la mitad de lo que hacemos en datos y aplicaciones y en ciberseguridad. Si quieres que lo miremos contigo, hablamos.
Fuentes. Todo lo que sigue se puede comprobar en el origen, y los enlaces van puestos a propósito. El fallo: aviso de Node.js del 21 de octubre de 2026 («Node.js Brotli decoder out-of-bounds write permits remote code execution», severidad High, corregido en 22.23.4, 24.22.1 y 26.12.1), de donde salen la descripción literal del cambio de bloque inválido y el búfer interno de ocho bytes, la vía de acceso por node:zlib y los créditos a OpenAI Codex Security (Khai Tran y Sylvie Mayer) y a mcollina y aduh95. Puntuación CVSS 4.0 de 9,2 asignada por Google como entidad asignadora, vector CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N y clasificación CWE-787 sobre CWE-252, de la ficha del NVD. La corrección río arriba: commit 0ed4268 de google/brotli, «Detect and report invalid stream earlier», fechado el 27 de febrero de 2026, que añade el código de error BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH y propaga códigos de error en las tres funciones de cambio de bloque; versiones 1.0.10, 1.1.1 y 1.2.1 publicadas las tres el 21 de octubre de 2026. Cada producto, en su propio repositorio: dotnet/runtime #136422 (abierta el 24 de octubre; las compilaciones 8.0.33, 9.0.22, 10.0.14 y 11.0.0-rc.3 las da el equipo de seguridad de Microsoft en el hilo el 25); apache/arrow #55166 (abierta el 23 de octubre, abierta todavía al cerrar este artículo); ClickHouse #127419 (fusionada el 25 de octubre, de donde salen el fork propio con parche para loongarch64 y los retroportes anotados a 26.10.3.6, 26.9.26.18, 26.8.35.17 y 26.3.61.13); rastreador de seguridad de Debian, con sus tres paquetes fuente; y el boletín ISTIO-SECURITY-2026-007, que acota el impacto a quien habilita la descompresión Brotli, por ejemplo con un EnvoyFilter. Las versiones corregidas de Envoy (1.36.12, 1.37.8, 1.38.6, 1.39.3, 1.40.0) e Istio (1.29.9, 1.30.6, 1.31.2) son anteriores a la divulgación: salieron entre el 6 y el 16 de octubre y el CVE se añadió al boletín después. El análisis: JFrog Security Research («Brotli2Shell», 25 de octubre, por Aviv Engelberg, Eddy Tsalolikhin y Ofri Ouzan) y su escáner abierto. Precisión importante: JFrog documenta en FFmpeg un fallo de segmento en 0x414141414141 al liberar un puntero derivado del payload, y dice haber reproducido control del puntero de instrucción en «aplicaciones nativas adicionales» que no nombra; atribuirle a FFmpeg el control del puntero sería ir más lejos que la fuente. Las dos columnas: el 91 % y el 82 % son telemetría propia de Wiz del 25 de octubre sobre los entornos que ve, no un censo del parque mundial, y Wiz no acota la población; la frase de que «no hay una prueba de concepto de ejecución remota de código pública y fiable» y el 8,1 en CVSS 3.1 son de la ficha de Red Hat; la demostración y sus límites, de Imperva, que ejecutó código dentro de aplicaciones sobre «la imagen oficial de Node.js 24.22.0 para Linux arm64» y advierte de que le costó varios intentos y de que no probó todas las compilaciones; el exploit no está publicado. La ficha SSVC de CISA del 23 de octubre (explotación «none», automatizable «no»), la cronología de nombres —Python2Shell y HTTP2Shell el 23, por Aikido— y el comportamiento automático de requests, urllib3, aiohttp y httpx al decodificar Content-Encoding: br, del análisis de Phoenix Security. El quinto nombre, Broccoli2Shell, y su justificación («the bug is in Brotli itself and reaches well beyond Node.js»), atribuida a la propuesta del investigador Juliano Rizzo, de la página independiente broccoli2shell.com, que declara no estar afiliada a Google, al proyecto Node.js ni a la Python Software Foundation. Los estados de parcheo son a 26 de octubre de 2026 y cambian cada pocas horas: antes de actuar, ve al repositorio del producto que te afecte, que es justo lo que pide este artículo. No hemos encontrado este fallo ni hemos auditado el código de Brotli, y no tenemos un cliente cuya plataforma se cayera por esto esta semana: este artículo lee avisos y repositorios públicos y los cruza con cómo llevamos nosotros un inventario, y lo dice así a propósito.