Loopback não é um limite de confiança: CVE-2026-2611
Em 19 de maio de 2026, o National Vulnerability Database publicou a CVE-2026-2611. O recurso Assistant do MLflow versão 3.9.0 tinha validação inadequada de origem nos endpoints locais /ajax-api. Uma página maliciosa aberta em qualquer aba podia enviar requisições de origem cruzada ao servidor local do Assistant, alterar sua configuração para ativar o modo de acesso total e então executar comandos arbitrários pela integração incluída com a CLI do Claude Code. A Huntr atribuiu à vulnerabilidade uma pontuação CVSS 3.0 de 9,6 (CRITICAL), com o vetor AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H: acessível pela rede, baixa complexidade, sem necessidade de privilégios, escopo alterado e impacto total em confidencialidade / integridade / disponibilidade.1
A causa raiz não é nova e a correção não é exótica, mas o modelo de ameaça mudou. Qualquer serviço local que escuta em loopback e expõe endpoints que alteram estado a uma aba do navegador cria uma superfície de limite de confiança. Quando esse serviço aciona um agente de IA capaz de ler arquivos, executar comandos de shell e fazer push de código, “apenas loopback” deixa de funcionar como controle de segurança. A CVE-2026-2611 é o caso didático de uma classe inteira de vulnerabilidades que vai aparecer repetidamente à medida que mais ferramentas de desenvolvimento incorporarem agentes de LLM em UIs web locais.
Resumo
A CVE-2026-2611 documenta um bug de enumeração incompleta de CORS no MLflow 3.9.0. O recurso Assistant expunha endpoints /ajax-api/ que a verificação is_api_endpoint() do MLflow não cobria, então o middleware de bloqueio de CORS os ignorava. Além disso, o CORSMiddleware legado permitia allow_origins=["*"], de modo que uma requisição de origem cruzada vinda de evil.com podia chamar /ajax-api/3.0/mlflow/assistant/config, colocar o Assistant em modo de acesso total e então enviar comandos de shell pela integração incluída com a CLI do Claude Code.1 A correção upstream (PR #20832, “Block CORS for ajax paths”) no MLflow 3.10.0 adiciona /ajax-api/ à enumeração de caminhos protegidos e restringe allow_origins à lista de permissões configurada mais uma regex de localhost.2 As defesas abaixo cobrem a classe de agentes de IA locais, não apenas o MLflow.
Principais pontos
| Papel | Ação |
|---|---|
| Engenheiros de IA/ML que usam MLflow | Atualize para o MLflow 3.10.0; audite qualquer outra UI local de agente de IA que escute em loopback |
| Times de segurança | Adicione “serviço local de agente de LLM em loopback” aos seus modelos de ameaça; uma lacuna de CORS equivale a RCE nessa classe |
| Autores de ferramentas que criam UIs locais de agentes | Enumere cada prefixo de caminho de API uma única vez; proíba allow_origins=["*"] mesmo em servidores restritos a localhost; proteja endpoints destrutivos com um gesto recente do usuário, não apenas com a origem |
| Líderes de engenharia | Isole os agentes de programação com IA do time em ambientes restritos e trate a capacidade deles de executar comandos como o verdadeiro raio de impacto |
Como é a cadeia de ataque
A cadeia de cinco etapas é curta. Cada etapa usa capacidades autorizadas; a composição falha.
| Etapa | Ação | O que o atacante controla |
|---|---|---|
| 1 | O usuário executa mlflow ui localmente; o Assistant escuta em loopback (padrão 127.0.0.1) |
Nada ainda (serviço local) |
| 2 | O usuário abre evil.com em qualquer aba enquanto o MLflow está em execução |
O conteúdo da página |
| 3 | evil.com envia fetch('http://127.0.0.1:5000/ajax-api/3.0/mlflow/assistant/config', { method: 'PUT', credentials: 'include', body: ... }) |
A requisição de origem cruzada |
| 4 | O MLflow aceita a requisição porque /ajax-api/ não estava na enumeração de caminhos com bloqueio de CORS; a configuração do Assistant muda para acesso total |
A configuração de ambiente de execução do Assistant |
| 5 | evil.com envia payloads de comando pelo endpoint de chat do Assistant; o MLflow invoca a CLI local do Claude Code com verificações de permissão burladas; os comandos rodam como o usuário |
Execução arbitrária de código |
O atacante não precisa instalar nada na máquina da vítima. Precisa apenas que a vítima carregue uma única aba com o payload enquanto mlflow ui está em execução. O ataque funciona a partir de qualquer origem porque o CORSMiddleware legado tinha allow_origins=["*"] e o middleware de bloqueio de CORS por caminho não cobria o prefixo /ajax-api/.2
A integração com o Claude Code é o amplificador de privilégios. O ClaudeCodeProvider do MLflow localiza a CLI local claude via shutil.which("claude") e instrui os usuários a instalá-la com npm install -g @anthropic-ai/claude-code quando ela não está presente. Quando a flag de configuração permissions.full_access do Assistant está ativada, o provider estende a invocação da CLI com --permission-mode bypassPermissions, o modo do Claude Code que suprime prompts interativos de permissão e permite que chamadas de ferramentas sejam executadas sem confirmação do usuário.3 As capacidades autorizadas do agente (shell, escrita de arquivos, HTTP) viram um shell remoto com UX em linguagem natural, exatamente o que um atacante quer.
A causa raiz é uma enumeração incompleta de caminhos
Ler o commit de correção torna o bug evidente. O security_utils.py do MLflow tinha uma constante:
API_PATH_PREFIX = "/api/"
e uma verificação:
def is_api_endpoint(path: str) -> bool:
return path.startswith(API_PATH_PREFIX) and path not in TEST_ENDPOINTS
O middleware de bloqueio de CORS chamava is_api_endpoint() para decidir se aplicaria validação de origem. Qualquer caminho que não começasse com /api/ passava como caminho não API. O Assistant introduziu /ajax-api/ para seus endpoints voltados ao navegador. Ninguém atualizou is_api_endpoint() para incluir o novo prefixo.
A correção é uma mudança comportamental de uma linha em security_utils.py:2
API_PATH_PREFIX = "/api/"
AJAX_API_PATH_PREFIX = "/ajax-api/"
def is_api_endpoint(path: str) -> bool:
return (
path.startswith(API_PATH_PREFIX) or path.startswith(AJAX_API_PATH_PREFIX)
) and path not in TEST_ENDPOINTS
Além disso, há uma correção paralela em fastapi_security.py que substitui allow_origins=["*"] no CORSMiddleware legado pela lista de permissões configurada mais uma regex de localhost. A mudança combinada tem 5 linhas adicionadas e 2 removidas em security_utils.py, e 5 adicionadas e 1 removida em fastapi_security.py.2
Essa é toda a vulnerabilidade. A CVE-2026-2611 aconteceu porque um novo prefixo de endpoint foi lançado sem que alguém atualizasse a única função que enumera quais caminhos recebem proteção de CORS. A superfície de ataque era uma constante de configuração esquecida.
Por que esse padrão vai se repetir
O bug é mais antigo que o MLflow e mais antigo que agentes de IA. Frameworks web cometem erros de enumeração de caminhos desde que enumeração de caminhos existe. O que mudou foi a consequência. Uma UI web local que esquecia de enumerar um endpoint costumava vazar alguns dados de configuração ou disparar uma negação de serviço. Uma UI web local que esquece de enumerar um endpoint enquanto roda um agente de programação com IA e acesso ao shell vaza a máquina inteira do desenvolvedor.
UIs locais de agentes de IA são uma categoria em crescimento. Qualquer ferramenta que embrulha um agente de LLM dentro de uma UI de navegador servida a partir de 127.0.0.1 entra no escopo: rastreadores de experimentos com painéis de chat (o formato do MLflow), plugins de IDE que acionam um agente por uma API em localhost, servidores em modo de desenvolvimento com os quais uma extensão se comunica por HTTP, dashboards web para executores locais de modelos. Cada uma escuta em loopback. Cada uma expõe endpoints que alteram estado a uma aba do navegador. Cada uma roda um agente com permissão para chamar shell, escrever arquivos e fazer push para o git. Todas estão a exatamente um bug de enumeração de caminhos de distância da CVE-2026-2611.
A premissa compartilhada por baixo dessa classe é: “a política de mesma origem do navegador vai proteger o servidor de loopback porque requisições legítimas também vêm de 127.0.0.1.” A premissa falha no momento em que qualquer barreira de origem cruzada (CORS, validação do cabeçalho Host, validação do cabeçalho Origin, cabeçalhos de metadados de fetch) deixa passar um prefixo de endpoint. O navegador não protege o servidor quando o servidor aceita explicitamente a requisição.
O ataque de egresso silencioso que cobri em março é a mesma premissa de confiança quebrada na outra direção. O egresso silencioso esconde a saída pelas ferramentas autorizadas do agente. A classificação incorreta de loopback esconde a entrada pelas chamadas de rede autorizadas do navegador. Ambos se reduzem a isto: as capacidades autorizadas de um agente de LLM agora são um primitivo de ataque sempre que uma única verificação de limite falha.
Como essa classe realmente se parece
A classe de vulnerabilidade tem quatro ingredientes. Um serviço é explorável quando os quatro existem:
- Servidor web local vinculado a loopback ou
0.0.0.0. A variante restrita a loopback é a mais comum. CORS, Host e Origin do navegador são o que protegem um serviço em localhost de uma aba de origem cruzada, não o vínculo de rede. - Endpoints que alteram estado acessíveis sem gesto do usuário. Mudanças de configuração, envios de comando, escrita de arquivos. Se um
fetch()de uma origem arbitrária consegue alterar estado, o serviço é alcançável. - Capacidade que o usuário local tem, mas uma página web não deveria ter. Ler o diretório home, executar comandos de shell, chamar APIs de nuvem com credenciais armazenadas, modificar arquivos-fonte.
- Uma verificação de limite ausente. Prefixo de caminho esquecido,
allow_originscuringa, validação ausente do cabeçalho Host, validação ausente de Origin, aplicação ausente de Sec-Fetch-Site.
A CVE-2026-2611 atingiu os quatro. A correção fecha o quarto ingrediente para o prefixo /ajax-api/ especificamente no MLflow. Os ingredientes um a três continuam presentes em todas as outras ferramentas locais de agentes de IA. A próxima CVE nessa classe terá a mesma aparência com outro nome de pacote.
Defesas contra a classe de loopback de agentes locais de IA
O conselho padrão de CORS (definir allow_origins como uma lista específica, habilitar credenciais apenas quando necessário) é necessário, mas não suficiente. A classe exige controles mais profundos.
Enumere prefixos de caminhos em um só lugar e escreva um teste que falhe quando um novo prefixo aparecer desprotegido. O bug do MLflow entrou porque o time atualizou is_api_endpoint() quando /api/ foi lançado e esqueceu de fazer o mesmo quando /ajax-api/ foi lançado. Um teste que percorresse todas as rotas registradas no app FastAPI, perguntasse a is_api_endpoint() se cada rota está protegida e falhasse em qualquer prefixo de caminho não reconhecido teria capturado o bug na hora do PR. A verificação custa cerca de 20 linhas de código de teste.
Valide os cabeçalhos Origin e Host, não apenas CORS. CORS protege navegadores da resposta do servidor. Ele não protege o servidor de aceitar a requisição. Serviços locais de agentes de IA devem rejeitar requisições cujo cabeçalho Origin não esteja na lista de permissões explícita e cujo cabeçalho Host não seja 127.0.0.1 ou localhost (este último para prevenir ataques de DNS rebinding). Ambas as verificações devem falhar fechadas.
Proteja endpoints destrutivos com Sec-Fetch-Site: same-origin. Navegadores enviam Sec-Fetch-Site: cross-site em qualquer fetch() de origem cruzada, inclusive de evil.com para 127.0.0.1. A especificação Fetch Metadata Request Headers define os valores do cabeçalho, e Chrome, Firefox, Safari e Edge o enviam.4 Um middleware que rejeita requisições a endpoints que alteram estado a menos que Sec-Fetch-Site: same-origin forneça uma camada adicional que não depende de lembrar todos os prefixos de caminho.
Isole a capacidade de execução do agente de IA. Claude Code, Cursor, Aider e Continue rodam com os privilégios completos do usuário por padrão porque essa é a UX mais fácil. Um ambiente restrito por workspace (bwrap com regras explícitas de montagem no Linux; isolamento de apps do macOS para ferramentas distribuídas como pacotes .app; contêiner por workspace para uso multiplataforma) reduz o raio de impacto sem sacrificar o fluxo de trabalho principal. A mitigação adiciona atrito. O atrito é o ponto.
Trate os commits do agente como não confiáveis até serem revisados. Proteção de branch, revisões obrigatórias, commits assinados. O modelo de “agente de IA com acesso a commit” desmorona no momento em que um atacante compromete o ambiente local do agente. A discordância do revisor (abordada em AI Code Review Needs Dissent) também se aplica aqui. Se o agente de IA e o revisor humano sempre concordam, o revisor humano é decoração.
Como deve ser a próxima CVE dessa classe
A pergunta interessante não é se a próxima vulnerabilidade no formato da CVE-2026-2611 vai aparecer. A pergunta interessante é qual pacote vai publicá-la primeiro. Os candidatos estão espalhados pelo ecossistema de ferramentas locais de IA: rastreadores de experimentos que incorporam um agente na UI, plugins de IDE que conversam com um backend local de agente por HTTP, servidores em modo de desenvolvimento que alguma ferramenta abre durante uma sessão, APIs em localhost que extensões usam para acionar UIs de agentes. Nenhum deles publicou uma CVE com esse formato ainda. Vários distribuem caminhos de código que escutam em loopback e aceitam requisições de origem cruzada usando o navegador como barreira de confiança.
A classe é maior que a CVE-2026-2611, e o modelo de ameaça mudou. Em 2026, a máquina de um desenvolvedor hospeda um agente de IA que consegue chamar shell, escrever arquivos, fazer push de commits e chamar APIs de nuvem. As UIs web locais que acionam esses agentes herdam os privilégios do agente pelo limite de confiança que o servidor HTTP da UI aplica. Uma configuração incorreta de CORS em um framework web de 2020 vazava alguns dados de configuração. Uma configuração incorreta de CORS em uma UI local de agente de IA de 2026 vaza a máquina inteira do desenvolvedor, mais a capacidade de deploy em produção.
A correção não é um patch. A correção é uma classe de controles: enumeração explícita de caminhos com testes, validação de cabeçalhos em profundidade, isolamento de capacidade e disciplina de revisão em commits escritos por agentes. Ferramentas que entregam um sem os outros entregam a sucessora da CVE-2026-2611 com um prefixo diferente.
FAQ
O que é CVE-2026-2611?
A CVE-2026-2611 é o identificador do National Vulnerability Database para uma falha de validação inadequada de origem no recurso Assistant do MLflow 3.9.0. A falha permite que qualquer página web faça requisições de origem cruzada ao endpoint local /ajax-api do Assistant, altere sua configuração para ativar o modo de acesso total e então execute comandos arbitrários pela integração incluída com a CLI do Claude Code. Pontuação base CVSS 3.0 de 9,6 (CRITICAL). Corrigida no MLflow 3.10.0 via PR #20832, “Block CORS for ajax paths.”12
Qual foi a causa raiz?
A verificação is_api_endpoint() do MLflow enumerava apenas o prefixo de caminho /api/. Quando o recurso Assistant introduziu endpoints /ajax-api/, ninguém atualizou a enumeração. O middleware de bloqueio de CORS então deixou passar requisições de origem cruzada para /ajax-api/ sem nenhuma verificação de origem. Uma segunda fraqueza agravou o bug: o CORSMiddleware legado permitia allow_origins=["*"], então até caminhos não bloqueados devolviam cabeçalhos CORS permissivos para qualquer origem. A correção adiciona AJAX_API_PATH_PREFIX = "/ajax-api/" à verificação de caminho e substitui allow_origins=["*"] pela lista de permissões configurada mais uma regex de localhost.2
Como sei se minha instalação do MLflow foi afetada?
Execute pip show mlflow | grep Version. As versões do MLflow >=3.9.0,<3.10.0 são afetadas segundo o intervalo CPE do NVD. O MLflow 3.10.0 (lançado em 20 de fevereiro de 2026) traz a correção. Atualize com pip install --upgrade mlflow e reinicie quaisquer processos mlflow ui em execução.
O ataque funciona sem a CLI do Claude Code instalada?
A etapa de alteração de configuração funciona de qualquer forma. A etapa de execução arbitrária de código exige que a CLI do Claude Code esteja presente no PATH da vítima, porque o Assistant do MLflow a invoca via shutil.which("claude"). Sem a CLI, o pior caso passa a ser divulgação de informações e adulteração arbitrária de configuração, em vez de RCE. Para usuários que configuraram o backend Claude Code (o caminho documentado de configuração do Assistant), a exposição prática é RCE.
Como protejo outros serviços locais de agentes de IA?
Audite os quatro ingredientes: vínculo a loopback ou curinga, endpoints que alteram estado, capacidades no nível do agente e qualquer verificação de limite que possa ter lacunas. Adicione listas de permissões explícitas para Origin, validação do cabeçalho Host contra 127.0.0.1/localhost (para derrotar DNS rebinding) e barreiras Sec-Fetch-Site: same-origin em endpoints destrutivos. Isole a capacidade de execução do agente com bwrap, isolamento de apps do macOS ou um contêiner por workspace. Trate os commits git do agente como não confiáveis até serem revisados.
-
National Vulnerability Database. “CVE-2026-2611: MLflow Assistant improper origin validation.” Publicado em 19 de maio de 2026. Pontuação base CVSS 3.0 de 9,6 (CRITICAL), vetor
CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H, emitida pela Huntr. Correção incluída no MLflow 3.10.0. https://nvd.nist.gov/vuln/detail/CVE-2026-2611. Relatório original: https://huntr.com/bounties/8462addd-b464-4a84-b6a2-5529604e6e5a. ↩↩↩ -
MLflow. “Block CORS for ajax paths (#20832).” Mesclado em 16 de fevereiro de 2026 por Tomu Hirata. Arquivos alterados:
mlflow/server/fastapi_security.py(+5/-1),mlflow/server/security_utils.py(+5/-2), além de atualizações de testes. O diff substitui a verificação de prefixo único deis_api_endpoint()por uma verificação de dois prefixos e restringe oallow_originsdoCORSMiddlewarelegado de["*"]para a lista de permissões configurada mais uma regex de localhost. https://github.com/mlflow/mlflow/commit/8f9c8a53af90842944101eb8b7d60706822c81bc. ↩↩↩↩↩↩ -
Código-fonte do MLflow 3.10.0.
mlflow/assistant/providers/claude_code.py: busca porshutil.which("claude")(linhas 268, 280, 355); dica de instalação"Install it with: npm install -g @anthropic-ai/claude-code"(linha 286); escalonamento de modo de permissãocmd.extend(["--permission-mode", "bypassPermissions"])condicionado aconfig.permissions.full_access(linhas 379–381).mlflow/assistant/config.py: padrãoPermissionsConfig.full_access: bool = False(linha 15).mlflow/server/assistant/api.py: prefixo do router/ajax-api/3.0/mlflow/assistant(linha 63); leitorGET /config(linha 261); mutadorPUT /config(linha 276), o endpoint PUT que o ataque de origem cruzada mira para ativarfull_access. Verificado contra o sdist do MLflow 3.10.0 no PyPI (mlflow-3.10.0.tar.gz). ↩ -
W3C. “Fetch Metadata Request Headers.” Define
Sec-Fetch-Sitee seus valorescross-site,same-origin,same-site,none. Implementado por Chrome 76+, Firefox 90+, Safari 16.4+ e Edge 79+. https://www.w3.org/TR/fetch-metadata/. ↩