Segurança de agentes / Model Context Protocol

MCP Tool Poisoning: da execução de código ao bypass das permissões do agente

Um MCP server malicioso publicado num repositório público pode encadear instalação, herança de privilégio via stdio, adulteração de configuração e persistência fora da sessão para transformar um install aparentemente inofensivo em controle duradouro da máquina e do agente.

Contexto: por que o stdio é o eixo do risco

O Model Context Protocol (MCP) padroniza como um agente de IA descobre e chama ferramentas externas: em vez de cada integração ser escrita à mão, um servidor MCP expõe tools que o modelo invoca via JSON-RPC.

O transporte mais comum para ferramentas locais é o stdio. O host (Claude Code, por exemplo) sobe o servidor como processo filho e conversa com ele por stdin/stdout. Não existe rede nem autenticação, porque não precisa: o processo filho herda o usuário do sistema operacional que o iniciou, com variáveis de ambiente, permissões de arquivo e credenciais já carregadas na sessão.

Essa escolha de design é intencional e correta para o caso de uso legítimo: rodar um script próprio, acessar um banco local. O problema aparece quando o código que roda dentro desse processo não é seu.

Uma nota sobre "escalada de privilégio"

No sentido clássico, escalada de privilégio local significa sair de usuário comum para root. Não é isso que acontece aqui: o UID nunca muda. O que escala é outra fronteira: a camada de permissões do agente. O atacante começa limitado ao que o processo filho pode fazer e termina controlando o que o agente pode fazer nas sessões seguintes, e depois nem precisa mais do agente.

Duas classes de ameaça que costumam ser confundidas

Código malicioso (supply chain). O handler faz algo diferente do que declara. É um trojan clássico, entregue via npm install, pip install ou clone direto.

Metadados maliciosos (tool poisoning, no sentido original). O termo foi formalizado pela Invariant Labs em abril de 2025: instruções maliciosas embutidas na descrição da tool, que o modelo lê mas o usuário normalmente não vê. O código pode ser inteiramente benigno. Por isso essa classe funciona inclusive com servidores remotos, sem nenhuma execução local.

A cadeia descrita aqui combina as duas.

0A dependência que ninguém audita

Existe um vetor anterior a tudo, e mais sutil: o MCP server pode ser honesto e ainda assim ser o veículo.

Você audita o repositório do servidor, lê os handlers, confere que a implementação bate com as descrições e aprova. O que você provavelmente não fez foi auditar as dezenas ou centenas de pacotes que ele arrasta como dependência transitiva. Basta que um deles esteja sob controle do atacante.

  • Comprometimento de pacote legítimo. Conta de mantenedor sequestrada por phishing, versão nova com uma linha a mais.
  • Typosquatting. Pacote com nome quase idêntico ao popular, esperando um erro de digitação ou uma sugestão errada de LLM.
  • Confusão de dependência. Pacote público com o mesmo nome de um pacote interno da empresa, publicado com versão maior e resolvido preferencialmente pelo gerenciador.
  • Pacote alucinado. Um modelo sugere uma biblioteca que não existe; o atacante registra esse nome e espera.

E o detalhe que fecha o círculo com a etapa 2: a execução acontece na instalação, não no uso. Scripts de ciclo de vida do gerenciador de pacotes rodam com o usuário que instalou. Você nunca chegou a subir o MCP server: o install já foi suficiente.

1A interface mente sobre a implementação

O que o modelo vê de uma tool são essencialmente três campos: name, description e inputSchema. A especificação também prevê title, outputSchema e annotations, como readOnlyHint, mas todos são declarados pelo próprio servidor. Nada no protocolo verifica se o código por trás cumpre o que eles prometem.

O que o modelo vêO que o handler realmente executa
format_document(path).env, ~/.ssh/, credenciais de cloud
"Formata um documento no padrão da empresa"Envia o conteúdo lido para um endpoint externo
Schema simples: {path: string}Retorna texto forjado: "Documento formatado com sucesso!"

Do lado dos metadados, três variantes documentadas:

  • Tool poisoning. A descrição carrega instruções ocultas que o modelo segue. No exemplo canônico da Invariant Labs, uma função de soma instrui o modelo a ler chaves SSH e passá-las num parâmetro extra.
  • Rug pull. O servidor entrega descrições benignas, é aprovado, e depois troca por versões envenenadas, sem nova aprovação.
  • Tool shadowing. A descrição de um servidor malicioso redefine como o modelo deve usar a tool de outro servidor, confiável e intocado.

Em todos os casos, a aprovação do usuário acontece para o nome bonito, não para o comportamento real.

2Execução com o privilégio do usuário

Um detalhe que muda a leitura do ataque: o código não começa a rodar quando a tool é chamada. Ele roda quando o servidor é iniciado e, antes disso, na instalação. Subir o servidor já é o ponto sem volta. A tool envenenada não é o gatilho, é a cobertura: dá ao ataque a aparência de uso normal e um resultado forjado para mostrar.

A partir do spawn, o processo pode, sem pedir nada a mais:

  • Ler qualquer arquivo que seu usuário possa ler: arquivos de ambiente, chaves SSH, credenciais de cloud, histórico de shell
  • Fazer requisições de rede para qualquer destino
  • Escrever ou modificar arquivos onde seu usuário tem permissão de escrita
  • Executar binários e scripts já instalados na máquina

Não é falha do MCP. É a mesma exposição de qualquer pacote desconhecido ou extensão não auditada. O MCP só torna o padrão mais fácil de disparar sem perceber, porque a interface esconde a implementação atrás de um convite de "clique e confie".

3O alvo muda do processo para o agente

Cabe uma pergunta honesta aqui: se o handler já tem acesso total do usuário (e as regras de deny do Claude Code nem se aplicam a ele, já que limitam as tools do agente, como Bash e Read), por que o atacante mexeria no settings.json?

Porque o objetivo deixa de ser acesso imediato e passa a ser persistência e controle do agente. Arquivos como ~/.claude/settings.json e as variantes de projeto são JSON comum no disco: sem assinatura, sem hash de integridade, sem trava contra escrita por outro processo do mesmo usuário. Editá-los permite:

  • Remover entradas de deny e adicionar allow, de forma que qualquer prompt injection futuro encontre o agente sem travas: o agente vira um confused deputy
  • Registrar hooks, que executam comandos de shell em eventos da sessão, garantindo execução mesmo depois que o servidor malicioso for desinstalado
  • Adulterar registros de servidores MCP ou arquivos de instrução como o CLAUDE.md, que o agente lê como contexto confiável

É o padrão do malware que desativa o antivírus antes de agir, com uma diferença: aqui o "antivírus" é a própria camada de permissão declarativa do agente.

Sobre o timing

A documentação atual do Claude Code afirma que edições em settings.json são detectadas por um file watcher e passam a valer na sessão em execução após um breve intervalo de estabilidade do arquivo, sem reiniciar. Ou seja: a adulteração pode surtir efeito ainda na mesma sessão, não só na próxima. Esse comportamento variou entre versões, e há relatos no repositório do Claude Code de regras de permissão que só foram aplicadas depois de um restart, então não conte com o reinício como barreira. Novos servidores MCP, por sua vez, normalmente só conectam quando a sessão é iniciada ou recarregada.

3-AQuando a tool é só o gatilho

Nas etapas anteriores o dano acontece dentro do processo do MCP server. Existe um desenho pior: o handler não faz nada de interessante durante a chamada, apenas escreve um artefato no disco e registra um mecanismo de início automático. A partir daí o ataque deixa de depender do agente, do MCP e da sua sessão.

Os mecanismos de persistência são os mesmos de qualquer malware de usuário, e todos estão ao alcance de um processo sem privilégio administrativo:

  • Agendadores de usuário (cron no Linux, Agendador de Tarefas no Windows, launchd no macOS), que disparam em intervalo fixo
  • Inicialização de sessão: pastas de startup, entradas de registro por usuário, unidades de serviço de usuário
  • Arquivos de inicialização de shell, executados a cada terminal aberto
  • Hooks de ferramentas de desenvolvimento: hooks de git no repositório, hooks do próprio agente, extensões de IDE

Nenhum deles exige root. Todos sobrevivem à desinstalação do MCP server, e esse é justamente o ponto. O usuário remove o pacote, considera o incidente encerrado, e o artefato continua lá.

O que o payload costuma fazer depois de ativo

  1. Varredura de segredos. Percorre o diretório do usuário e os projetos procurando padrões de credencial: arquivos de ambiente, chaves privadas, credenciais de cloud, tokens em arquivos de configuração, e também histórico do shell e histórico do git, onde segredo removido do código costuma continuar vivo. Existem ferramentas de código aberto de secret scanning, criadas para uso defensivo, que fazem exatamente isso; atacantes simplesmente as embutem em vez de escrever a própria varredura.
  2. Exfiltração com cobertura. O destino nem sempre é um servidor com IP suspeito. O worm Shai-Hulud publicava os segredos roubados em repositórios públicos criados na conta da própria vítima: tráfego de saída para um domínio que já estava na sua allowlist, com credencial legítima.
  3. Propagação. Com token de registro de pacotes ou de repositório em mãos, o payload republica a si mesmo nos pacotes que a vítima mantém, fechando o ciclo sem operador humano.
  4. Segundo estágio. O primeiro artefato é pequeno e discreto; busca e executa a carga real quando quiser, o que permite trocar o payload depois da infecção sem tocar no pacote original.

A leitura importante: o MCP server é o vetor de entrada, não o malware. Depois desta etapa, remover o servidor e limpar o settings.json não resolve nada.

4A cadeia completa

Cada etapa isolada parece pequena. Somadas, formam um comprometimento duradouro disfarçado de instalação de ferramenta.

  1. A vítima instala um MCP server de repositório público não auditado, ou um servidor honesto com uma dependência comprometida
  2. Scripts de instalação já executam com o usuário dela
  3. O servidor sobe via stdio e herda ambiente, credenciais e permissões de disco
  4. A vítima pede uma tarefa comum; o modelo escolhe a tool envenenada porque a descrição parece adequada
  5. O handler lê segredos, exfiltra e devolve uma resposta forjada de sucesso
  6. O mesmo processo adultera a configuração do agente: remove deny, adiciona allow, registra hooks. A mudança pode valer ainda na mesma sessão
  7. E grava um artefato com início automático, que passa a rodar fora da sessão do agente
  8. A vítima desinstala o servidor; o agendador continua disparando

O dano final depende só do que aquele usuário do sistema operacional tem permissão de fazer, o que, num ambiente de desenvolvimento típico, costuma ser quase tudo.

Diagrama da cadeia de ataque

Vítima (usuário)Agente (Claude Code)MCP server(processo filho, stdio)Artefato persistente(agendador / startup)Servidor do atacanteinstala e sobe o servercódigo já executa: herdausuário, env vars e discopede tarefa comumtools/call format_document(path)handler real:lê .env, ~/.ssh, credenciaisPOST com dados sensíveisresultado forjado ("sucesso")edita settings.json: removedeny, adiciona allow e hooksgrava artefato e início automáticomostra resposta normaldesinstala o servidorcontinua disparandofora da sessãovarredura periódica e exfiltração

A vítima nunca vê a linha que sai para o atacante, nem a edição da configuração, nem o registro do agendador. As três acontecem dentro do processo filho, fora da conversa visível com o agente.

Isso já aconteceu

Em setembro de 2025, pesquisadores divulgaram o que parece ser o primeiro MCP server malicioso encontrado em uso real: o pacote npm postmark-mcp. As versões 1.0.0 a 1.0.15 funcionavam perfeitamente; a 1.0.16 adicionou uma única linha que colocava um BCC oculto em todo e-mail enviado, copiando tudo para um endereço externo. O pacote tinha cerca de 1.500 downloads semanais.

No mesmo mês, o worm Shai-Hulud mostrou o modelo completo de payload persistente no ecossistema npm: execução via script de instalação, varredura de segredos com uma ferramenta de scanning legítima embutida, exfiltração para repositórios públicos na conta da vítima e republicação automática em pacotes do mantenedor comprometido. A primeira onda, que executava no postinstall, atingiu de 180 a mais de 500 pacotes, conforme a fonte e o momento da contagem. A segunda, em novembro de 2025, passou a executar no preinstall e chegou a cerca de 800 pacotes e 25 mil repositórios.

Do lado dos metadados, a Invariant Labs publicou em abril de 2025 uma prova de conceito em que um servidor de "fato do dia" trocava sua interface na segunda carga e manipulava um servidor WhatsApp legítimo, conectado em paralelo, para vazar o histórico de conversas, sem nenhuma nova aprovação do usuário.

Mitigações reais

A camada que efetivamente resiste é a que fica fora do alcance do mesmo usuário que rodou o processo malicioso. Na prática, são duas fronteiras, uma para cada classe de ameaça. Contra código malicioso, o isolamento do processo: container, VM ou sandbox. Contra metadados maliciosos, a policy gerenciada e as permissões do agente, porque a descrição envenenada age sobre o agente, que está fora da sandbox e executa com os privilégios dele. Uma sem a outra deixa metade da cadeia aberta.

Nem a sandbox é absoluta. O servidor precisa de algum token e de alguma rede para ser útil, e tudo que entra ali pode vazar: ela reduz o raio do dano, não zera. E os controles de supply chain são probabilísticos, mas atuam antes de o código rodar, que é o melhor momento para barrar.

MitigaçãoPor que funciona
Managed settings (managed-settings.json)Vive em caminho de sistema; exige privilégio de admin ou root para editar
allowManagedPermissionRulesOnlyImpede que configurações de usuário e projeto definam regras de allow, ask ou deny; só valem as da policy gerenciada
allowedMcpServers + allowManagedMcpServersOnlySem a segunda flag, as allowlists de todas as fontes se fundem, inclusive a do próprio usuário. E ela é independente da flag de permissões: uma não ativa a outra
Casar servidor stdio por serverCommandCompara o comando e os argumentos exatos que iniciam o servidor, em vez de um nome que o usuário escolhe
managed-mcp.jsonConjunto fixo de servidores; só quem tem privilégio administrativo num caminho de sistema consegue implantar
allowManagedHooksOnlyBloqueia hooks de usuário, de projeto e de plugins comuns, fechando o vetor de persistência da etapa 3. Continuam rodando os hooks gerenciados, os de SDK e os de plugins ativados à força pela própria policy gerenciada
Hook ConfigChange gerenciadoO evento dispara quando um arquivo de configuração muda durante a sessão. Definido na policy gerenciada, registra ou alerta sobre a adulteração da etapa 3 sem que o processo malicioso consiga removê-lo
Instalar sem scripts de ciclo de vidaNeutraliza a execução no install, ou seja, a etapa 0 inteira. Abra exceções pontuais, não por padrão
Lockfile e instalação determinísticaSem latest, sem resolução flutuante; o caso postmark-mcp entrou por uma atualização
Atraso de adoçãoNão instalar versão publicada nas últimas 24 a 72 horas, janela em que a maioria dos pacotes maliciosos é detectada e removida
Registro interno com proxy e allowlistElimina confusão de dependência e dá um ponto único de bloqueio
Containerizar instalação e execuçãoO sandbox nativo do Claude Code restringe apenas comandos de Bash e seus processos filhos. File tools, hooks e MCP servers rodam direto no host. Para isolar o servidor, coloque o processo inteiro dentro da fronteira: container, VM ou o sandbox-runtime da Anthropic (ainda em beta), sem secrets reais montados e com usuário restrito
Scanner com hash pinningDetecta instruções ocultas em descrições e definições de tool que mudaram desde o último scan
Monitorar egress de redeServidor "de formatação de documento" fazendo HTTP de saída é bandeira vermelha imediata
Integridade dos arquivos de configuraçãoChecksum monitorado externamente detecta adulteração fora da sessão do agente

Como verificar se você foi atingido

Checklist antes de instalar um MCP server

  1. Quem publica? Repositório oficial do fornecedor, ou conta desconhecida com nome parecido?
  2. Li o handler de cada tool, não só a descrição?
  3. Olhei a árvore de dependências, ou só o repositório do servidor?
  4. A versão está fixada, com lockfile e hash?
  5. A instalação e a execução acontecem isoladas, sem acesso aos meus secrets reais?
  6. Tenho alguma visibilidade do tráfego de saída desse processo?

settings.json e suas variantes não protegem contra esse ataque por si só: são configuração declarativa, editável por qualquer processo do mesmo usuário. A defesa real está no que esse usuário não alcança: o isolamento do processo e a policy gerenciada em caminho de sistema.

Escrito em nível conceitual, sem prova de conceito executável. As referências a comportamento do Claude Code valem para as versões atuais e devem ser revalidadas a cada atualização.

Todos os artigos