RCE en el decodificador Brotli: Node.js, Bun, Python y Envoy
CVE-2026-111497 es un único fallo en C replicado en cada runtime que incluye el decodificador Brotli de Google. Qué está confirmado y cómo parchearlo.

Un fallo de corrupción de memoria en el decodificador Brotli de Google (CVE-2026-111497) resulta parecerse menos a una vulnerabilidad de biblioteca habitual y más a un jefe de misión secundaria distribuida. El fallo está en el decodificador C de referencia, lo que significa que cada proyecto que incluye ese código lleva su propia copia: Node.js, Bun, los paquetes Python Brotli y brotlicffi, Envoy e Istio cuando la descompresión Brotli está habilitada, y una larga cola de bindings, proxies y herramientas de línea de comandos. The Hacker News informa de que el equipo Red Team de Imperva logró ejecución de código real en una build de Node.js sin parchear, pero solo en esa build, y sin explotación conocida en el mundo real. Solucionarlo no es un parche único. Es una búsqueda del tesoro entre tus dependencias transitivas.
El fallo se hizo público el 21 de octubre, cuando Node.js y Google publicaron las correcciones, y el resto de software fue llegando con sus parches producto a producto. El equipo Codex Security de OpenAI, en concreto Khai Tran y Sylvie Mayer, a quienes Node.js acredita, lo reportó como ejecución remota de código sin autenticación en Brotli, y funciona en ambos sentidos: un cliente malicioso que ataca a un servidor que descomprime subidas, o un servidor malicioso que ataca a un cliente que obtiene respuestas comprimidas. Google le asigna un 9,2 (crítico) en CVSS 4.0; Node.js lo califica como alto; Red Hat lo sitúa en 8,1. Esa dispersión en las puntuaciones es la primera pista de que la historia de explotabilidad es más matizada que el titular.
Cómo funciona realmente el fallo
La explicación técnica en broccoli2shell.com recorre el primitivo con claridad, y es un caso clásico. Un flujo Brotli se divide en bloques, y el decodificador cambia entre tipos de bloque mientras lee. La función DecodeBlockTypeAndLength() en c/dec/decode.c devolvía un booleano simple, así que quien la llamaba no podía distinguir un flujo malformado de uno que simplemente necesitaba más datos. En la ruta rápida, el decodificador ignoraba por completo ese valor de retorno.
Un flujo diseñado solicita un cambio de bloque que no puede producirse. El decodificador lo interpreta como una necesidad de más datos y sigue añadiendo bytes controlados por el atacante a un búfer interno de ocho bytes, escribiendo más allá de su final. Esa es toda la cadena: un valor de retorno no comprobado (CWE-252) se convierte en una escritura fuera de límites (CWE-787), y la sobrescritura corrompe el estado adyacente del decodificador. A partir de ahí, según la disposición del heap y del programa anfitrión, se obtiene un fallo, una fuga de memoria o, con suficiente preparación, ejecución de código.
// Upstream fix: google/brotli commit 0ed4268 (plus 50a9b70, b418fa8, da98063)
// Before: boolean return — malformed stream indistinguishable from needing more input
// After: hard format error returned to the caller
return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH; /* -17 */
Lo más molesto es esto: el commit con la corrección llegó al árbol de código fuente de Brotli el 27 de febrero. Nadie lo reconoció entonces como una corrección de seguridad, y Brotli simplemente no publicó una versión durante ocho meses. La propia explicación de OpenAI dice que la causa raíz «se corrigió en el código fuente de Brotli en febrero, pero no se sabía en ese momento que daba lugar a una vulnerabilidad de seguridad y no había una versión más reciente de Brotli». El parche existía, pero estaba en un repositorio mientras cada consumidor posterior seguía distribuyendo el decodificador vulnerable. Si necesitabas un argumento para rastrear la procedencia de las dependencias vendorizadas, aquí lo tienes.
Confirmado, afirmado y desconocido: mantenlo separado
Aquí la disciplina importa, porque la relación señal-ruido en este caso es complicada. Tres categorías:
- Confirmado: La escritura fuera de límites existe en las versiones 1.0.3 a 1.2.0 del decodificador Brotli, y está corregida en 1.0.10, 1.1.1 y 1.2.1. El Red Team de Imperva (nombre del proyecto Node2Shell) logró ejecución de código en una build sin modificar de Node.js 24.22.0 para Linux en ARM64, entregada tanto como respuesta
Content-Encoding: brobtenida como cuerpo de una petición Express. El exploit necesitó varios intentos. - Afirmado pero no demostrado: OpenAI describe RCE bidireccional y sin autenticación que afecta a Node.js, Bun y Envoy, incluido el escenario de servidor malicioso que ataca a cliente (piensa en
curl --compressed). Es plausible dado el primitivo, pero nadie ha demostrado ejecución de código en ningún software distinto de esa build de Node.js. El exploit no se ha publicado. - Desconocido: Si seguirá la explotación en el mundo real. La evaluación de CISA del 23 de octubre registró la explotación como «ninguna» y la automatización como «no». Red Hat señala que no existe una prueba de concepto pública fiable para ejecutar código. La fiabilidad de la preparación del heap entre builds, asignadores y arquitecturas sigue abierta: ARM64 en Linux resultó viable, y eso es todo lo que sabemos.
¿Por qué importa la distinción? Porque tu prioridad de parcheo debe seguir la explotabilidad demostrada multiplicada por la exposición, no la frase más aterradora del aviso. El caso confirmado es un decodificador del lado servidor accesible por HTTP. El encuadre de RCE bidireccional es la valoración del reportero sobre el techo del primitivo, no una capacidad documentada. Ambas cosas justifican parchear rápido, porque un fallo público con un primitivo conocido de desbordamiento de heap de ocho bytes es un imán para los desarrolladores de exploits, pero nadie debería dar un incidente por cerrado por una PoC que todavía no existe.
Estás expuesto en más lugares de los que admite tu lista de dependencias
La exposición requiere una sola cosa: que tu programa decodifique datos Brotli que controla otra persona. Lo peligroso es cuántas rutas hacen eso sin aparecer nunca en tu código.
En Node.js, el fetch() integrado decodifica de forma transparente las respuestas Content-Encoding: br, y Express 5 / body-parser 2 descomprimen los cuerpos de petición Brotli, así que los webhooks, los extractores de vista previa de enlaces, los crawlers y las funciones de «importar desde URL» son superficie de ataque. Ojo con la letra pequeña de broccoli2shell.com: Express y body-parser no contienen el fallo, solo son rutas hacia él. Node incluye su propia copia de Brotli, así que la versión de libbrotli del sistema no importa allí; solo importa la versión del runtime. Un servidor node:http básico no decodifica cuerpos por sí mismo.
Python es más sigiloso. Si el paquete Brotli o brotlicffi está instalado, aunque sea en parte, urllib3 (y por tanto requests), aiohttp, httpx con el extra brotli y crawlers como Scrapy decodificarán automáticamente las respuestas Brotli. Tu aplicación puede estar totalmente expuesta sin que la cadena «brotli» aparezca en ningún lugar de tu código. Es un fantasma de dependencia transitiva: la vulnerabilidad no está en lo que escribiste ni siquiera en lo que importaste, sino en lo que tu cliente HTTP cargó en silencio.
# Don't trust the scanner — check by hand
pip show Brotli brotlicffi
# Vulnerable: Brotli <= 1.2.0, brotlicffi <= 1.2.0.x
# Fixed: Brotli 1.2.1, brotlicffi 1.2.1.0
Y todavía no puedes fiarte del escáner. El registro del CVE menciona el repositorio de Brotli de Google, no los paquetes de PyPI, y en el momento de la publicación el aviso de GitHub, que es lo que lee Dependabot, no listaba ningún paquete afectado. PYSEC-2026-4322 y PYSEC-2026-4323 para los dos paquetes Python solo aparecieron el 23 de octubre. Si este caso deja alguna enseñanza para los defensores, es que las herramientas basadas en CVE subestiman de forma sistemática el código C vendorizado. Vimos una versión más leve del mismo problema cuando un paquete con puerta trasera se coló a través de las comprobaciones del ecosistema en el incidente de cadena de suministro de LiteLLM: el grafo de avisos va por detrás del grafo de artefactos reales.

Envoy, Istio y el radio de impacto más allá de los runtimes
Las versiones de Envoy 1.31.0–1.31.1, 1.30.0–1.30.5 y 1.29.0–1.29.8 solo están afectadas cuando la descompresión Brotli está configurada, por ejemplo mediante un filtro. Las versiones corregidas son 1.31.2, 1.30.6 y 1.29.9. Istio sigue la misma lógica condicional. Si la descompresión está desactivada, el fallo es inalcanzable, lo que al menos es una pregunta de triaje clara para el equipo de plataforma.
Más allá del software citado, cualquier cosa que incluya o enlace Brotli 1.0.3–1.2.0 puede estar afectada incluso antes de que un aviso lo diga: la biblioteca del sistema (libbrotlidec, empaquetada como libbrotli1 y similares), los wrappers JNI de Java, las extensiones de PHP y Ruby, los bindings cgo de Go, las herramientas de archivado. Los decodificadores reescritos en otros lenguajes están bien: los puertos a Java, JavaScript y C# no pueden alcanzar el fallo, brotli-decompressor de Rust detecta la condición y devuelve un error, y el port a Go (andybalholm/brotli) tenía el mismo error lógico, pero solo podía provocar un cierre; se corrigió el 23 de octubre. La ejecución de código es un privilegio exclusivo del decodificador en C. La elección del lenguaje como mitigación de exploits, una vez más, demuestra su valor.
Mitigación: qué funciona y qué no
Primero, las falsas soluciones. Quitar br de la cabecera Accept-Encoding de tu cliente no ayuda: un servidor malicioso puede enviar Brotli independientemente de lo que pidas. Las reglas de WAF también tienen un techo claro: Vercel, que publicó reglas de firewall el día de la divulgación, indicó que no cubren «las respuestas que tus funciones obtienen de otros servidores». Para la ruta de peticiones salientes, solo actualizar el runtime o la biblioteca cierra el agujero. Y en el caso concreto de Node.js, no se ha publicado ningún workaround: actualizar el runtime es la única solución.
Lo que sí funciona, según la página de divulgación de OpenAI y la firma de seguridad Aikido, cuando no puedes parchear de inmediato:
- Rechaza en el perímetro las peticiones entrantes con
Content-Encoding: br, antes de que cualquier proxy o parser toque el cuerpo. - Desactiva la decodificación Brotli automática en los clientes HTTP y rechaza las respuestas Brotli antes de decodificarlas.
- En Python, desinstala por completo Brotli y brotlicffi para que los clientes recurran a gzip y deflate.
- Tras parchear, reinicia el servicio y reconstruye las imágenes de contenedor: un proceso en ejecución mantiene el decodificador antiguo en memoria hasta que se reinicia, y una imagen lo mantiene hasta que se reconstruye sobre una base parcheada.
La matriz de versiones, resumida: Node.js 22.23.4, 24.22.1 y 26.12.1 están corregidos (20.x y anteriores ya no tienen soporte: hay que migrar); Bun 1.4.4 está corregido; Python Brotli 1.2.1 y brotlicffi 1.2.1.0 están corregidos. En el lado de las distribuciones, Debian parcheó el paquete brotli de trixie el 23 de octubre (1.1.0-2+deb13u1), mientras que Amazon Linux 2023 seguía listando brotli y Node.js 24 como pendientes a fecha del 22 de octubre. Espera ese retraso: es exactamente la razón por la que «hemos parcheado» debe significar el proceso en ejecución, no la lista de pendientes del gestor de paquetes.
Si miramos el panorama completo, es la misma lección que el ecosistema sigue reaprendiendo a dificultad de jefe final: el código C vendorizado multiplica el riesgo. Un único booleano sin comprobar en un decodificador, copiado en una docena de runtimes, y el coste de remediación se paga por consumidor, por reinicio y por reconstrucción. Y hay que reconocer el mérito: Khai Tran y Sylvie Mayer encontraron y reportaron el fallo, los mantenedores de Node.js Matteo Collina y Antoine du Hamel publicaron la corrección con rapidez, y el texto de broccoli2shell.com es la referencia técnica más clara disponible. Todavía no hay explotación en el mundo real. Parchea antes de que alguien publique la PoC que cambie esa frase.
Fuentes
Este artículo se basa en informaciones y avisos de:


