Radar Tech: o CVE que mora no seu grafo de dependências e não no seu código
Uma falha crítica no decodificador Brotli atravessou Node.js, Python, curl, Bun, Envoy e Istio em 48 horas — e as ferramentas de varredura não a viram. O incidente revela o componente sem dono que quase todo time tem: capacidade de runtime herdada de uma dependência transitiva.
O que aconteceu
O CVE-2026-111497 saiu pelo Node.js e ganhou apelido de node2shell: falha de escrita fora dos limites (out-of-bounds write) no decodificador de Brotli da Google, CVSS 4.0 de 9.2. Em 24 horas, a mesma falha atravessou o ecossistema: pacotes Python, libbrotli, curl, Bun, Envoy e Istio. Patches existem, mas o detalhe que importa para arquitetura não é a lista de versões — é que a falha estava no seu grafo de dependências sem aparecer em nenhum lugar que você lê.
Três fatos do incidente merecem atenção de quem desenha sistema:
- Ele é bidirecional. Um cliente malicioso derruba o servidor; um servidor malicioso derruba o cliente que aceita resposta comprimida. Não é um problema de borda, é de toda chamada HTTP de saída.
- Não requer import. Se
Brotlioubrotlicffiestiver instalado,requests,httpx,urllib3eaiohttppassam a anunciarAccept-Encoding: bre a decodificar respostas sozinhos. O código da aplicação nunca menciona Brotli. - A ferramenta de varredura não vê. Na data do anúncio, o CVE não estava mapeado para nenhum dos dois pacotes PyPI.
pip-audit, Dependabot e afins reportavam limpo enquanto o decodificador vulnerável rodava em produção.
A raiz técnica é prosaica e por isso vale ser contada: uma função que decide o tipo de bloco retornava um booleano, e falso significava tanto "o fluxo está malformado" quanto "preciso de mais bytes". O chamador não conseguia distinguir as duas coisas — e no caminho rápido descartava o retorno inteiro. Fluxo forjado força uma troca de bloco impossível, o decodificador continua com o estado antigo e escreve fora do buffer. O bug foi encontrado em fevereiro e corrigido no código-fonte, mas ninguém o classificou como falha de segurança, então nenhum release levou o conserto até esta semana. Um conserto que não vira release não protege ninguém.
O problema de arquitetura: o decodificador tem dono?
Quando a notícia chega, a pergunta habitual é "quais serviços usam Brotli?". Ela é a pergunta errada, e é por isso que a resposta sai vazia ou absurda — às vezes os dois.
O componente vulnerável não é código seu. É capacidade de runtime herdada de uma dependência transitiva, carregada por um extra (httpx[brotli]), por um wheel compilado numa imagem que ninguém reconstrói, ou pelo pacote do sistema operacional que veio com o vendor. Ele tem três propriedades que o tornam invisível:
- Não aparece no código. Não há
import brotlipara ogrepachar. O caminho de execução é escolhido por um header (Content-Encoding: br) que um servidor externo decide enviar. - Não aparece na política. SBOM lista pacote, raramente lista quem decidiu aceitar aquele formato. "Temos SBOM" e "sabemos onde decodificamos Brotli" são afirmações diferentes.
- Muda de dono. Numa imagem, o dono é o time da imagem; num servidor gerenciado, é o fornecedor; num binário compilado, é quem assina o build. Três notificações possíveis, nenhuma garantida.
A consequência é a pergunta de arquitetura que realmente decide o incidente: quem é o dono do formato que você aceita decodificar? Se nenhuma pessoa responde com nome, você não tem um problema de patch — tem um componente sem dono, e o próximo CVE de descompressão vai encontrar exatamente o mesmo buraco.
Descompressão é superfície de ataque, não otimização
Este é o terceiro incidente na mesma família em poucos anos — de bibliotecas de compactação a parsers de formato e cascatas de erro em middleware. O padrão se repete porque a compressão é tratada como detalhe de performance: alguém liga o suporte para economizar banda, ninguém documenta, e o decodificador passa a aceitar bytes controlados por terceiros.
A regra que sobrevive ao próximo CVE é estreita e desconfortável:
- Todo decoder que toca bytes de fora é código de segurança — merece dono, inventário e teste com entrada malformada, igual a um parser de protocolo.
- Aceitar um formato é uma decisão de arquitetura, não um flag de performance.
Accept-EncodingeContent-Encodingsão política de fronteira. - Desligar é mais rápido que patchar. Enquanto não há patch, rejeitar
brna borda e remover o formato do que você anuncia resolve a exposição — mas só se o controle estiver antes do decoder, não depois da resposta baixada. - Fallback não é garantido. Tirar o suporte pode virar erro de aplicação quando o servidor insiste em Brotli. Isso é um modo de falha que precisa ser testado, não descoberto em produção.
O caminho que fecha a classe de falha
O patch (Brotli 1.2.1, brotlicffi 1.2.1.0, runtimes atualizados) é obrigatório e é a parte fácil. A parte que fica é eliminar a cegueira:
- Inventário por caminho de decodificação, não por pacote: quais serviços aceitam corpo comprimido, quais chamam URLs de terceiros com resposta comprimida, quais imagens embutem decodificador nativo.
- Ordem por exposição real: primeiro quem aceita requisição de fora ou busca URL arbitrária (webhook, preview, crawler, importador), depois o resto.
- Evidência de fechamento: versão efetiva em runtime, não lockfile. Imagem reconstruída, pod reiniciado,
Content-Encoding: brrejeitado e comprovado por teste. - Governança da exceção: todo serviço que ficar exposto por decisão consciente precisa de prazo e nome. Exposição sem prazo é esquecimento com outro nome.
O que fazer hoje com o dossiê que você tem
- Liste cada serviço com suas superfícies de decodificação: entrada (aceita
br?), saída (baixa de terceiros?), imagem (decoder nativo embutido?), binário (compilado com o runtime antigo?). - Para cada linha, marque quem é o dono do patch — seu time, o vendor ou ninguém. "Ninguém" é o achado mais importante do laudo.
- Classifique a exposição antes da versão: aceita corpo externo é mais urgente que servidor interno que só fala com serviços conhecidos.
- Escreva a mitigação compensatória com o ponto exato de inserção — na borda, antes de qualquer processamento de corpo.
- Registre a decisão (aceitar ou não formatos comprimidos de terceiros, e como isso é revisto) com o risco residual e o dono das lacunas.
Nada disso é resposta a este CVE. É o mesmo raciocínio para qualquer decodificador herdado — imagem, fonte, archive, serialização — e é por isso que vale ter o documento pronto antes do próximo nome de shell aparecer no feed.