Voltar

2026/10/26

HTTP2Shell (CVE-2026-111497): atualizar um binário não corrige os processos em execução

A falha do Brotli por trás do HTTP2Shell atinge Node.js, Bun e Python. Como medir a exposição real e reiniciar agentes de IA com segurança.

Em 21 de outubro de 2026, o Node.js publicou correções para a CVE-2026-111497, uma escrita fora dos limites no decodificador Brotli do Google. Nos dias seguintes surgiram avisos para Bun, para os pacotes Python brotli e brotlicffi, para Envoy e para Istio, e a imprensa especializada batizou a falha de Node2Shell e, mais tarde, de HTTP2Shell. Apesar do apelido, não se trata de um bug no protocolo HTTP/2. É um bug de descompressão, relevante onde quer que um software decodifique dados Brotli que um atacante possa influenciar.

Este artigo expõe o que está verificado, o que é apenas possível e como verificar os seus próprios sistemas. Também aborda uma lição da nossa própria resposta: instalar um binário corrigido não corrige um processo que continua executando o antigo. No nosso ambiente, sessões do Claude Code de longa duração mantiveram os seus runtimes embutidos antigos depois de o Claude Code instalado ter sido atualizado.

Pontos principais

  • Afetado: Brotli upstream de 1.0.3 a 1.2.0. Corrigido: 1.0.10, 1.1.1 e 1.2.1. Produtos que incluem o Brotli precisam da sua própria versão corrigida.
  • A exposição exige um caminho real: dados Brotli influenciados por um atacante chegando a um decodificador vulnerável. O nome de um produto ou o acesso à rede, por si sós, não são prova.
  • Verifique os processos em execução, não apenas os pacotes instalados. Um processo mantém o runtime com que foi iniciado até ser reiniciado.
  • Reinicie os agentes de IA com estado um de cada vez, transferindo antes os seus eventos, aprovações e estado de trabalho.

1. O que é a vulnerabilidade

Quando o decodificador encontrava uma troca de bloco inválida, informava “need more input” (é preciso mais entrada) em vez de um erro de formato permanente. Um chamador em streaming continuava então a enviar bytes, e o decodificador acrescentava-os a um buffer interno de oito bytes, escrevendo para além do seu fim. O aviso do Node.js afirma que um atacante capaz de fornecer dados Brotli que uma aplicação descomprime com node:zlib “pode corromper a memória do processo, o que pode levar a uma falha, à divulgação da memória do processo ou à execução remota de código”.

As classificações de gravidade variam conforme a fonte. O Node.js classifica-a como High (alta). O aviso do GitHub para os pacotes Python e o aviso do Envoy classificam-na como Critical (crítica), com pontuação CVSS 4.0 de 9.2. O Ubuntu lista-a atualmente com prioridade Medium (média). Em 26 de outubro, a avaliação da CISA no registro da CVE indica a exploração como “none” (nenhuma), e a CVE não consta do catálogo Known Exploited Vulnerabilities da CISA. Ainda assim, o aviso do Node.js aponta a execução remota de código como resultado possível, por isso não trate a ausência de ataques reportados como motivo para esperar.

2. Pré-requisitos: quando você está realmente exposto

As três condições têm de se verificar:

  1. Um decodificador vulnerável está carregado no processo: uma biblioteca Brotli incluída ou do sistema, na faixa afetada, sem correção nem backport.
  2. Algo o chama: descompressão explícita ou decodificação automática de Content-Encoding: br.
  3. A entrada pode ser influenciada por um atacante: respostas de URLs arbitrárias, arquivos enviados, corpos de requisições recebidas ou tráfego HTTP simples numa rede hostil.

A decodificação automática é o caminho que as equipes mais deixam escapar. O aviso do Bun observa que o fetch envia Accept-Encoding: br por padrão e decodifica a resposta, pelo que qualquer programa Bun que acesse URLs não confiáveis tem um caminho. Em Python, assim que brotli ou brotlicffi está instalado, o urllib3 decodifica automaticamente as respostas com Content-Encoding: br, e o requests também anuncia br nas suas requisições. Isto acontece mesmo que o seu próprio código nunca mencione o Brotli. Envoy e Istio só ficam expostos quando um descompressor Brotli está configurado.

3. Afetado confirmado versus apenas possível

Situação em 26 de outubro de 2026. Consulte os avisos indicados, porque a situação evolui rapidamente.

ComponenteSituaçãoCorrigido em
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 e anteriores (fim de vida)Não avaliado; o Node.js considera as linhas em fim de vida sempre afetadasMigrar para uma linha suportada
Bun (incluindo executáveis compilados)Confirmado: 1.0.21 a 1.4.31.4.4, e recompilar as aplicações compiladas
Python brotli / brotlicffiConfirmado: a partir de 1.0.31.2.1 / 1.2.1.0
Envoy (descompressor Brotli configurado)Confirmado1.40.0, 1.39.3, 1.38.6, 1.37.8 (1.36.12 no ramo 1.36, sem suporte)
Istio (descompressão Brotli ativada)Confirmado1.31.2, 1.30.6, 1.29.9 (a última versão 1.29)
Pacote brotli do UbuntuEm avaliação (todas as versões)Ainda sem pacote corrigido
Pacote nodejs do UbuntuVulnerável no 20.04 (Brotli incluído). A partir do 22.04 usa o Brotli do sistema, portanto segue a linha acimaVer o rastreador do Ubuntu
Pacote brotli do DebianCorrigido em bookworm e trixie; forky ainda não1.0.9-2+deb12u1, 1.1.0-2+deb13u1
Chrome, Electron e aplicações ElectronDesconhecido: nenhuma declaração do fornecedor encontradaAcompanhar as notas de versão do fornecedor

Sobre a última linha: o Chrome e o Electron trazem as suas próprias cópias do Brotli, e uma aplicação Electron também embute um runtime Node.js. No momento em que escrevemos, as versões estáveis mais recentes do Electron (45.0.0, 44.9.3 e 43.8.3, todas de 21 de outubro) indicam o Node.js 24.22.0, e não o 24.22.1, e as suas notas não mencionam esta CVE. As pré-versões do Electron de 23 de outubro trazem o Node.js 24.22.1, pelo que uma correção estável parece provável, mas ainda não foi publicada. Isso torna uma aplicação desktop construída sobre Electron possivelmente exposta, não vulnerável confirmada. O mesmo vale para clientes de chat e ferramentas desktop de IA: consulte a declaração de cada fornecedor antes de considerá-los afetados ou seguros.

Mais uma armadilha: a versão reportada de uma biblioteca não é prova em nenhum sentido. Um Node.js 22.23.4 corrigido continua a reportar process.versions.brotli como 1.1.0, porque a correção foi aplicada por backport. As distribuições fazem o mesmo. Avalie pela versão corrigida do produto ou pelo estado de segurança da distribuição, não pela string de versão do Brotli.

4. O que encontramos no nosso próprio ambiente

Fizemos um inventário apenas de leitura antes de alterar qualquer coisa. Eis o que observamos, de forma anonimizada, mantendo as constatações confirmadas separadas do risco que não conseguimos verificar.

Observações confirmadas

  • O Claude Code instalado já tinha sido atualizado, mas uma dúzia de sessões de longa duração ainda executava builds mais antigas, iniciadas semanas antes. Nunca tinham sido reiniciadas.
  • A nossa primeira contagem deixou escapar três delas. Executavam sob um processo auxiliar diferente, por isso uma pesquisa pela linha de comando não as encontrou. A pesquisa pelo executável encontrou-as.
  • Os arquivos das builds antigas já tinham sido apagados do disco pelo atualizador, pelo que não foi possível ler a versão do Bun embutida nesses processos.
  • Uma instalação remanescente, via gerenciador de pacotes, de uma build mais antiga do Claude Code embutia o Bun 1.4.0. Nada a usava em operação normal, mas um caminho de fallback poderia tê-la usado. Removemo-la depois de demonstrar que nada dependia dela.
  • Um runtime Node.js em fim de vida, sem uso, ainda estava instalado num servidor. Removemo-lo da mesma forma.
  • A build atual do Claude Code para a qual atualizamos (2.1.310) contém o Bun 1.4.4. Confirmamos isto inspecionando o binário. O changelog da Anthropic não menciona a CVE, por isso verifique a sua própria build da mesma forma.

Não verificado, tratado como risco

  • Se as sessões antigas eram de fato vulneráveis. A versão do Bun delas era ilegível. O Bun 1.4.4 corrigido só apareceu por volta de 22 de outubro, depois dessas builds, por isso tratamo-las como afetadas.
  • Se um atacante poderia ter alcançado o decodificador através dessas sessões, por exemplo ao acessar uma URL maliciosa. Este caminho é plausível dado o comportamento documentado do fetch do Bun, mas não o comprovamos.
  • Se outros softwares que incluem a sua própria cópia do Brotli são afetados. Onde não existe declaração do fornecedor, acompanhamos o produto em relação às versões do fornecedor, em vez de presumir que é seguro ou vulnerável.

Não encontramos nenhuma evidência de exploração. Também não tratamos isso como prova de segurança: a solução foi eliminar a exposição, não argumentar contra ela.

5. Lista de verificação para detecção e correção

Encontrar as dependências

Procure o Brotli onde ele vem incluído, não apenas onde está instalado como pacote. Verifique os seus SBOMs e imagens de contêiner em busca de Node.js, Bun, pacotes Python, Envoy e Brotli com ligação estática. Verifique as dependências transitivas do Python, porque o Brotli chega muitas vezes 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'

Encontrar os runtimes embutidos

Os programas compilados com bun build --compile trazem a sua própria cópia do Bun. O aviso do Bun indica que continuam afetados até serem recompilados com o 1.4.4. Atualizar o Bun na máquina de build não altera os binários já implantados.

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

Encontrar os processos em execução

Verifique o que está em execução, não o que está instalado. No Linux, um processo cujo executável foi substituído ou removido mostra (deleted). É um forte indício de que ainda executa uma build antiga.

# 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>

No macOS, lsof -p <pid> mostra o executável que um processo carregou. Faça a correspondência pelo executável, não pelo nome do processo, ou deixará escapar processos auxiliares e workers em segundo plano.

Corrigir, recompilar, reimplantar

  • Atualize para as versões corrigidas da tabela. Use o pacote corrigido da sua distribuição onde existir um backport.
  • Recompile as imagens de contêiner e os executáveis compilados e, em seguida, reimplante-os. Uma imagem base corrigida não serve de nada para uma imagem que você não recompilou.
  • Remova runtimes sem uso e instalações remanescentes, depois de comprovar que nenhum serviço, job ou script depende deles. Mantenha uma forma de reverter.
  • Onde ainda não existir correção, desative a decodificação Brotli de entradas não confiáveis onde for seguro fazê-lo. No Bun, fetch(url, { decompress: false }) funciona por requisição. Atribua a cada mitigação um responsável e uma data de término.

Reiniciar e verificar

Após reiniciar, verifique o executável e a versão do runtime efetivos do novo processo. Depois verifique o resultado de negócio: filas esvaziadas, jobs agendados concluídos com sucesso, alertas de monitoramento chegando. Um comando de atualização bem-sucedido prova apenas que o comando foi executado.

6. Reiniciar agentes de IA persistentes com segurança

Os agentes de IA de longa duração guardam estado: chamadas de ferramentas pendentes, aprovações, mensagens em fila e reivindicações de trabalho. Reiniciá-los todos de uma vez pode perder esse estado. Esta sequência funcionou para nós:

  1. Inventarie cada sessão: versão do executável, estado (ociosa, em espera ou ocupada), última atividade, trabalho pendente, aprovações que aguarda ou detém, eventos não confirmados e como será recuperada.
  2. Tire as aprovações da memória. Uma aprovação que existe apenas na conversa de uma sessão perde-se ao reiniciar. Antes de reiniciar, registre cada aprovação pendente (o que foi aprovado, por quem, para que alteração exata) num local durável, como o ticket ou o pull request. Depois, confirme que a sessão ainda a vê. Em caso de dúvida, peça a aprovação novamente em vez de a presumir.
  3. Comprove primeiro a substituição. Confirme que as sessões na build corrigida já funcionam e que um observador de eventos independente das sessões antigas está em execução, para que nenhum evento dependa de uma sessão ser reiniciada.
  4. Reinicie primeiro as sessões ociosas, uma de cada vez. Após cada uma, verifique que o processo antigo desapareceu, que o novo processo executa o executável corrigido, que o histórico da conversa está intacto e que qualquer conexão remota foi restabelecida. Só então passe à seguinte.
  5. Reinicie por último o responsável mais ocupado, entre tarefas. Peça-lhe que escreva uma nota de passagem, pare o seu observador apenas com zero eventos pendentes e zero não confirmados, reinicie e confirme que o observador retomou a partir da posição guardada sem perder nada.
  6. Conte com entradas em fila ao reconectar. Uma das nossas sessões reiniciadas recebeu uma mensagem que estava à espera havia dez dias e agiu com base nela. Verifique a caixa de entrada de cada sessão antes de reiniciar e avise o seu responsável.
  7. Pare e escale se algum passo não conseguir demonstrar que o trabalho foi recuperado. Não recorra a encerrar tudo à força.

Defina “concluído” antes de começar. Concluído significa nenhum processo em execução numa build afetada, cada exceção documentada com um responsável e uma data de nova verificação, e as verificações de negócio aprovadas. “O pacote foi atualizado” não é concluído.

Fontes