Volver

2026/10/26

HTTP2Shell (CVE-2026-111497): actualizar un binario no corrige los procesos en ejecución

La falla de Brotli detrás de HTTP2Shell afecta a Node.js, Bun y Python. Cómo medir la exposición real y reiniciar agentes de IA con seguridad.

El 21 de octubre de 2026, Node.js publicó correcciones para CVE-2026-111497, una escritura fuera de límites en el decodificador Brotli de Google. En los días siguientes llegaron avisos para Bun, para los paquetes de Python brotli y brotlicffi, para Envoy y para Istio, y la prensa especializada bautizó la falla como Node2Shell y, más tarde, HTTP2Shell. Pese al apodo, no es un error del protocolo HTTP/2. Es un error de descompresión, y es relevante allí donde un software decodifique datos Brotli en los que un atacante pueda influir.

Este artículo expone qué está verificado, qué es solo posible y cómo comprobar sus propios sistemas. También recoge una lección de nuestra propia respuesta: instalar un binario corregido no corrige un proceso que sigue ejecutando el antiguo. En nuestro entorno, las sesiones de Claude Code de larga duración conservaron sus runtimes integrados antiguos después de que se actualizara el Claude Code instalado.

Puntos clave

  • Afectado: Brotli upstream de 1.0.3 a 1.2.0. Corregido: 1.0.10, 1.1.1 y 1.2.1. Los productos que incluyen Brotli necesitan su propia versión corregida.
  • La exposición requiere una vía real: datos Brotli influidos por un atacante que llegan a un decodificador vulnerable. El nombre de un producto o el acceso de red, por sí solos, no son prueba.
  • Compruebe los procesos en ejecución, no solo los paquetes instalados. Un proceso conserva el runtime con el que arrancó hasta que se reinicia.
  • Reinicie los agentes de IA con estado de uno en uno, tras traspasar antes sus eventos, aprobaciones y estado de trabajo.

1. En qué consiste la vulnerabilidad

Cuando el decodificador encontraba un cambio de bloque no válido, informaba “need more input” (se necesitan más datos) en lugar de un error de formato permanente. Un llamador en streaming seguía entonces enviando bytes, y el decodificador los añadía a un búfer interno de ocho bytes, escribiendo más allá de su final. El aviso de Node.js indica que un atacante capaz de suministrar datos Brotli que una aplicación descomprime con node:zlib “puede corromper la memoria del proceso, lo que puede provocar una caída, la divulgación de memoria del proceso o la ejecución remota de código”.

Las calificaciones de gravedad varían según la fuente. Node.js la califica como High (alta). El aviso de GitHub para los paquetes de Python y el aviso de Envoy la califican como Critical (crítica), con una puntuación CVSS 4.0 de 9.2. Ubuntu la clasifica actualmente con prioridad Medium (media). A 26 de octubre, la evaluación de CISA en el registro de la CVE indica la explotación como “none” (ninguna), y la CVE no figura en el catálogo Known Exploited Vulnerabilities de CISA. Aun así, el aviso de Node.js menciona la ejecución remota de código como posible consecuencia, así que no tome la ausencia de ataques notificados como motivo para esperar.

2. Requisitos previos: cuándo está realmente expuesto

Deben cumplirse las tres condiciones:

  1. Hay un decodificador vulnerable cargado en el proceso: una biblioteca Brotli incluida o del sistema, dentro del rango afectado, sin corrección ni backport.
  2. Algo lo invoca: descompresión explícita o decodificación automática de Content-Encoding: br.
  3. Un atacante puede influir en la entrada: respuestas de URL arbitrarias, archivos subidos, cuerpos de solicitudes entrantes o tráfico HTTP sin cifrar en una red hostil.

La decodificación automática es la vía que los equipos pasan por alto con más frecuencia. El aviso de Bun señala que fetch envía Accept-Encoding: br de forma predeterminada y decodifica la respuesta, de modo que cualquier programa Bun que obtenga URL no fiables tiene una vía. En Python, en cuanto brotli o brotlicffi está instalado, urllib3 decodifica automáticamente las respuestas con Content-Encoding: br, y requests también anuncia br en sus solicitudes. Esto ocurre aunque su propio código nunca mencione Brotli. Envoy e Istio solo están expuestos cuando hay un descompresor Brotli configurado.

3. Afectado confirmado frente a solo posible

Situación a 26 de octubre de 2026. Consulte los avisos enlazados, porque la situación evoluciona rápidamente.

ComponenteEstadoCorregido en
Brotli upstream (C)Confirmado: 1.0.3 a 1.2.01.0.10, 1.1.1, 1.2.1
Node.js 22, 24, 26Confirmado22.23.4, 24.22.1, 26.12.1
Node.js 20 y anteriores (fin de vida)No evaluado; Node.js considera que las líneas en fin de vida están siempre afectadasPasar a una línea con soporte
Bun (incluidos los ejecutables compilados)Confirmado: 1.0.21 a 1.4.31.4.4, y recompilar las aplicaciones compiladas
Python brotli / brotlicffiConfirmado: desde 1.0.31.2.1 / 1.2.1.0
Envoy (descompresor Brotli configurado)Confirmado1.40.0, 1.39.3, 1.38.6, 1.37.8 (1.36.12 en la rama 1.36, sin soporte)
Istio (descompresión Brotli habilitada)Confirmado1.31.2, 1.30.6, 1.29.9 (la última versión 1.29)
Paquete brotli de UbuntuPendiente de evaluación (todas las versiones)Aún no hay paquete corregido
Paquete nodejs de UbuntuVulnerable en 20.04 (Brotli incluido). Desde 22.04 usa el Brotli del sistema, por lo que sigue la fila anteriorVer el rastreador de Ubuntu
Paquete brotli de DebianCorregido en bookworm y trixie; forky todavía no1.0.9-2+deb12u1, 1.1.0-2+deb13u1
Chrome, Electron y aplicaciones ElectronDesconocido: no se ha encontrado declaración del proveedorSeguir las notas de versión del proveedor

Sobre la última fila: Chrome y Electron incluyen sus propias copias de Brotli, y una aplicación Electron también integra un runtime de Node.js. En el momento de redactar este artículo, las versiones estables más recientes de Electron (45.0.0, 44.9.3 y 43.8.3, todas del 21 de octubre) indican Node.js 24.22.0, no 24.22.1, y sus notas no mencionan esta CVE. Las versiones preliminares de Electron del 23 de octubre incluyen Node.js 24.22.1, por lo que una corrección estable parece probable, pero aún no se ha publicado. Eso hace que una aplicación de escritorio basada en Electron esté posiblemente expuesta, no que sea vulnerable confirmada. Lo mismo se aplica a clientes de chat y herramientas de IA de escritorio: consulte la declaración de cada proveedor antes de calificarlos como afectados o seguros.

Otra trampa: la versión de biblioteca reportada no es prueba en ningún sentido. Un Node.js 22.23.4 corregido sigue reportando process.versions.brotli como 1.1.0, porque la corrección se aplicó mediante backport. Las distribuciones hacen lo mismo. Juzgue por la versión corregida del producto o por el estado de seguridad de la distribución, no por la cadena de versión de Brotli.

4. Qué encontramos en nuestro propio entorno

Realizamos un inventario de solo lectura antes de cambiar nada. Esto es lo que observamos, de forma anonimizada, separando los hallazgos confirmados del riesgo que no pudimos verificar.

Observaciones confirmadas

  • El Claude Code instalado ya se había actualizado, pero una docena de sesiones de larga duración seguían ejecutando builds más antiguas iniciadas semanas antes. Nunca se habían reiniciado.
  • Nuestro primer recuento pasó por alto tres de ellas. Se ejecutaban bajo un proceso auxiliar distinto, por lo que una búsqueda por línea de comandos no las detectó. La búsqueda por ejecutable sí las encontró.
  • El actualizador ya había borrado del disco los archivos de las builds antiguas, por lo que no fue posible leer la versión de Bun integrada en esos procesos.
  • Una instalación residual, mediante gestor de paquetes, de una build más antigua de Claude Code integraba Bun 1.4.0. Nada la usaba en el funcionamiento normal, pero una vía de respaldo podría haberlo hecho. La eliminamos tras demostrar que nada dependía de ella.
  • En un servidor seguía instalado un runtime de Node.js en fin de vida que no se usaba. Lo eliminamos del mismo modo.
  • La build actual de Claude Code a la que actualizamos (2.1.310) contiene Bun 1.4.4. Lo confirmamos inspeccionando el binario. El changelog de Anthropic no menciona la CVE, así que compruebe su propia build del mismo modo.

No verificado, tratado como riesgo

  • Si las sesiones antiguas eran realmente vulnerables. Su versión de Bun era ilegible. El Bun 1.4.4 corregido no apareció hasta alrededor del 22 de octubre, después de esas builds, así que las tratamos como afectadas.
  • Si un atacante podría haber alcanzado el decodificador a través de esas sesiones, por ejemplo al obtener una URL maliciosa. Esta vía es plausible dado el comportamiento documentado de fetch en Bun, pero no lo demostramos.
  • Si otro software que incluye su propia copia de Brotli está afectado. Cuando no existe declaración del proveedor, seguimos el producto frente a las versiones del proveedor en lugar de suponer que es seguro o vulnerable.

No encontramos ninguna evidencia de explotación. Tampoco lo tomamos como prueba de seguridad: la solución fue eliminar la exposición, no descartarla con argumentos.

5. Lista de comprobación de detección y corrección

Localizar las dependencias

Busque Brotli allí donde va incluido, no solo donde está instalado como paquete. Revise sus SBOM e imágenes de contenedor en busca de Node.js, Bun, paquetes de Python, Envoy y Brotli enlazado estáticamente. Revise las dependencias transitivas de Python, porque Brotli suele llegar como extra opcional.

# Node.js: compare with 22.23.4 / 24.22.1 / 26.12.1
node -p process.version
# Python packages: run once per interpreter and virtual environment
python -m pip list 2>/dev/null | grep -iE '^(brotli|brotlicffi) '
pipdeptree -r -p brotli          # what pulled it in
# Debian / Ubuntu system library (then check the distro tracker)
dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' 'libbrotli*'
# Container images, with an SBOM scanner such as Syft
syft <image> | grep -iE 'brotli|node|bun'

Localizar los runtimes integrados

Los programas compilados con bun build --compile llevan su propia copia de Bun. El aviso de Bun indica que siguen afectados hasta que se recompilan con 1.4.4. Actualizar Bun en la máquina de compilación no cambia los binarios ya desplegados.

# The Bun version inside a compiled executable
grep -aoE 'Bun v1\.[0-9]+\.[0-9]+' /path/to/binary | sort -u

Localizar los procesos en ejecución

Compruebe lo que se está ejecutando, no lo que está instalado. En Linux, un proceso cuyo ejecutable se reemplazó o eliminó muestra (deleted). Es un indicio claro de que sigue ejecutando una build antigua.

# Processes still running a deleted (replaced) executable (run as root to see all users)
for p in /proc/[0-9]*; do
  e=$(readlink "$p/exe" 2>/dev/null) || continue
  case "$e" in *"(deleted)") echo "${p#/proc/} $e";; esac
done
# Processes older than the fix
pgrep -af 'node|bun|claude'      # then: ps -o pid,lstart,args -p <pid>

En macOS, lsof -p <pid> muestra el ejecutable que cargó un proceso. Busque por ejecutable, no por nombre de proceso, o pasará por alto procesos auxiliares y workers en segundo plano.

Corregir, recompilar, volver a desplegar

  • Actualice a las versiones corregidas de la tabla. Use el paquete corregido de su distribución cuando exista un backport.
  • Recompile las imágenes de contenedor y los ejecutables compilados y vuelva a desplegarlos. Una imagen base corregida no sirve de nada para una imagen que no ha recompilado.
  • Elimine los runtimes sin uso y las instalaciones residuales, tras demostrar que ningún servicio, tarea o script depende de ellos. Conserve una vía de reversión.
  • Donde aún no haya corrección, desactive la decodificación Brotli de entradas no fiables cuando sea seguro hacerlo. En Bun, fetch(url, { decompress: false }) funciona por solicitud. Asigne a cada mitigación un responsable y una fecha de fin.

Reiniciar y verificar

Tras reiniciar, compruebe el ejecutable y la versión del runtime reales del nuevo proceso. Después compruebe el resultado de negocio: colas vaciadas, tareas programadas completadas con éxito, alertas de monitorización llegando. Un comando de actualización correcto solo demuestra que el comando se ejecutó.

6. Reiniciar agentes de IA persistentes con seguridad

Los agentes de IA de larga duración guardan estado: llamadas a herramientas pendientes, aprobaciones, mensajes en cola y trabajo reclamado. Reiniciarlos todos a la vez puede hacer perder ese estado. Esta secuencia nos funcionó:

  1. Inventaríe cada sesión: versión del ejecutable, estado (inactiva, en espera u ocupada), última actividad, trabajo pendiente, aprobaciones que espera o mantiene, eventos no confirmados y cómo se recuperará.
  2. Saque las aprobaciones de la memoria. Una aprobación que solo existe en la conversación de una sesión se pierde al reiniciar. Antes de reiniciar, registre cada aprobación pendiente (qué se aprobó, quién lo hizo y para qué cambio exacto) en un lugar persistente, como el ticket o el pull request. Después, confirme que la sesión todavía la ve. En caso de duda, vuelva a solicitar la aprobación en lugar de darla por supuesta.
  3. Demuestre primero el reemplazo. Confirme que las sesiones con la build corregida ya funcionan y que hay en ejecución un observador de eventos independiente de las sesiones antiguas, para que ningún evento dependa de que se reinicie una sesión.
  4. Reinicie primero las sesiones inactivas, de una en una. Después de cada una, verifique que el proceso antiguo ha desaparecido, que el nuevo ejecuta el ejecutable corregido, que el historial de la conversación está intacto y que cualquier conexión remota se ha restablecido. Luego pase a la siguiente.
  5. Reinicie al final al responsable más ocupado, entre tareas. Haga que escriba una nota de traspaso, detenga su observador solo con cero eventos pendientes y cero no confirmados, reinícielo y confirme que el observador se reanudó desde la posición guardada sin perder nada.
  6. Cuente con entradas en cola al reconectar. Una de nuestras sesiones reiniciadas recibió un mensaje que llevaba diez días esperando y actuó en consecuencia. Revise la bandeja de entrada de cada sesión antes de reiniciar y avise a su responsable.
  7. Deténgase y escale si algún paso no puede demostrar que el trabajo se recuperó. No recurra a terminarlo todo a la fuerza.

Defina “terminado” antes de empezar. Terminado significa que ningún proceso en ejecución usa una build afectada, que cada excepción está documentada con un responsable y una fecha de revisión, y que las comprobaciones de negocio se superan. “Se actualizó el paquete” no es terminado.

Fuentes