# Limites de segurança do MCP stdio para agentes locais de IA

Servidores MCP locais costumam ser tratados como inofensivos porque falam stdio e rodam no mesmo Mac que o agente. Essa conclusão deixa de valer assim que o servidor pode chamar uma API externa, usar SSH, ler um arquivo de credenciais ou executar um comando de shell com a autoridade do desenvolvedor. O transporte local elimina um salto de rede. Ele não reduz a autoridade do processo que recebe as solicitações.

A fronteira que importa é simples: um servidor MCP deve expor contexto e computação cuidadosamente limitada; um gateway de ações confiável deve guardar as credenciais e executar ações que saem da máquina. Misturar essas funções dá a um modelo de linguagem um caminho conveniente entre instruções não confiáveis e autoridade duradoura. Já vi esse erro aparecer disfarçado de uma ferramenta organizada em um único arquivo e depois crescer até virar uma mistura de tokens, chamadas de subprocessos e exceções que ninguém consegue explicar durante um incidente.

## Stdio é um transporte, não uma decisão de confiança

A segurança do MCP stdio começa pelo reconhecimento de que a entrada e a saída padrão não autenticam a intenção. O cliente MCP inicia o servidor e troca mensagens JSON-RPC por pipes. A especificação do Model Context Protocol descreve o stdio como um transporte: o servidor lê mensagens da entrada padrão e escreve mensagens na saída padrão. Ela não afirma que o transporte prova que uma solicitação é segura, foi aprovada por uma pessoa ou sequer foi produzida pelo modelo esperado.

Um cliente local pode enviar diretamente uma solicitação `tools/call`. Ele pode contornar o fluxo normal do modelo, repetir chamadas na velocidade da máquina, escolher argumentos que a descrição da ferramenta desaconselhava e conservar todos os resultados recebidos. Se uma extensão comprometida do editor iniciar o cliente, o servidor não tem uma forma mágica de saber que ele não era o Claude Code ou outro chamador esperado, a menos que o projeto ao redor forneça essa informação.

A árvore de processos comum também oferece menos isolamento do que muitos imaginam. Se o agente inicia um servidor MCP com sua conta de usuário, esse servidor normalmente herda sua identidade, diretório de trabalho, ambiente, permissões de arquivos, acesso à rede e qualquer segredo deixado em variáveis de ambiente. Um pipe não reduz essa autoridade.

Trate cada chamada de ferramenta como uma solicitação não confiável de um processo que convenceu o modelo, ou se fez passar pelo modelo, a executá-la. Isso parece rigoroso porque é rigoroso. Também é a suposição que resiste a injeção de prompt, clientes com bugs, configurações copiadas e a um desenvolvedor testando uma solicitação com um script JSON-RPC bruto.

## O servidor deve parar antes da autoridade reutilizável

Um servidor MCP deve parar antes de precisar revelar ou gerenciar uma credencial reutilizável. Entre suas boas tarefas estão pesquisar um repositório indexado, analisar a saída de um build, formatar um payload, ler um arquivo de projeto deliberadamente exposto e produzir um comando proposto para revisão. Essas tarefas ainda podem causar danos se forem mal implementadas, mas não exigem um segredo que continue útil depois do fim da sessão.

Ações externas precisam de outro responsável. Uma solicitação de API autenticada, uma conexão SSH, a publicação de um pacote, uma consulta de produção ou a atualização de uma issue juntam entrada não confiável a uma identidade com consequências. Coloque a credencial em um componente que execute a solicitação e devolva um resultado limitado ao servidor MCP ou ao agente.

Essa distinção costuma ficar confusa porque os dois componentes podem ser executáveis locais. Eles não são intercambiáveis:

- Um servidor MCP traduz uma solicitação do agente em uma operação limitada ou em uma solicitação de operação.
- Um gateway de ações é responsável pela credencial, decide se esse processo pode usá-la, executa a chamada externa e registra o que aconteceu.
- O agente recebe a saída, não os meios de repetir a ação autenticada fora desse gateway.

Não envie `API_TOKEN=...` como resultado de uma ferramenta. Não envie uma referência ao cofre e chame isso de segurança. Não exponha um comando que imprime uma chave privada na saída padrão esperando que o modelo desvie o olhar. Assim que o cliente obtém um segredo, todo controle posterior passa a ser apenas uma recomendação.

Um gateway também não deve se transformar em uma API de shell para qualquer finalidade. `run(command)` é um atalho tentador porque evita projetar ferramentas. Ele também entrega a análise de argumentos, o acesso a arquivos, os destinos de rede e, muitas vezes, o acesso a segredos a uma única string opaca. Crie ações específicas, como `get_deployment_status`, `create_issue`, `run_readonly_query` ou `ssh_exec` com um host nomeado e uma família de comandos limitada. Ações específicas tornam a validação e a revisão possíveis.

## Os esquemas das ferramentas descrevem chamadas, mas não as limitam

Um JSON Schema para uma ferramenta é útil para validar entradas, mas não é autorização. A especificação MCP exige que as ferramentas publiquem esquemas de entrada, e os clientes podem usá-los para formar chamadas. Ainda assim, um modelo pode escolher qualquer valor válido segundo o esquema. Pior: implementações descuidadas costumam aceitar uma string válida e depois inseri-la em um comando de shell ou URL, onde seu significado muda.

Considere uma ferramenta destinada a consultar o status de uma implantação:

```json
{
  "name": "deployment_status",
  "inputSchema": {
    "type": "object",
    "properties": {
      "environment": {"enum": ["staging", "production"]},
      "service": {"type": "string", "pattern": "^[a-z0-9-]{1,48}$"}
    },
    "required": ["environment", "service"],
    "additionalProperties": false
  }
}
```

Esse esquema impede um campo inesperado no nível superior e rejeita pontuações óbvias de shell em `service`. Ele não autoriza o chamador a consultar produção, não prova que `service` pertence ao repositório atual nem limita o destino HTTP depois que o servidor monta uma URL. Um validador de esquema responde: «A estrutura está correta?» A autorização responde: «Este chamador pode executar esta ação agora com esta identidade?» Mantenha essas perguntas separadas no código e na revisão.

Uma implementação ruim costuma ser parecida com esta:

```python
subprocess.run(
    f"ssh {host} systemctl status {service}",
    shell=True,
    check=True,
)
```

Mesmo que `host` e `service` passem por um esquema permissivo, a análise do shell cria outra linguagem com outra superfície de ataque. Use vetores de argumentos, rejeite hosts desconhecidos antes de abrir uma conexão e faça o gateway escolher a credencial por um identificador fixo, em vez de aceitar um caminho ou nome de token fornecido pelo agente.

Para HTTP, analise a URL antes de conectar, exija `https`, compare o nome de host normalizado com uma lista exata de hosts aprovados e desative ou revalide redirecionamentos. Um redirecionamento de um host permitido para um endereço interno ou um endpoint controlado por um invasor pode transformar uma solicitação aparentemente inofensiva em exposição de credenciais. Não dependa de uma verificação de prefixo como `url.startswith("https://api.example.com")`; informações de usuário, portas e nomes de host parecidos tornam verificações de string pouco confiáveis.

## A identidade do processo precisa estar visível no momento da aprovação

Um botão de aprovação humana só ajuda quando informa quem está solicitando e que autoridade a aprovação concede. «Permitir acesso do agente» é fraco porque esconde o executável que receberá a permissão e por quanto tempo ela valerá. Isso treina as pessoas a aprovar uma categoria vaga de atividade.

Um projeto melhor identifica o processo solicitante por sua autoridade de assinatura de código, relação com o processo pai, caminho do executável e tempo de vida do processo. A pessoa pode então aprovar uma execução de um cliente conhecido, em vez de abençoar permanentemente um rótulo. Quando o processo termina, a aprovação também deve terminar. Um novo processo precisa de uma nova decisão.

A assinatura de código não prova que todo prompt ou plugin dentro do cliente é benigno. Ela responde a uma pergunta mais restrita, mas ainda útil: qual executável assinado solicitou autoridade? Essa distinção importa quando um programa local malicioso ou alterado tenta reutilizar um nome conhecido. No macOS, o sistema fornece informações de assinatura de código que um gateway pode mostrar antes de permitir que um processo aja.

O Sallyport usa essa identidade do processo para autorização por sessão, enquanto seu cofre bloqueado nega todas as ações até que o usuário o abra com os controles protegidos pelo hardware do Mac. Esse modelo é deliberadamente pequeno: um cofre bloqueado, aprovação para um novo processo e aprovação opcional para cada uso de uma credencial específica.

Não tente resolver o cansaço causado por aprovações com uma política complexa em linguagem natural. As pessoas não conseguem avaliar com segurança um conjunto denso de regras depois que ele cresce e acumula dezenas de exceções. Use poucas decisões que correspondam a coisas que o desenvolvedor possa ver: se os segredos estão disponíveis, qual processo pode agir nesta execução e quais credenciais precisam de uma nova confirmação a cada uso.

## O escopo da aprovação deve acompanhar o dano que uma ação pode causar

Uma aprovação por sessão é adequada para tarefas repetitivas e de baixo impacto, como ler um rastreador de issues ou verificar o estado de um serviço de desenvolvimento. Ela se torna perigosa quando a mesma aprovação também cobre silenciosamente alterações destrutivas em bancos de dados, publicação de pacotes, movimentação de dinheiro, comunicações com clientes ou acesso SSH à produção.

Dê a cada credencial seu próprio nível de sensibilidade para aprovação. Um token somente leitura pode funcionar depois que o processo recebe aprovação da sessão. Um token de escrita em produção ou uma chave SSH deve exigir confirmação a cada uso. O gateway precisa mostrar contexto suficiente para que uma pessoa avalie a ação: identidade da credencial, host ou serviço de destino, método ou classe de comando e argumentos sanitizados. Não mostre o segredo.

Uma aprovação deve autorizar uma solicitação concreta, não uma promessa de que o agente se comportará bem depois. Se uma chamada de ferramenta diz `POST /releases`, a tela de aprovação não deve reduzir isso a «usar a API de releases». O método, o destino final e o nome da operação são os fatos que distinguem uma leitura inofensiva de uma escrita irreversível.

A alternativa popular é uma lista de permissões ampla: aprovar um domínio, um binário de shell ou um agente durante todo o dia de trabalho. Isso parece eficiente até que uma instrução injetada direcione a mesma capacidade permitida para outro repositório, endpoint ou argumento. Concessões amplas reduzem interrupções ao transferir o peso da revisão para o momento em que ninguém consegue ver a chamada real.

Use expirações de forma agressiva. Uma concessão de sessão deve desaparecer com o processo do cliente. Uma decisão por chamada deve expirar depois daquela operação. Se um gateway precisar oferecer concessões mais longas no futuro, deixe o escopo e a expiração explícitos, em vez de permitir que uma aprovação armazenada se passe por confiança permanente.

## O SSH precisa de sua própria fronteira, não de uma saída para o shell

O SSH é onde os projetos de agentes locais costumam perder a disciplina. Os desenvolvedores já têm um agente SSH, aliases de host, chaves encaminhadas e o hábito de digitar comandos arbitrários em um terminal. A tentação é deixar o servidor MCP chamar `ssh` usando o ambiente existente do usuário. Isso transforma o agente em um chamador de toda identidade e regra de host que o shell consegue alcançar.

A documentação do OpenSSH registra uma limitação séria do encaminhamento de agente: um usuário remoto que consiga acessar o socket encaminhado do agente pode solicitar operações ao seu agente local, embora não possa extrair as chaves privadas. Isso já é suficiente para agir como você enquanto o encaminhamento durar. Um agente autônomo não deve seguir esse caminho casualmente, porque ele amplia a autoridade para além do host original.

Use uma identidade SSH dedicada ao trabalho do agente e associe-a a um registro de host nomeado. No servidor, limite essa identidade com as opções adequadas à conta, como um comando forçado e o encaminhamento desativado quando o caso de uso permitir. No lado local, selecione o host e a identidade por uma configuração mantida fora do controle do agente. O agente pode solicitar `host: build-staging` e uma ação de comando limitada, mas não deve enviar um nome de host arbitrário, o caminho de uma chave privada ou `-o ProxyCommand=...`.

Esta é a forma mínima de uma solicitação que um gateway de ações pode validar:

```json
{
  "action": "ssh_exec",
  "host_id": "build-staging",
  "command_id": "read_service_status",
  "args": {"service": "worker"}
}
```

O gateway associa `build-staging` ao host conhecido, à política de chave do host, à conta e à credencial dedicada. Ele associa `read_service_status` a um vetor fixo de argumentos. Não concatena esse payload em uma string de shell. Uma solicitação rejeitada deve explicar o motivo em um registro de auditoria sem repetir material secreto ou dados potencialmente hostis em um terminal.

Se você precisa de diagnóstico remoto arbitrário, transforme-o em uma operação separada, com alto atrito, confirmação por chamada e limites claros de saída. Não esconda acesso arbitrário ao shell sob um nome simpático de ferramenta como `check_server`.

## Os registros de auditoria precisam sobreviver ao autor da ação

Um log de texto escrito pelo mesmo processo que executa a ação só é uma evidência até o momento em que esse processo decide alterá-lo. A atividade do agente precisa de um registro que permita reconstruir tanto a execução quanto cada chamada externa e detectar remoções ou edições posteriores.

Registre a identidade do processo, o identificador da sessão, o horário, o tipo de ação, o identificador da credencial, o destino aprovado, o formato sanitizado da solicitação, o resultado da aprovação, o status da resposta e a categoria do erro. Separe um diário de sessão de um diário de ações. A visão da sessão responde: «Qual execução do agente tinha autoridade?» A visão das ações responde: «O que ele fez com essa autoridade?» Não obrigue um investigador a deduzir uma coisa a partir de um fluxo único de linhas.

Uma cadeia de hashes oferece uma verificação prática de integridade. Para cada registro, calcule um resumo sobre o resumo do registro anterior e os bytes canônicos do novo registro criptografado. Armazene o novo resumo com o registro. Um verificador pode detectar um registro alterado, um registro removido no meio ou uma sequência reordenada sem precisar do texto original.

O verificador de auditoria deve funcionar de forma independente do agente e não deve precisar de acesso às credenciais. Uma interface de comando pode ser simples assim:

```text
$ sp audit verify
records: 184
first sequence: 1
last sequence: 184
chain: valid
```

Esse formato oferece ao operador algo específico para anexar a um ticket ou registro de incidente. Se a verificação falhar, o comando deve informar a primeira sequência em que a continuidade foi rompida e retornar um status de saída diferente de zero. «Log ilegível» é vago demais para investigar.

Evidência de adulteração não é o mesmo que prevenção de adulteração. Um usuário local com acesso suficiente ainda pode apagar todo o log ou reverter seu armazenamento. Mantenha essa limitação visível. Se o risco exigir prova contra reversões locais, exporte checkpoints assinados para um sistema controlado separado. Não afirme que uma cadeia de hashes local resolve uma ameaça que ela não aborda.

## Mantenha os segredos fora das variáveis de ambiente e da saída das ferramentas

Variáveis de ambiente são convenientes para uma sessão de shell humana e oferecem um isolamento ruim para agentes autônomos. Um processo filho as herda por padrão. O registro de depuração pode imprimi-las. Um comando que lista o ambiente pode devolvê-las ao modelo. Relatórios de falhas, pacotes de suporte, inspeção de processos e transcrições de terminal copiadas já expuseram segredos dessa forma.

Um arquivo de credenciais no workspace é ainda pior. O modelo pode lê-lo, uma ferramenta pode enviá-lo, um comando Git pode adicioná-lo ao stage e um serviço de indexação pode preservar uma cópia. Mover o arquivo para um diretório oculto reduz a frequência de acidentes, mas não muda a fronteira de segurança.

Mantenha as credenciais em um cofre controlado pelo gateway de ações. O gateway seleciona a credencial com base em um mapeamento fixo de ações e a injeta apenas na própria operação HTTP ou SSH. Ele devolve um corpo de resposta depois de filtrá-lo, somente quando esse corpo for seguro para o agente ver. Um token bearer nunca deve passar pelo resultado MCP, nem mesmo redigido, porque erros de redação se tornam parte permanente da transcrição.

Para HTTP, prefira um contrato de resposta em vez de uma passagem direta do conteúdo. Uma ação de status de implantação poderia devolver isto:

```json
{
  "environment": "staging",
  "service": "worker",
  "state": "healthy",
  "revision": "a1b2c3d4"
}
```

Ela não deve devolver cabeçalhos de resposta que possam conter identificadores de sessão, detalhes internos de roteamento ou um novo token. Decida quais campos o agente precisa antes da implementação. Um proxy bruto é outro atalho que se torna caro de desfazer.

## Uma fronteira pequena é mais fácil de operar sob pressão

Você pode revisar uma integração de agente local sem uma linguagem de políticas ou um grande programa de segurança. Comece pelo inventário de ações e force cada ação a entrar em um de dois grupos: ela lê ou calcula contexto local sem autoridade reutilizável, ou atravessa a fronteira para um serviço externo e precisa de um gateway.

Para cada ação externa, anote a identidade fixa do destino, a credencial selecionada pelo gateway, os argumentos exatos que o agente pode influenciar, o escopo da aprovação e o registro de auditoria produzido. Se alguma linha disser «comando arbitrário», «qualquer URL», «token do ambiente» ou «o agente escolhe a credencial», a fronteira ainda não está pronta.

Faça este exercício de falhas antes de entregar um segredo útil ao agente:

1. Envie uma solicitação válida segundo o esquema que indique um destino inesperado ou um argumento grande demais.
2. Repita uma solicitação aprovada anteriormente depois que o processo do cliente terminar.
3. Tente uma solicitação HTTP redirecionada e uma solicitação SSH com opções de encaminhamento.
4. Bloqueie o cofre e confirme que toda ação falha antes de qualquer conexão de rede ser aberta.
5. Altere um registro de auditoria armazenado e verifique se o comando de auditoria identifica uma cadeia quebrada.

Esses testes encontram erros de projeto que uma demonstração agradável esconde. Um modelo que segue instruções perfeitamente não é um teste de segurança.

A arquitetura local mais limpa mantém o servidor MCP comum e descartável. Deixe-o expor contexto útil e solicitar operações projetadas de forma limitada. Coloque segredos, aprovação consciente da identidade do processo, execução e um registro auditável atrás da fronteira de ações. Quando alguém perguntar por que uma ferramenta não pode simplesmente receber o token de produção, a resposta deve estar visível no projeto: a ferramenta nunca precisou do token para fazer seu trabalho.
