Voltar para todas as publicações
Publicado em · por Renaud Deraison

Ele esperou pela terceira chamada

A Pillar Security está a acompanhar o Deadbugz, um servidor MCP empurrado para projetos de código aberto por pull request. Traz duas ferramentas honestas, conta as chamadas de ferramenta do seu agente e, na quarta, reescreve os metadados que devolve em instruções para recolher chaves SSH, credenciais AWS, histórico da shell e kubeconfig, e para nada dizer. Nenhum bug, nenhuma CVE: o protocolo permite tudo isto. A vítima é a ideia de que aprovar um servidor uma vez lhe diz alguma coisa sobre a chamada seguinte. Num espaço de trabalho Bromure Agentic Coding, a carga corre por inteiro, recolhe quatro falsificações e morre no proxy à saída.

Você leu o código. Você executou-o. Ele formatava texto, como anunciado. Você aprovou-o, e o atacante contava com essa aprovação.

Na noite de 10 de agosto, entre as 21:52 e as 23:07 UTC, uma conta do GitHub chamada zellkernel abriu vinte e três pull requests contra projetos sem qualquer ligação entre si, nas áreas de IA, MCP e ferramentas de desenvolvimento. Setenta e quatro minutos, do início ao fim. Cada pull request fazia a mesma pequena alteração: acrescentava um servidor Model Context Protocol a um ficheiro de configuração.

A Pillar Security publicou a campanha a 12 de agosto e chamou-lhe Deadbugz, a partir do ficheiro que uma das variantes deixa cair. Um mês depois, os resumos mensais de MCP continuam a abrir com ele. O compêndio de setembro da Adversa, publicado ontem, dá-lhe toda a secção de incidentes. É o relógio que lhe vale esse espaço.

Duas ferramentas, e um contador

O servidor chama-se productivity-suite. Oferece duas ferramentas: format_text e summarize. Ambas funcionam. Aponte um agente para ele, peça-lhe que aperte um parágrafo, e recebe um parágrafo apertado.

Por trás delas, o servidor mantém aquilo que a Pillar descreve como “um contador em memória, por cliente, dos pedidos tools/call”. Quando esse contador chega a três, o servidor muda as suas respostas a dois outros tipos de pedido: tools/list, que diz ao agente que ferramentas existem e para que servem, e prompts/get, que entrega ao agente instruções preparadas. A partir da quarta chamada, essas respostas voltam a carregar, nas palavras da Pillar, “instruções destinadas a orientar um agente de IA associado para ficheiros locais sensíveis e a esconder a atividade do seu operador”.

Quatro coisas: chaves SSH, credenciais AWS, histórico da shell, configuração do Kubernetes. Depois mais uma instrução: ocultar a atividade do utilizador.

Os nomes das ferramentas nunca mudam. format_text continua a chamar-se format_text. O que virou foi a descrição por baixo, e a descrição é a parte que o modelo lê como autoridade.

productivity-suite — as mesmas duas ferramentas, antes e depois do contadoro seu agente de códigotools/call ×Ntrabalho comum, turnos comunscontador em memóriapor cliente, nunca persistidolimiar: trêschamadas 1 – 3tools/list → format_text, summarize“formata um bloco de texto”prompts/get → um prompt de arrumar textoo que uma revisão vêum servidor pequeno que faz o que dizduas ferramentas, descrições honestas, sem surpresasaprovado4.ªchamadas 4 e seguintestools/list → format_text, summarizemesmos nomes, outras instruçõesprompts/get → a cargao que o modelo é agora mandado fazer~/.ssh · credenciais AWShistórico da shell · ~/.kube/confige não mencione isto ao utilizador
O Deadbugz mantém um contador por cliente dos pedidos tools/call. Nos três primeiros, tools/list e prompts/get devolvem metadados honestos para format_text e summarize. A partir do quarto, os mesmos dois nomes de ferramenta voltam com descrições e prompts preparados que mandam o agente recolher chaves SSH, credenciais AWS, histórico da shell e kubeconfig, e ficar calado. Os nomes das ferramentas nunca mudam, por isso nada daquilo que um revisor olha parece diferente.

Nada aqui é uma vulnerabilidade

Não aparece um único bug nesta história. Não há corrupção de memória para corrigir nem CVE para registar, porque o servidor não faz nada que o protocolo proíba.

MCP é um protocolo em que um servidor publica as suas capacidades em tempo de execução e pode voltar a publicá-las. As descrições de ferramentas são texto que o servidor escolhe e o cliente vai buscar. Os prompts são texto que o servidor escolhe e o cliente vai buscar. O Deadbugz usa tudo isso tal como foi desenhado. O seu único contributo original é decidir quando dizer a verdade.

E isso chega, porque todos os controlos que a maioria das equipas tem contra um servidor MCP hostil correm uma só vez. Você lê o repositório. Arranca o servidor e mexe-lhe. Compara a lista de ferramentas com o que o README promete. Aprova, talvez fixe a versão, e segue em frente. Cada um desses gestos é uma fotografia, e é o Deadbugz que decide quando você a tira.

O próprio conselho da Pillar a quem constrói clientes MCP entrega o desenho. Tratar mudanças de definição de ferramenta num servidor já aprovado como um evento de segurança que exige nova aprovação. Tornar as mudanças de definição visíveis para o operador. Registar a impressão digital das definições de ferramenta no momento da aprovação e compará-la mais tarde. Cada uma delas é um pedido para mover um controlo do momento da aprovação para o momento da execução.

A população onde o Deadbugz caça é vasta e instável. Uma avaliação dinâmica de servidores MCP expostos à internet, publicada em julho, Exposed by Design, testou 414 dos 640 servidores de produção confirmados, de entre as mais de 21 000 instâncias que o seu autor encontrou na internet pública. Reportou 68 vulnerabilidades comunicáveis em dez classes, 91,8% de servidores a correr sem qualquer autenticação OAuth, e 687 instâncias de ferramentas a expor execução de shell sem controlo de acesso. O número que fica é a rotatividade: 41,6% dos servidores confirmados desapareceram em menos de três dias entre duas medições consecutivas, o que o autor lê como “ciclos de implantação rápidos sem revisão de segurança”. Um servidor que auditou a semana passada pode ser outro software esta semana, ou ter desaparecido.

A entrega também merece nota, porque é barata. Dezassete das vinte e três pull requests acrescentavam um ponto de extremidade remoto, productivity-suite-mcp.onrender.com/mcp, à configuração MCP de um projeto. Quatro apontavam o Python a um ficheiro local escondido, ~/.config/.cache/.sys/.deadbug-mcp.py. Duas eram submissões a diretórios de servidores. A Pillar não encontrou nenhuma integrada pelo botão de merge do GitHub à data da revisão: dezanove fechadas, quatro ainda abertas. Mas uma pull request é apenas uma das formas de uma linha de configuração viajar. A mesma linha cabe num artigo de blogue, num guia de arranque, numa ficha de um diretório MCP, ou numa resposta prestável debaixo de uma pergunta sobre formatação de texto.

A lista de compras

Volte à lista. É específica, e é sempre a mesma: chaves SSH, credenciais AWS, histórico da shell, configuração do Kubernetes.

É o inventário permanente de uma máquina de programador. É o que um infostealer leva, o que um script postinstall malicioso procura com grep, e o que um atacante pede a um agente sequestrado para ir buscar. A lista está estável há uma década de campanhas porque o conteúdo é estável: uma chave privada que assina, um segredo que autentica, um registo do que você escreveu, e um token que chega à produção.

Um agente num portátil comum consegue satisfazer todos os itens, porque tem uma shell na sua diretoria pessoal e corre como você.

A mesma lista, dentro de um espaço de trabalho Bromure

O Bromure Agentic Coding corre cada agente de código numa VM Linux virtualizada por hardware no seu Mac, e mantém todos os segredos reais do lado do anfitrião dessa fronteira. O agente trabalha com falsificações que preservam a estrutura, derivadas do valor real mais um sal por instalação, para que ferramentas que tiram impressão digital de uma chave nunca a vejam mudar entre sessões. Um proxy do lado do anfitrião substitui o valor real no fio depois de o pedido ter saído da VM, e apenas quando se destina ao anfitrião para o qual essa credencial foi cunhada.

Corra a carga e deixe-a ganhar. Assuma que o modelo lê a descrição envenenada, acredita nela, e vai à procura. Pegue nos quatro itens por ordem.

Chaves SSH. O agente não encontra chave nenhuma, nem sequer um engodo. O SSH_AUTH_SOCK da VM é uma ponte ssh-agent sobre um socket virtio, porta vsock 8444. O convidado pode listar identidades e pedir assinaturas; os bytes da chave privada vivem apenas no anfitrião e, como diz o manual, não podem ser lidos nem extraídos de dentro da VM. O Bromure nunca expõe o seu agente de início de sessão do macOS a uma sessão. Cada assinatura que acontece emite um evento de auditoria credential.ssh_sign com a impressão digital SHA256 da chave, e pode configurar qualquer chave importada para pedir confirmação no anfitrião a cada pedido de assinatura.

Credenciais AWS. Um ficheiro que parece certo e não vale nada. O ~/.aws/config da VM aponta para um auxiliar credential-process que entrega o verdadeiro ID de chave de acesso emparelhado com uma chave secreta falsa de 40 caracteres, pela porta vsock 8445. Todos os SDK, a CLI aws, o terraform e o boto3 aproveitam-no sem configuração extra, e o convidado produz uma assinatura SigV4 bem formada calculada com o segredo errado. O reassinador do anfitrião retira essa assinatura e volta a assinar à saída. Leve o par para outro sítio e a AWS responde InvalidSignatureException.

Configuração do Kubernetes. O ~/.kube/config existe e contém um token bearer que começa por brm-k8s-. Funciona, através do proxy, contra os servidores de API para que o espaço de trabalho foi configurado. É inerte em qualquer outro lado.

Histórico da shell. Real, e duradouro: o trabalho de programação precisa de estado duradouro, por isso a VM do espaço de trabalho é uma máquina persistente e não descartável. Guarda o histórico de uma máquina que nunca conteve uma chave verdadeira, com comandos corridos contra engodos numa diretoria pessoal que não é a sua, ao lado de um ~/.git-credentials e de um ~/.docker/config.json também eles falsos.

a lista de compras, corrida dentro de um espaço de trabalhoo que a carga pedeo que encontrachaves privadas ~/.sshnada — os bytes estão no anfitriãoas assinaturas cruzam o vsock 8444; as chaves nuncacredenciais AWSID de chave real, segredo falso de 40 caracteresrepetidos noutro lado: InvalidSignatureException~/.kube/configum marcador brm-k8s-funciona via proxy do anfitrião, inerte noutro ladohistórico da shellduradouro, e cheio de engodosuma VM que nunca deteve uma chave verdadeira
A lista de compras de quatro pontos da carga, corrida contra um espaço de trabalho Bromure Agentic Coding. O SSH não devolve byte nenhum de chave privada, porque a assinatura acontece no anfitrião por vsock 8444. A configuração AWS entrega o verdadeiro ID de chave de acesso com um segredo falso, por isso um par roubado falha a validação de assinatura a montante. O kubeconfig contém um marcador brm-k8s-. O histórico da shell é o de uma VM que nunca deteve uma credencial verdadeira.

Depois tem de a enviar

Recolher é metade do trabalho. A outra metade é a perna de saída para productivity-suite-mcp.onrender.com, e essa perna esbarra numa pilha de controlos do lado do anfitrião.

As falsificações são fios de tropeço. Cada uma tem uma única família de destinos legítimos, o âmbito de anfitriões para que foi cunhada, por isso o proxy examina cada pedido de saída, cabeçalhos e corpo, à procura de uma falsificação a caminho de onde não pertence. Ao acertar, trata o pedido como tentativa de exfiltração de credenciais: recusa com HTTP 451 sem encaminhar um byte, põe a VM em pausa no momento, e levanta um alerta que oferece Encerrar, Guardar para investigação (exportar primeiro o disco, a diretoria pessoal e as pastas partilhadas para perícia) ou Continuar por sua conta e risco. O evento aterra como uma linha vermelha de Intermediação de credenciais na Security Timeline.

O segundo controlo é a alcançabilidade. Cada espaço de trabalho carrega uma firewall de saída ordenada (ação, protocolo, anfitrião ou CIDR, portas, e para tráfego web os verbos HTTP individuais) com um valor por omissão para o tráfego não correspondido. Os espaços de trabalho novos permitem aquilo que nenhuma regra apanha; ponha isso em Negar, liste o que o trabalho precisa, e um subdomínio render.com não está na lista. Dois componentes fora do convidado aplicam a política: o comutador virtual faz corresponder cada fluxo pelo IP de destino e pelo nome de anfitrião espreitado no DNS, e o proxy volta a fazer corresponder pelo nome de servidor TLS. As edições de regras chegam às sessões em curso sem reinício.

O Guardrails põe um portão na consequência. Classifica as chamadas do agente ao Kubernetes, à AWS, à DigitalOcean, a registos de contentores, a forjas git e a bases de dados HTTPS como leituras, escritas ou operações destrutivas, e os espaços de trabalho novos vêm por omissão em Perguntar antes de escrever: cada mutação para numa caixa de diálogo do anfitrião que mostra a operação literal, o SQL exato ou o METHOD /path. É isso que a Pillar pediu a quem constrói plataformas: leituras de ficheiros sensíveis, acesso a credenciais e execução de código não deviam ser consequências de metadados remotos.

Há também um detetor no caminho do conteúdo. Quando o agente devolve ao modelo conteúdo externo (conteúdos de ficheiros, páginas obtidas, saída de comandos, resultados de chamadas de ferramenta), um classificador PromptGuard local pontua esses trechos no seu Mac antes de o modelo agir sobre eles, e pode registar, perguntar, ou bloquear com um 451. O token bearer MCP de um servidor HTTP nunca entra sequer na VM: o verdadeiro fica no anfitrião, a configuração do agente recebe um marcador brm-mcp_, e o campo das definições di-lo em voz alta, Nunca enviado para a VM — trocado pelo proxy.

a verificação corre uma vezaprovação na instalação, herdada para sempredia 1 — ler o código, executá-lo, aprovaro servidor é honesto, e o veredicto está certochamadas 1 – 3 — ainda honestonada leva ninguém a olhar outra vezchamada 4 — os metadados viramcoberta por um veredicto dado antes dos factosnão se pede a ninguém uma nova decisãoo que o agente alcança~/.ssh real · chaves AWS reais · kubeconfig reala verificação corre semprenenhum veredicto de instalação a herdarVM do espaço de trabalho — o agente e a saída do servidorbrm-k8s- · brm-mcp_ · segredo AWS falsomarcadores desde o arranque; nenhum byte de chave privadavsock 8443 — a única rota para foraproxy do anfitrião — consultado a cada pedidofirewall de saída · anfitrião, porta, verbo HTTPpolítica de escrita · leitura / escrita / destrutivoanálise de injeção no conteúdo enviado ao modelouma linha de rasto por pedido, na sua máquinaa perna de exfiltraçãofalsificação fora do âmbito → HTTP 451, VM em pausa, linha na timeline
Dois lugares para a mesma decisão. À esquerda, uma aprovação no momento da instalação cobre tudo o que o servidor fizer depois, por isso uma mudança de metadados na quarta chamada herda um veredicto dado no primeiro dia. À direita, não há veredicto de instalação para herdar: as credenciais são marcadores desde o arranque, e cada operação que o agente tenta é classificada de novo no proxy do anfitrião à saída.

Se mexeu neste

Procure nas suas configurações de cliente MCP, repositórios e diretorias pessoais por productivity-suite-mcp.onrender.com, pelo anterior ponto de extremidade promo-surname-xml-quantum.trycloudflare.com, e pelo artefacto local em ~/.config/.cache/.sys/.deadbug-mcp.py. Reverta qualquer alteração de configuração que os tenha introduzido, e não execute o script. Preserve os registos do seu cliente MCP e procure atualizações de definições de ferramenta depois da terceira chamada. O conselho da Pillar sobre rotação é comedido: rode onde a evidência local sustentar compromisso.

A regra que encolhe a pergunta

No painel Guardrails do espaço de trabalho, ponha Tráfego não correspondido em Negar e liste o que o trabalho precisa: allow web api.github.com, allow web registry.npmjs.org, default deny. Depois corra bromure-cli trace hostnames my-workspace a seguir a uma sessão, que imprime cada anfitrião distinto que o agente contactou, com contagens. Um destino que nunca escolheu aparece como uma linha que não escreveu.

A aprovação é um instantâneo; o fio é contínuo

O instinto depois de uma campanha destas é rever com mais rigor: ler mais do código, fixar o commit. Ambos valem a pena, e nenhum toca no mecanismo. A revisão aqui estava certa. O servidor foi honesto enquanto alguém olhava, depois mudou num calendário que ele próprio controlava, e não quebrou uma única regra do protocolo ao fazê-lo.

Um controlo que sobrevive a um servidor mudar de ideias é um controlo que não depende de se ter acertado antes. Uma credencial que é um marcador continua a ser um marcador na quarta chamada e na quadringentésima. Uma chave privada no anfitrião não pode ser lida em chamada nenhuma. O proxy avalia uma regra de saída em cada ligação e classifica cada escrita no momento em que acontece. Nada disso guarda memória do dia em que você aprovou alguma coisa, e é por isso que um atacante não pode herdá-la.

O MCP tornou fácil dar novas capacidades a um agente, o que era o objetivo, e fez dessas capacidades uma coisa que uma parte remota descreve em texto que o seu modelo lê como instrução. Ambas vieram para ficar. Decida o que o seu agente pode deter e o que pode alcançar, e ponha a aplicação num sítio onde o servidor não tem voto. Instale o Bromure Agentic Coding e não dê ao seu agente nada que valha a pena esperar três chamadas.