O notebook trouxe o próprio servidor MCP
CVE-2026-75149 é uma falha de injeção de código no marimo: um notebook é um único arquivo Python, esse arquivo carrega a própria configuração num bloco de comentários e, até a versão 0.23.15, essa configuração podia declarar um servidor MCP. Bastava abrir o notebook em modo de edição para o marimo lançar o comando do atacante como subprocesso, antes de qualquer célula rodar. O patch conta o resto. Ele remove cinco categorias inteiras de ambiente que um documento baixado tinha permissão para definir, com precedência maior que a configuração do operador. No Bromure Agentic Coding, a lista de programas que seu agente pode lançar é um painel do lado do host, escrito na VM no boot, e nada dentro do workspace consegue acrescentar nada a ela.
Um repositório pode reconfigurar seu agente de codificação. Isso a gente já sabia. O que mudou foi o tamanho da coisa capaz de fazer isso. Agora é um único arquivo, encaminhado para você no Slack, que você abre só para dar uma olhada.
Alguém manda um notebook para você. É um notebook marimo, ou seja, um arquivo
.py comum em vez de um pacote de JSON com saídas em base64 dentro, que é quase
toda a razão de as pessoas gostarem do formato. Você consegue ler, consegue
fazer diff e consegue colocar no git sem um hook de pre-commit para tirar as
saídas.
Então você lê, parece tudo certo, e abre em modo de edição para mexer numa célula.
Um subprocesso começa. Você ainda não rodou nenhuma célula; isso é uma coisa que o arquivo pediu por conta própria, antes de o notebook terminar de carregar.
Isso é a CVE-2026-75149, publicada em 19 de agosto e repercutida esta semana pelo The Hacker News. CVSS 8.7, CWE-94, creditada a Gregory Tan, corrigida no marimo 0.23.15. O aviso da VulnCheck resume o mecanismo em uma linha: o notebook contém uma entrada de servidor MCP forjada, e abri-lo em modo de edição lança o comando dessa entrada como subprocesso local.
A configuração estava num comentário
Notebooks marimo carregam as próprias configurações usando
PEP 723, a convenção Python para metadados
de script embutidos. É uma pequena tabela TOML que vive dentro de um bloco de
comentários no topo do arquivo, e existe para que um script possa declarar suas
dependências e sua versão de Python sem um arquivo de projeto ao lado. O uv lê
isso. O marimo também, e estende com uma tabela tool.marimo para a
configuração do notebook.
Duas propriedades dessa tabela causaram o estrago, e nenhuma delas é um bug por si só.
A primeira: o marimo mesclava os metadados do notebook na precedência mais alta, acima da configuração definida por quem estava rodando o marimo. Esse é o padrão correto para uma preferência de formatação. Um notebook que quer indentação de dois espaços deve ganhar da sua configuração global, porque o autor sabe como o arquivo dele é e você não.
A segunda: a mesclagem era quase sem filtro. Antes da correção, o sanitizador do
marimo removia exatamente uma chave da configuração vinda do notebook:
tool.marimo.runtime.auto_instantiate. Todo o resto de tool.marimo passava,
inclusive tool.marimo.mcp. É ali que você lista servidores Model Context
Protocol, e uma lista de servidores MCP é uma lista de comandos a lançar.
Junte as duas coisas e um comentário no topo de um arquivo baixado podia nomear um programa e fazer o marimo iniciá-lo. Nada aqui é um exploit. O arquivo usou o recurso exatamente como ele foi projetado, e o projeto presumia que você tinha escrito o arquivo.
O patch é a divulgação
A correção, PR #10281, se chama “additional pep 723 sanitization”, e troca uma lista de bloqueio por uma lista de permissão. A configuração vinda do notebook agora fica restrita a seções cosméticas e de editor: formatação, salvamento, exibição, atalhos de teclado, diagnósticos, lint, snippets, fontes de dados, servidores de linguagem, compartilhamento, venv, runtime, gerenciamento de pacotes.
As seções que caíram são a metade interessante: ai, mcp, completion,
secrets, server.
São cinco seções inteiras, não cinco chaves, e um documento que você baixou
podia definir todas elas. A justificativa do PR também detalha uma segunda carga
que nunca precisou de subprocesso. A seção ai carrega URLs base de API, então
um notebook podia reapontar o endpoint do modelo para um host à escolha dele, o
que, nas palavras dos mantenedores, “poderia permitir a exfiltração das chaves
de API do operador para endpoints controlados pelo atacante”.
Cobrimos a variante de URL base desse ataque em julho, quando
um repositório podia definir ANTHROPIC_BASE_URL
e o Claude Code mandava a própria chave para o endereço que o repositório
escolhesse. É o ataque mais barato dessa categoria, porque nenhum código roda e
quem faz o envio é o cliente da própria vítima. A variante do marimo encolhe o
veículo de entrega de um repositório para um comentário.
A unidade de confiança não para de encolher
Essa classe de bug tem uma história curta, e nessa história o atacante precisa de menos da sua máquina a cada vez.
Em abril, a Wiz Research reportou a
CVE-2026-12957
no Amazon Q Developer, CVSS 8.5, corrigida em maio e divulgada em junho. O
Amazon Q lia .amazonq/mcp.json de um workspace aberto e lançava os servidores
que ele definia. Esses processos herdavam o ambiente inteiro do desenvolvedor:
chaves AWS, tokens de CLI de nuvem, segredos de API, sockets do agente SSH. A
correção da Amazon foi perguntar antes de rodar um servidor MCP a partir de um
workspace não confiável.
Em agosto, o ChainDrop
commitava configuração de agente nos repositórios por onde passava:
um hook SessionStart em .claude/settings.json e uma tarefa folderOpen em
.vscode/tasks.json, de modo que clonar o projeto e abrir já bastava.
Os dois precisavam de um repositório. Alguém tinha que publicar ou comprometer um projeto, você tinha que cloná-lo, e um diretório de dotfiles ficava ali na árvore, onde uma pessoa desconfiada podia olhar.
O marimo precisa de um arquivo. Um arquivo, que se apresenta como um documento e que chega como documentos chegam: uma thread do Slack, um anexo de e-mail, um gist que alguém linkou na daily, um download do Kaggle. Você não clona um notebook. Você abre, porque abrir é como se descobre o que é.
O bloco mcp se espalhou pela maior parte do ferramental de desenvolvimento nos
últimos dezoito meses, e esses blocos vivem em arquivos que viajam entre
pessoas: raízes de repositório, diretórios de editor e agora metadados de
documento. Cada um é uma lista de programas que algo na sua máquina está
disposto a iniciar, escrita por quem tocou no arquivo por último. Scanners de
registro não os leem, lockfiles não os cobrem, e ninguém propôs uma atestação de
proveniência que se aplique a um comentário.
De onde vêm os servidores MCP num perfil
O Bromure Agentic Coding responde à pergunta da proveniência onde ela pode ser respondida: no host, antes de a VM subir.
Cada perfil tem um painel MCP nas configurações, ao lado de Agents, Credenciais, Guardrails e Supply Chain. Ele guarda uma lista de servidores. Cada entrada liga e desliga de forma independente e usa um de dois transportes: HTTP, uma URL remota com um bearer token opcional, ou stdio, um comando local lançado dentro da VM. O Bromure traduz essa lista para o formato que o agente ativo espera, JSON para o Claude Code e TOML para o Codex, e injeta na VM no boot.
Leia a direção da viagem nessa última frase. A configuração vai para dentro do convidado, a partir do host, no boot, saindo de uma lista que você mantém num painel de configurações. Um arquivo que aparece no workspace depois disso não é um lugar de onde servidores MCP venham, então não existe mesclagem cuja precedência dê para errar nem sanitizador para manter em sincronia com um esquema de configuração que não para de ganhar seções. Um documento não consegue acrescentar uma entrada a uma lista que ele não alcança.
Agora abra o notebook mesmo assim
Nada disso ajuda no dia em que você está rodando uma versão que tem o bug. Então pegue o marimo anterior à 0.23.15, entregue a vitória ao atacante e faça um notebook hostil passar por um perfil.
O subprocesso começa dentro do computador de outra pessoa. O trabalho de
sessão no Bromure Agentic Coding acontece em abas do kitty dentro da VM Ubuntu
do perfil, a um hipervisor de distância do macOS. O marimo edit roda ali, e o
comando do comentário também. Em Recursos → Armazenamento o perfil são três
camadas: um /home/ubuntu por perfil com Apagar home…, um disco de sistema do
workspace com Redefinir para a base… que reclona a partir da imagem
compartilhada, e embaixo um SO base somente leitura em que nada do convidado
consegue tocar. Um item de menu leva embora tudo o que a entrada instalou.
O ambiente que ele herda é um conjunto de iscas. O bug do Amazon Q doeu
porque o servidor lançado era filho do shell real do desenvolvedor e herdava as
credenciais reais dele. Num perfil do Bromure não há nada por trás desses nomes
de variável. Chaves de API genéricas são marcadores brm_… exportados para
dentro da VM e trocados pelos valores reais pelo proxy do host no caminho de
saída. O kubeconfig é sintético, com certificados de cliente descartáveis.
Requisições AWS são reassinadas no host, de modo que um processo que contorna o
proxy recebe InvalidSignatureException em vez de acesso. O
~/.docker/config.json guarda um blob base64 falso. Chaves SSH privadas
nunca estão na VM, porque o host
assina através de um agente por perfil. Ligue Exigir aprovação para usar
numa credencial e cada troca vira um diálogo no host com uma concessão limitada
no tempo: cinco minutos, uma hora, o resto da sessão.
O truque da URL base colhe um marcador. O proxy substitui uma credencial
real apenas em requisições de saída para o host daquela credencial. Um notebook
que reaponta um endpoint de IA para attacker.example colhe o que está no
ambiente da VM, que é uma string brm_… que não abre nada.
O servidor precisa alcançar a rede. Uma entrada MCP que dispara algo útil
para um atacante precisa de saída, seja para buscar um estágio posterior, seja
para mandar de volta o que encontrou. Guardrails → Conexões de saída é uma
tabela de regras no estilo pf: uma ação, um protocolo (tcp, udp, web,
any), um host ou CIDR, uma lista de portas e, para web, uma lista de métodos
HTTP, avaliadas de cima para baixo com a primeira correspondência vencendo, mais
uma configuração Tráfego não correspondente em Permitir ou Negar. Coloque em
Negar, liste os hosts de que seu trabalho precisa, e o destino da entrada não
está na lista. A tabela é aplicada no host, no switch virtual e no proxy, então
nada que o convidado faça no próprio roteamento muda a resposta.
O arquivo precisa estar na VM, para começar. Um perfil compartilha no máximo
oito pastas do Mac, cada uma escolhida à mão no painel Pastas e montada em
/home/ubuntu/<basename>. Um notebook que caiu em ~/Downloads não está no
perfil até você colocá-lo lá, e o resto do seu Mac não fica a uma caminhada de
diretórios daquilo que o notebook iniciou.
E você consegue ver acontecer. A janela do Log de Segurança (Janela → Log de Segurança…) é uma única tabela cronológica do lado do host: veredictos de pacotes, decisões de firewall, trocas de credenciais, detecções de injeção de prompt. Código convidado não consegue editá-la, porque código convidado não consegue alcançá-la. Uma conexão de saída bloqueada vinda de um processo que você não iniciou aparece ali como uma linha, o que é melhor do que descobrir por uma fatura de nuvem seis semanas depois.
Ligue isso
Atualize o marimo para 0.23.15 ou mais novo; a lista de permissão que está lá é
um bom patch. Depois vá olhar todos os outros lugares de onde o seu ferramental
lê um bloco mcp, e pergunte quem tem o direito de escrever aquele arquivo.
Num perfil, as configurações que valem dois minutos são a mesma lista curta de sempre. Guardrails → Conexões de saída, com Tráfego não correspondente em Negar e uma lista de permissão para os hosts de que seu trabalho precisa. Credenciais → Exigir aprovação para usar em tudo que possa gastar dinheiro ou apagar dados. Supply Chain → Verificação de vulnerabilidades OSV, e filtragem socket.dev ou Delpi se você tiver chave, por cima da trava de idade de dois dias que já vem ligada. Prompt Injection → o scanner de CLAUDE.md e AGENTS.md, que trata os arquivos que reivindicam autoridade sobre o seu agente exatamente como isso.
Notebooks vão continuar sendo encaminhados, e formatos de configuração vão continuar ganhando seções, porque as duas coisas são úteis. O que você pode mudar é qual máquina está ouvindo quando um documento pede que um programa seja iniciado. Instale o Bromure Agentic Coding, mantenha sua lista de servidores no editor de perfis, e deixe o próximo bloco de comentários prestativo configurar uma VM que você pode apagar por um item de menu.