Brotli CVE-2026-111497: parchea apps Node.js, Bun y Python
CVE-2026-111497 es un fallo de corrupción de memoria en el decodificador Brotli de Google, el código C que descomprime los datos con Content-Encoding: br. Unos datos comprimidos manipulados escriben más allá del final de un búfer, lo que puede tumbar el programa, filtrar memoria o ejecutar código del atacante. Node.js, Bun, los paquetes de Python Brotli y brotlicffi y Envoy incluyen cada uno su propia copia de ese decodificador, así que hay que actualizar cada uno por separado. Esta guía explica cuándo una aplicación está realmente expuesta, cómo revisar un servidor con un solo comando y cómo desplegar la corrección sin olvidar ningún contenedor.
Qué tan grave es
- Google le asigna 9.2 (critical) en CVSS 4.0, Node.js la califica como high y Red Hat con 8.1.
- Los investigadores de Imperva ejecutaron su propio código en un Node.js 24.22.0 sin parchear sobre Linux ARM64, tanto a través de una respuesta obtenida con fetch como del cuerpo de una petición en Express. El exploit no es público.
- La evaluación de CISA del 23 de octubre no registró explotación activa, y no existe una prueba de concepto pública fiable.
- El fallo funciona en ambos sentidos: un servidor que descomprime entradas comprimidas puede ser atacado por un cliente, y un cliente que descomprime respuestas comprimidas puede ser atacado por un servidor.
La corrección se integró en el código fuente de Brotli el 27 de febrero, pero ninguna versión de Brotli la incluyó hasta el 21 de octubre, por eso todos los runtimes parcheados aparecieron la misma semana.
¿Estás expuesto?
Solo corre riesgo el código que decodifica datos Brotli controlados por otra persona:
- Node.js y Bun - el
fetch()integrado al leer una respuesta enviada conContent-Encoding: br, las llamadas Brotli denode:zlibsobre datos no confiables y los parsers de cuerpo que descomprimen subidas en Brotli (Express 5, body-parser 2). Un servidor HTTP de Node.js sin más no decodifica los cuerpos de las peticiones por sí solo. - Python - basta con tener instalado el paquete Brotli o brotlicffi: requests, urllib3, httpx y aiohttp decodifican entonces las respuestas Brotli por su cuenta, aunque tu código nunca importe Brotli.
- Envoy e Istio - solo si hay configurado un filtro de descompresión Brotli.
Cualquier servicio que llame a APIs de terceros, webhooks o proveedores de pago, o que haga scraping de páginas, ejecuta fetch() contra servidores que no controlas. Quitar br de Accept-Encoding no es una solución: un servidor puede enviar Brotli de todos modos.
| Software | Afectado | Corregido en |
|---|---|---|
| Node.js | 22.x, 24.x, 26.x antes de la corrección; 20.x y anteriores (fin de soporte) | 22.23.4, 24.22.1, 26.12.1 |
| Bun | 1.4.3 y anteriores | 1.4.4 |
| Python Brotli | 1.2.0 y anteriores | 1.2.1 |
| Python brotlicffi | 1.2.0.x y anteriores | 1.2.1.0 |
| Google Brotli (biblioteca C) | 1.0.3 - 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Envoy | 1.39.2, 1.38.5, 1.37.7, 1.36.11 | 1.40.0, 1.39.3, 1.38.6, 1.37.8, 1.36.12 |
| Istio | 1.31.0 - 1.31.1, 1.30.0 - 1.30.5, 1.29.0 - 1.29.8 | 1.31.2, 1.30.6, 1.29.9 |
Revisa un servidor con un solo comando
Este script de shell POSIX revisa Bun, Node.js y Python en la máquina (o dentro de un contenedor) e indica cuáles son anteriores a la corrección. Guárdalo como brotli-check.sh:
#!/bin/sh
# CVE-2026-111497 (Brotli decoder): which runtimes on this host are older than the fixes?
ver_lt() { [ "$1" = "$2" ] && return 1; [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }
if command -v bun >/dev/null 2>&1; then
v=$(bun --version)
if ver_lt "$v" 1.4.4; then echo "bun $v: VULNERABLE, upgrade to 1.4.4+"; else echo "bun $v: fixed"; fi
fi
if command -v node >/dev/null 2>&1; then
v=$(node -p "process.versions.bun ? '' : process.versions.node")
if [ -z "$v" ]; then echo "node: this is Bun's node shim, see the bun line"; else major=${v%%.*}
case $major in
22) fix=22.23.4 ;; 24) fix=24.22.1 ;; 26) fix=26.12.1 ;;
*) fix= ;;
esac
if [ -z "$fix" ]; then echo "node $v: no fixed release for this line, move to 22, 24 or 26"
elif ver_lt "$v" "$fix"; then echo "node $v: VULNERABLE, upgrade to $fix+"
else echo "node $v: fixed"; fi
fi
fi
for py in python3 python; do
command -v $py >/dev/null 2>&1 || continue
$py - <<'PY'
from importlib import metadata
for pkg, fix in (("Brotli", "1.2.1"), ("brotlicffi", "1.2.1.0")):
try:
v = metadata.version(pkg)
except metadata.PackageNotFoundError:
print(f"python {pkg}: not installed")
continue
a = [int(x) for x in v.split(".") if x.isdigit()]
b = [int(x) for x in fix.split(".")]
print(f"python {pkg} {v}: {'VULNERABLE, upgrade to ' + fix + '+' if a < b else 'fixed'}")
PY
break
done
sh brotli-check.sh
docker exec my-app sh -s < brotli-check.sh # the same check inside a running container
Salida comprobada el 2026-10-24 (un portátil de desarrollo y después imágenes oficiales de Docker):
bun 1.4.1: VULNERABLE, upgrade to 1.4.4+
node 26.6.0: VULNERABLE, upgrade to 26.12.1+
python Brotli: not installed
python brotlicffi: not installed
bun 1.4.4: fixed
node: this is Bun's node shim, see the bun line
node 22.23.4: fixed
node 16.20.2: no fixed release for this line, move to 22, 24 or 26
python Brotli 1.1.0: VULNERABLE, upgrade to 1.2.1+
python brotlicffi: not installed
Ejecútalo en cada contenedor, no solo en el host: el Node.js del host no dice nada del que va empaquetado en una imagen.
Cómo desplegar la corrección
- Encuentra cada copia. Las imágenes son lo que más se olvida. Lista lo que ejecutas:
docker ps --format '{{.Image}}'y luego las líneasFROMde tus Dockerfiles. Una etiqueta comonode:22uoven/bun:alpinesolo avanza cuando haces pull; una etiqueta fijada comooven/bun:1.4.1-slimno avanza nunca. - Actualiza el runtime. Sube las etiquetas fijadas (
oven/bun:1.4.4-slim,node:22.23.4-alpine), ejecutabun upgradey actualiza los paquetes de Node.js en los hosts, y en los entornos Pythonpip install -U "Brotli>=1.2.1"(obrotlicffi>=1.2.1.0). Si una aplicación Python no necesita Brotli,pip uninstall Brotli brotlicffitambién cierra la vía: los clientes HTTP pasan a usar gzip y deflate. - Reconstruye y sustituye.
docker compose build --pull && docker compose up -d. Un proceso en marcha conserva el decodificador antiguo hasta que se reinicia, así que reinicia también los servicios en los hosts. - Verifica. Vuelve a ejecutar el script dentro de cada contenedor (
docker exec <name> bun --versiones la versión rápida). Los lockfiles con--frozen-lockfilepueden hacer fallar la reconstrucción tras subir dependencias: haz commit del lockfile actualizado junto conpackage.json. - ¿Aún no puedes actualizar? Rechaza en el proxy las peticiones con
Content-Encoding: brantes de que nada decodifique el cuerpo, y evita descargar datos de hosts no confiables. Vercel señala que las reglas de firewall no cubren las respuestas que tu propio código obtiene de otros servidores - solo la actualización lo hace.
Para un stack Node.js en una VM consulta Servidor cloud para Node.js, para Python Servidor cloud para Python y Flask y para imágenes que se reconstruyen sin problemas Servidor cloud para Docker Swarm.
Qué hicimos nosotros
A los pocos días de las correcciones pasamos a 1.4.4 todos los servicios de DCXV que funcionan con Bun: las imágenes base fijadas pasaron a etiquetas corregidas y todas las imágenes se reconstruyeron con un pull nuevo. Nuestros componentes Node.js no decodifican cuerpos Brotli y nuestros proxies no tienen descompresión Brotli. Los servidores de los clientes los gestionan sus propietarios: si ejecutas Node.js, Bun o Python en un servidor cloud de DCXV, lanza hoy la comprobación de arriba o pregúntanos por la administración gestionada de servidores.
Fuentes (consultadas el 2026-10-24): las versiones de seguridad de Node.js de octubre de 2026 (22.23.4, 24.22.1, 26.12.1), la versión Bun 1.4.4, la evaluación de CISA del 23 de octubre de 2026 y la investigación de Imperva.
