Voltar as noticias
A Fronteira de Permissão que Meu Servidor MCP Não Tem
MCP ProtocolMediaEN

A Fronteira de Permissão que Meu Servidor MCP Não Tem

Dev.to - MCP·16 de agosto de 2026

Há um tema aparecendo muito em postagens sobre ferramentas de agente esta semana: agentes segurando ferramentas que podem ser mal utilizadas, e construtores conectando algum tipo de portão na frente das perigosas — uma capacidade assinada, uma camada de política, uma verificação de humano no loop antes que a escrita realmente aconteça. Eu construí algo que se parece com esse portão há um tempo atrás. Então eu fui e li meu próprio código de forma detalhada o suficiente para notar que não é um.

A ferramenta em questão é update_article no servidor MCP que eu executo para meu pipeline de publicação no DEV.to. Ela edita um artigo ao vivo por id — título, corpo, flag de publicado. Sobrescrever o conteúdo de um artigo publicado é o caso perigoso: o DEV.to não mantém histórico de versões, então uma escrita ruim desaparece no momento em que acontece. De volta em julho, eu adicionei um portão exatamente para isso:

live_content_write = before.get("published") and ("title" in article or "body_markdown" in article)
...
if live_content_write and not confirm:
    return {
        "id": article_id,
        "url": before.get("url"),
        "applied": False,
        "reason": "artigo está atualmente publicado — mudanças no título/body_markdown "
                  "exigem confirm=True (DEV.to não tem histórico de versões para desfazer isso)",
        ...
    }

Chame-o sem confirm=True contra um artigo ao vivo e você recebe o diff proposto de volta, não uma escrita. Isso parecia uma fronteira de permissão. Não é uma, e a razão levou-me embaraçosamente muito tempo para ver: confirm é um argumento de palavra-chave que o chamador fornece. Não há caminho de código que force um agente a passar pela etapa de visualização antes que ele possa definir confirm=True. Um agente que nunca viu a docstring, ou que decidiu por conta própria que o artigo precisava de correção agora, pode chamar update_article(id, body_markdown=new_body, confirm=True) como seu primeiro movimento e o portão simplesmente não está lá para essa chamada. Ele apenas impede o chamador que já estava prestes a parar e perguntar.

Eu adicionei uma segunda camada algumas semanas depois, uma vez que percebi que a primeira tinha sua própria lacuna: confirm=True por si só não prova que o diff que você aprovou ainda descreve o artigo ao vivo. Alguém poderia editá-lo no local entre sua visualização e sua confirmação. Então update_article agora também aceita expected_fingerprint, um hash do estado do artigo no momento da visualização, e recusa uma escrita obsoleta mesmo com confirm=True:

if live_content_write and expected_fingerprint is not None and expected_fingerprint != fingerprint:
    return {
        "id": article_id,
        "applied": False,
        "reason": "obsoleto — o artigo ao vivo mudou desde que o diff que você aprovou foi "
                  "gerado (expected_fingerprint não' corresponde ao artigo atual); "
                  "revisualize e reaprove antes de escrever",
        "fingerprint": fingerprint,
        ...
    }

Esta é uma verificação genuinamente melhor — ela captura a deriva que a primeira versão não conseguiu. Mas tem a mesma forma de buraco que confirm tinha: expected_fingerprint é None por padrão, e ignorá-lo pula a verificação, não a escrita. Passe confirm=True sem fingerprint e a correção que eu implementei para obsolescência nunca é executada. Minha própria docstring diz isso claramente: "isso é opcional, não obrigatório." Eu escrevi essa frase como uma nota para um futuro leitor. Eu deveria ter lido isso como um relatório de bug contra o design.

O padrão por trás de ambas as lacunas é o mesmo que sempre têm as verificações de segurança de argumentos de palavra-chave: uma verificação que dispara apenas quando o chamador fornece a evidência para isso é uma verificação que o chamador pode optar por não fazer por omissão, não apenas por uma flag de bypass explícita. confirm=False não é o caminho arriscado — o caminho arriscado é um chamador que nunca aprendeu que o parâmetro existe, e o Python não faz esse chamador passar nada. Compare isso a uma fronteira que não pode ser pulada pelo silêncio: o fingerprint poderia ser obrigatório sempre que live_content_write for verdadeiro, ponto final, sem padrão. Um chamador sem fingerprint é recusado, não liberado. Isso é uma linha — expected_fingerprint: str em vez de expected_fingerprint: str = None, além de remover o is not None da condição — e transforma "protege o chamador que já sabia se proteger" em "protege o artigo independentemente do que o chamador sabia."

Eu ainda não implementei essa mudança, e quero ser honesto sobre o porquê: essa ferramenta específica é invocada apenas por mim, através do Claude Desktop, na minha própria máquina. O modelo de ameaça realista para update_article agora é meu próprio erro ou uma confusão de agente, não um chamador adversário — que é exatamente o caso que a confirmação opcional ainda protege na maior parte, já que sou eu quem escreveu a docstring e geralmente lembro de visualizar primeiro. Mas "geralmente lembro" é precisamente a propriedade que um

Contexto Triplo Up

Empresas brasileiras que utilizam servidores MCP devem estar atentas a falhas de segurança em suas implementações. A reflexão sobre a necessidade de camadas de proteção é crucial para evitar danos em conteúdos publicados. Melhorias no design podem prevenir erros de agentes de IA.

Noticias relacionadas

Gostou do conteudo?

Receba toda semana as principais novidades sobre WebMCP.