Um plugin agora pode pedir que o Hermes proteja seu próprio servidor MCP


Você instala um plugin da comunidade a partir do catálogo. Ele registra silenciosamente um servidor MCP, e esse servidor pode fazer pedidos, movimentar dinheiro ou enviar mensagens em seu nome. O Hermes nunca pergunta uma única vez. A única coisa que separa um modelo atingido por prompt injection da ação é uma frase no texto da skill do plugin pedindo que o modelo confirme com você antes — e se o modelo obedece é questão de humor, não de código.

Isso não é culpa do plugin. É um buraco no formato: o Agent Plugins v1 não define nenhum campo de trust, então todo servidor MCP carregado de um pacote portátil rodava com o padrão trust: full. O PR #134524, mesclado em 7 de outubro de 2026, fecha essa lacuna: um pacote pode pedir ao Hermes que proteja seu próprio servidor, e não pode pedir nada mais amplo.

De onde veio a lacuna

O Hermes suporta dois formatos de plugin. Plugins nativos são um plugin.yaml mais uma função register(ctx), conectados ao núcleo. Pacotes portáteis seguem o formato Agent Plugins v1.0.0 e se parecem com isto:

my-portable-plugin/
├── plugin.json
├── skills/
│   └── summarize/
│       ├── SKILL.md
│       └── references/
└── mcp.json

plugin.json é o manifesto, mcp.json declara os servidores MCP aos quais o pacote se conecta, e skills/ carrega as skills que ele distribui. Um pacote recém-instalado permanece desabilitado até que você rode hermes plugins enable <name> — boa higiene, mas isso responde apenas “isto deve carregar ou não”, e não “quem pisa no freio enquanto ele roda”.

O problema real era o leitor. O mcp.json portátil do Hermes aceita apenas type, url e headers (veja hermes_cli/agent_plugins.py), enquanto o nível de trust fica na config nativa em mcp_servers.<name>.trust, no config.yaml. Sem esse campo na especificação, o leitor nunca traduzia nenhum, e todo servidor de pacote subia com o full padrão.

O que colocou isso no roadmap foi um caso concreto. Uma varredura do catálogo encontrou um plugin de trading cujo servidor hospedado pausa cada operação na conta Hyperliquid do usuário até que resolve_approval seja chamado. Como o PR coloca, a única coisa que separava um modelo atingido por prompt injection de uma operação aprovada era o texto da skill. O próprio Hermes nunca pedia confirmação.

Como um pacote pede por uma proteção

O plugin.json já carrega um bloco de extensão específico do Hermes — o mesmo que guarda app, requires e liveness. Agora ele também aceita trust:

{
  "extensions": {
    "com.nousresearch.hermes": {
      "servers": {
        "trade": { "trust": "untrusted" }
      }
    }
  }
}

Esse untrusted é escrito na entrada de servidor do mcp.json traduzido, e a partir daí o gate existente em tools/mcp_tool_handlers.py se aplica sem mudanças:

  • Toda chamada de ferramenta sem uma anotação readOnlyHint: true pede sua aprovação primeiro, pela superfície de aprovação padrão;
  • onde ninguém pode responder — cron jobs, one-shots do hermes chat -q, uma sessão de gateway sem supervisão — a chamada falha de forma fechada (fail-closed) em vez de passar batida;
  • uma entrada que declara trust e nada mais não cria nenhum gate de disponibilidade; apenas coloca uma trava no servidor.

Apenas restringir, por construção

A regra de design importa mais do que o recurso. Um pacote pode declarar exatamente dois valores:

O que o pacote escreve O que acontece
untrusted Aplicado — toda chamada com capacidade de escrita nesse servidor exige aprovação
full Aceito, mas já é o padrão, então não concede nada
Qualquer outra coisa O pacote é recusado por completo, a mesma postura fail-closed de um valor não reconhecido na config nativa

Mais uma regra de precedência dá a retaguarda: um servidor do config.yaml com o mesmo nome substitui a entrada do pacote por inteiro, incluindo o trust. Um pacote nunca pode sobrepor o usuário. Defina você mesmo um servidor como full e nenhuma quantidade de untrusted em um manifesto vai mudar isso.

O enquadramento do próprio PR vale a pena guardar: isso existe para que um pacote possa restringir, nunca ampliar. O campo de extensão não é uma forma de os plugins ganharem autoridade, é uma forma de devolverem autoridade.

Verifique antes de habilitar

O hermes plugins validate agora lista os servidores protegidos, para que revisores do catálogo — e você — possam ver a declaração:

server trust: trade — untrusted (Hermes asks before every write-capable tool call)

Quando aparece um pacote portátil desconhecido, esta é a ordem que custa menos:

hermes plugins validate /path/to/package   # o que ele declara, incluindo servidores protegidos
hermes plugins install owner/repo          # instala desabilitado
hermes plugins capabilities <name>         # capacidades declaradas vs concedidas
hermes plugins enable <name>               # ligue depois que você concordar

Se você publica plugins

Três coisas que valem a pena levar:

  1. Proteja seus servidores de escrita. Qualquer servidor MCP que gaste dinheiro, opere, envie mensagens ou altere contas deve declarar trust: "untrusted" no plugin.json. Não confie no texto da skill dizendo ao modelo para perguntar — isso é uma dica, não um gate.
  2. Nunca coloque credenciais no mcp.json. Os valores de env declarados ali são dados visíveis do pacote, não um cofre de segredos.
  3. “Instala desabilitado” não é um gate. Assim que o usuário habilita o pacote, o bloco de extensão é a única alegação de segurança que você controla.

Para o modelo de trust mais amplo em servidores MCP remotos — por que readOnlyHint é uma dica fornecida pelo próprio servidor, e por que ferramentas sem anotação retornam outcome_uncertain quando uma sessão de transporte expira no meio de uma chamada — veja o modelo de segurança do catálogo de MCP remoto. Para saber até onde o Hermes isola um plugin do host, leia isolamento de plugins e o limite do host, e para entender como as aprovações se comportam quando não há humano por perto, o guia de aprovações sem supervisão vai mais fundo. Referência de comando: hermes plugins.

A parte que vale a pena lembrar

readOnlyHint é uma dica que o servidor dá sobre si mesmo — um servidor mentiroso pode, no máximo, pular um prompt para uma ferramenta que afirma ser somente leitura, nunca ganhar acesso extra. trust: untrusted remove a dependência de qualquer autodescrição: tudo que não estiver marcado como somente leitura pergunta primeiro.

Essa diferença é o ponto central da mudança. Uma fronteira de segurança não deve ser desenhada pela parte que está sendo verificada, e até este PR um pacote portátil não podia desenhar nenhuma. Agora pode, pelo preço de uma linha extra no seu manifesto.