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) | Lê .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
denye adicionarallow, 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
- 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.
- 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.
- 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.
- 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.
- A vítima instala um MCP server de repositório público não auditado, ou um servidor honesto com uma dependência comprometida
- Scripts de instalação já executam com o usuário dela
- O servidor sobe via stdio e herda ambiente, credenciais e permissões de disco
- A vítima pede uma tarefa comum; o modelo escolhe a tool envenenada porque a descrição parece adequada
- O handler lê segredos, exfiltra e devolve uma resposta forjada de sucesso
- O mesmo processo adultera a configuração do agente: remove
deny, adicionaallow, registra hooks. A mudança pode valer ainda na mesma sessão - E grava um artefato com início automático, que passa a rodar fora da sessão do agente
- 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
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ção | Por que funciona |
|---|---|
Managed settings (managed-settings.json) | Vive em caminho de sistema; exige privilégio de admin ou root para editar |
allowManagedPermissionRulesOnly | Impede que configurações de usuário e projeto definam regras de allow, ask ou deny; só valem as da policy gerenciada |
allowedMcpServers + allowManagedMcpServersOnly | Sem 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 serverCommand | Compara o comando e os argumentos exatos que iniciam o servidor, em vez de um nome que o usuário escolhe |
managed-mcp.json | Conjunto fixo de servidores; só quem tem privilégio administrativo num caminho de sistema consegue implantar |
allowManagedHooksOnly | Bloqueia 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 gerenciado | O 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 vida | Neutraliza 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ística | Sem latest, sem resolução flutuante; o caso postmark-mcp entrou por uma atualização |
| Atraso de adoção | Nã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 allowlist | Elimina confusão de dependência e dá um ponto único de bloqueio |
| Containerizar instalação e execução | O 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 pinning | Detecta instruções ocultas em descrições e definições de tool que mudaram desde o último scan |
| Monitorar egress de rede | Servidor "de formatação de documento" fazendo HTTP de saída é bandeira vermelha imediata |
| Integridade dos arquivos de configuração | Checksum monitorado externamente detecta adulteração fora da sessão do agente |
Como verificar se você foi atingido
- Compare
~/.claude/settings.jsone os arquivos de configuração dos projetos com uma versão conhecida. Versionar esses arquivos em git ajuda muito aqui - Revise hooks e servidores MCP registrados; remova o que você não reconhece
- Liste tarefas agendadas e itens de inicialização do seu usuário, nos três sistemas operacionais que você usa
- Revise arquivos de inicialização de shell, hooks de git dos repositórios ativos e extensões de IDE instaladas recentemente
- Procure alterações recentes em
CLAUDE.mde em arquivos de instrução equivalentes - Verifique se alguma credencial sua aparece em repositório público
- Se um servidor suspeito chegou a rodar, trate como comprometimento: rotacione chaves SSH, tokens de cloud, tokens de registro de pacotes e tudo que estava em arquivos de ambiente. Desinstalar o pacote não desfaz a exfiltração
Checklist antes de instalar um MCP server
- Quem publica? Repositório oficial do fornecedor, ou conta desconhecida com nome parecido?
- Li o handler de cada tool, não só a descrição?
- Olhei a árvore de dependências, ou só o repositório do servidor?
- A versão está fixada, com lockfile e hash?
- A instalação e a execução acontecem isoladas, sem acesso aos meus secrets reais?
- 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.