Pular para o conteúdo principal

Documentação oficial desta página · v0.20.6

Segurança

O Hermes Agent foi projetado com um modelo de segurança em camadas. Esta página cobre cada fronteira — da aprovação de comandos ao isolamento em container e à autorização de usuários nas plataformas de mensagem.

Visão geral​

O modelo tem oito camadas:

  1. Autorização de usuário — quem pode falar com o agente (listas de permissão, pareamento por DM)
  2. Aprovação de comandos perigosos — uma pessoa no circuito para operações destrutivas
  3. Segurança na escrita de arquivos — lista de bloqueio e sandbox opcional para write_file e patch
  4. Isolamento em container — sandbox com Docker, Singularity ou Modal, com opções endurecidas
  5. Filtragem de credenciais no MCP — isolamento de variáveis de ambiente nos subprocessos MCP
  6. Varredura de arquivos de contexto — detecção de injeção de prompt nos arquivos do projeto
  7. Isolamento entre sessões — sessões não acessam dados nem estado umas das outras; os caminhos de armazenamento das tarefas de cron são endurecidos contra travessia de diretório
  8. Higienização de entrada — os parâmetros de diretório de trabalho nos backends de terminal são validados contra uma lista de permissão, para evitar injeção de shell

Aprovação de comandos perigosos​

Antes de executar qualquer comando, o Hermes o compara com uma lista curada de padrões perigosos. Se houver correspondência, o usuário precisa aprovar explicitamente.

Modos de aprovação​

São três modos, definidos em approvals.mode, no ~/.hermes/config.yaml:

approvals:
mode: smart # smart | manual | off
timeout: 300 # segundos de espera pela resposta (padrão: 300)
cron_mode: deny # deny | approve — o que as tarefas de cron fazem diante de um comando perigoso
single_query_mode: deny # deny | approve — o que sessões de pergunta única (-q) fazem
mcp_reload_confirm: true # /reload-mcp pergunta antes de invalidar o cache de ferramentas MCP
destructive_slash_confirm: true # /clear, /new, /reset e /undo perguntam antes de descartar estado

O conjunto completo de chaves:

ChavePadrãoO que controla
modesmartPolítica de aprovação para comandos de shell perigosos — veja a tabela abaixo.
timeout300Segundos que o Hermes espera por uma resposta antes de desistir.
cron_modedenyComo as tarefas de cron se comportam sem ninguém olhando, quando disparam uma aprovação. deny bloqueia o comando (o agente precisa achar outro caminho); approve aprova tudo em contexto de cron.
single_query_modedenyComo sessões de hermes chat -q se comportam ao disparar uma aprovação. Uma sessão -q roda um turno e sai, sem ninguém esperando para responder; deny bloqueia o comando, approve aprova tudo nesse contexto. Espelha o cron_mode.
mcp_reload_confirmtrueQuando verdadeiro, o /reload-mcp pergunta antes de reconstruir o conjunto de ferramentas MCP. Reconstruir invalida o cache de prompt do provedor (os esquemas das ferramentas vivem no prompt de sistema), então a mensagem seguinte reenvia os tokens de entrada inteiros. Quem clica em Always Approve muda essa chave para false.
destructive_slash_confirmtrueQuando verdadeiro, comandos de barra destrutivos (/clear, /new, /reset, /undo) perguntam antes de descartar o estado da conversa. Diálogo de três opções (aprovar uma vez, aprovar sempre, cancelar), com botões nativos de sim e não no Telegram, Discord e Slack, e texto nas outras plataformas. Quem clica em Always Approve muda essa chave para false. A TUI também respeita essa opção no modal de /clear, /new e /reset; HERMES_TUI_NO_CONFIRM=1 pula esse modal, independentemente do valor.
ModoComportamento
smart (padrão)Usa um modelo auxiliar para avaliar o risco. Comandos de risco baixo (por exemplo, python -c "print('hello')") são aprovados automaticamente, só para aquele comando. Comandos genuinamente perigosos são negados automaticamente. Os casos duvidosos vão para uma aprovação manual.
manualSempre pergunta ao usuário diante de um comando perigoso.
offDesliga todas as checagens de aprovação — equivalente a rodar com --yolo. Tudo executa sem pergunta.
aviso

Definir approvals.mode: off desliga todas as perguntas de segurança. Use só em ambientes de confiança (CI/CD, containers etc.).

Modo YOLO​

O modo YOLO ignora todas as aprovações de comando perigoso na sessão atual. Dá para ativar de três jeitos:

  1. Opção de linha de comando: abra a sessão com hermes --yolo ou hermes chat --yolo
  2. Comando de barra: digite /yolo durante a sessão para ligar e desligar
  3. Variável de ambiente: defina HERMES_YOLO_MODE=1

O /yolo é um interruptor — cada uso inverte o estado:

> /yolo
⚡ YOLO mode ON — all commands auto-approved. Use with caution.

> /yolo
⚠ YOLO mode OFF — dangerous commands will require approval.

O modo YOLO existe tanto na CLI quanto nas sessões de gateway. Por dentro, ele define a variável HERMES_YOLO_MODE, consultada antes de cada execução.

Com o YOLO ativo, o Hermes mostra dois lembretes visuais permanentes, para ficar difícil esquecer que as aprovações estão desligadas:

  • Uma linha vermelha no banner, no início da sessão, quando o YOLO já está ativo: ⚠ YOLO mode — all approval prompts bypassed. Fica escondida quando o YOLO está desligado, para o banner padrão não poluir.
  • Um trecho ⚠ YOLO na barra de status, em todas as larguras, atualizado na hora em que você liga ou desliga.
perigo

O modo YOLO desliga todas as checagens de comando perigoso da sessão — exceto a lista de bloqueio absoluto (veja abaixo). Use só quando você confia plenamente nos comandos que estão sendo gerados (por exemplo, scripts de automação bem testados em ambientes descartáveis).

Para os comandos de barra destrutivos (/clear, /new e /reset, /undo, /quit --delete — com /exit --delete como apelido), a CLI também pede confirmação antes de rodar. Veja comandos de barra.

Lista de bloqueio absoluto​

Alguns comandos são tão catastróficos — apagar o sistema de arquivos sem volta, bombas de fork, escrita direta em dispositivo de bloco — que o Hermes se recusa a rodá-los independentemente de:

  • --yolo ou /yolo ligados
  • approvals.mode: off
  • tarefas de cron rodando em modo approve
  • o usuário ter clicado em "permitir sempre"

A lista de bloqueio é o piso, abaixo do --yolo. Ela dispara antes da camada de aprovação sequer ver o comando, e não existe opção de contornar. Os padrões cobertos hoje (não é a lista inteira; ela acompanha tools/approval.py::UNRECOVERABLE_BLOCKLIST):

PadrãoPor que é absoluto
rm -rf / e variantes óbviasApaga a raiz do sistema de arquivos
rm -rf --no-preserve-root /A variante explícita "sim, eu quis dizer a raiz"
:(){ :|:& };: (bomba de fork no bash)Trava a máquina até reiniciar
mkfs.* num dispositivo raiz montadoFormata o sistema em uso
dd if=/dev/zero of=/dev/sd*Zera um disco físico
Canalizar URLs não confiáveis para sh na raizVetor de execução remota de código amplo demais para aprovar

Se você bater na lista de bloqueio, a chamada devolve um erro explicativo ao agente e nada roda. Se um fluxo legítimo precisa de um desses comandos (você opera um pipeline de formatar e reinstalar, por exemplo), rode fora do agente.

Regras de negação próprias (approvals.deny)​

A lista de bloqueio absoluto é fixa e vem no código. O approvals.deny é a contraparte editável: uma lista de padrões que bloqueiam comandos de terminal incondicionalmente — antes de --yolo, /yolo e approvals.mode: off serem consultados. Serve para rodar em modo yolo com exceções: "deixe o agente fazer tudo, menos estas coisas específicas, nunca".

approvals:
deny:
- "git push --force*"
- "*curl*|*sh*"
- "dd if=* of=/dev/*"

Detalhes:

  • Os padrões são globs de fnmatch (*, ?, [...]), comparados sem diferenciar maiúsculas contra o texto inteiro do comando. git push --force* casa com git push --force origin main, mas não com git push origin main.
  • A comparação roda sobre as mesmas variantes normalizadas e desofuscadas que o detector de padrões perigosos usa, então truques simples de aspas (git pu""sh --force) não passam.
  • Aspas no YAML: sempre coloque os padrões entre aspas. Um * solto no começo é um apelido de YAML e não compila; {, ! e : têm significado próprio. Aspas simples são as mais seguras para conteúdo de shell.
  • As regras de negação valem para os backends que alcançam a máquina (local, SSH, Docker com montagem do host). Backends de container isolado pulam a pilha de proteção inteira, como sempre fizeram — nada que rode lá dentro toca o host.
  • Um comando negado devolve um erro BLOCKED ao agente, dizendo para ele não tentar de novo nem reescrever. Nada roda.

Como o resto da configuração de aprovação, as mudanças valem na hora (o cache é indexado pela data de modificação) — não é preciso reiniciar a sessão.

Modelo de ameaça

Regras de negação são um freio contra um agente honesto mas errado, o mesmo modelo de ameaça do detector de padrões perigosos. Elas não são sandbox contra um processo deliberadamente hostil — para isso, use um backend isolado (Docker, Modal) ou um ambiente com saída restrita.

Tempo limite da aprovação​

Quando uma aprovação aparece, o usuário tem um tempo configurável para responder. Sem resposta dentro do prazo, o comando é negado por padrão (falha fechada).

approvals:
timeout: 300 # segundos (padrão: 300)

O que dispara aprovação​

Os padrões abaixo disparam aprovação (definidos em tools/approval.py):

PadrãoDescrição
rm -r / rm --recursiveRemoção recursiva
rm ... /Remoção em caminho raiz
chmod 777/666 / o+w / a+wPermissão de escrita para todo mundo
chmod --recursive com permissão inseguraEscrita para todos, recursiva
chown -R root / chown --recursive rootMudança recursiva de dono para root
mkfsFormatar sistema de arquivos
dd if=Cópia de disco
> /dev/sdEscrita em dispositivo de bloco
DROP TABLE/DATABASEDROP em SQL
DELETE FROM sem WHEREDELETE em SQL sem WHERE
TRUNCATE TABLETRUNCATE em SQL
> /etc/Sobrescrever configuração de sistema
systemctl stop/restart/disable/maskParar, reiniciar ou desabilitar serviços
kill -9 -1Matar todos os processos
pkill -9Matar processos à força
Padrões de bomba de forkBombas de fork
bash -c / sh -c / zsh -c / ksh -cExecução via -c (inclusive combinações como -lc)
python -e / perl -e / ruby -e / node -cExecução de script via -e ou -c
curl ... | sh / wget ... | shCanalizar conteúdo remoto para o shell
bash <(curl ...) / sh <(wget ...)Executar script remoto por substituição de processo
tee para /etc/, ~/.ssh/, ~/.hermes/.envSobrescrever arquivo sensível via tee
> / >> para /etc/, ~/.ssh/, ~/.hermes/.envSobrescrever arquivo sensível por redirecionamento
xargs rmxargs com rm
find -exec rm / find -deletefind com ações destrutivas
cp, mv ou install para /etc/Copiar ou mover arquivo para a configuração do sistema
sed -i / sed --in-place em /etc/Edição no lugar da configuração do sistema
pkill / killall em hermes ou gatewayEvita que o agente se mate
gateway run com &, disown, nohup ou setsidEvita subir o gateway fora do gerenciador de serviços
docker stop/kill/restart, docker compose down/stop/kill/restartCiclo de vida de container (pega também as opções globais e o docker-compose)
docker -H, --host, --context, DOCKER_HOST=, DOCKER_CONTEXT=Redirecionamento do daemon do Docker — o comando aponta para outro daemon, muitas vezes remoto
docker context useTroca o daemon padrão de todos os comandos docker seguintes
podman --remote, -r, --url, --connection, --identity, CONTAINER_HOST=Redirecionamento de daemon remoto do Podman
informação

Container dispensa a checagem: nos backends docker, singularity, modal, daytona e vercel_sandbox, as checagens de comando perigoso são puladas, porque o próprio container é a fronteira de segurança. Comandos destrutivos lá dentro não machucam o host.

Fluxo de aprovação na CLI​

Na CLI interativa, comandos perigosos mostram uma aprovação em linha:

⚠️ DANGEROUS COMMAND: recursive delete
rm -rf /tmp/old-project

[o]nce | [s]ession | [a]lways | [d]eny

Choice [o/s/a/D]:

As quatro opções:

  • once — permite só esta execução
  • session — permite esse padrão pelo resto da sessão
  • always — acrescenta à lista de permissão permanente (gravada no config.yaml)
  • deny (padrão) — bloqueia

Fluxo de aprovação no gateway​

Nas plataformas de mensagem, o agente manda os detalhes do comando perigoso para a conversa e espera a resposta:

  • Responda yes, y, approve, ok ou go para aprovar
  • Responda no, n, deny ou cancel para negar

A variável HERMES_EXEC_ASK=1 é definida automaticamente quando o gateway está rodando.

Lista de permissão permanente​

Comandos aprovados com "always" vão para o ~/.hermes/config.yaml:

# Padrões de comando perigoso permitidos permanentemente
command_allowlist:
- rm
- systemctl

Esses padrões são carregados na inicialização e aprovados em silêncio nas sessões seguintes.

dica

Use hermes config edit para revisar ou remover padrões da sua lista permanente.

Minerar o histórico de aprovações (hermes approvals suggest)​

Em vez de responder a mesma pergunta sessão após sessão, dá para transformar as decisões passadas em propostas de lista de permissão:

hermes approvals suggest # só simula — imprime uma proposta numerada
hermes approvals suggest --apply 1,3 # incorpora as escolhas ao command_allowlist
hermes approvals suggest --json # saída legível por máquina

O comando varre o banco de sessões (~/.hermes/state.db) atrás de comandos classificados como perigosos que de fato executaram — ou seja, os que você aprovou —, agrega em padrões (git push *, ou a chave da classe perigosa no caso de comandos compostos) e ordena por frequência de aprovação:

Proposed command_allowlist additions (from approval history, last 90 days):

1. git push * — approved 14x
2. docker restart/stop/kill (container lifecycle) — approved 9x (class key)

Regras de segurança:

  • Nada é aplicado automaticamente — a execução padrão é só leitura; só um --apply N[,M...] explícito escreve no config.yaml.
  • Classes destrutivas nunca são propostas, por mais que tenham sido aprovadas: remoções recursivas, sudo, escrita em disco e dispositivo, edição de credenciais e de configuração do sistema, canalização para shell, DROP e TRUNCATE em SQL, morte de processos e todas as classes da lista de bloqueio absoluto ficam de fora. rm -rf build/ aprovado 100 vezes ainda assim nunca vira uma entrada rm.
  • Propostas já cobertas pelo seu command_allowlist são puladas.

Opções úteis: --days N (janela do histórico, padrão 90), --min-count N (mínimo de aprovações para entrar, padrão 2), --limit N e --db CAMINHO.

Segurança na escrita de arquivos​

Antes de o write_file ou o patch tocarem o disco, o Hermes confere o caminho de destino contra uma lista de bloqueio e um sandbox opcional. Escritas bloqueadas devolvem erro ao agente na hora — não existe pergunta de aprovação e não dá para contornar pela conversa. O modelo ainda pode afirmar que a edição deu certo; com display.file_mutation_verifier ligado (o padrão), confie no rodapé do verificador de mudanças mais do que no resumo final do assistente.

Caminhos protegidos, sempre bloqueados​

Estas categorias são sempre negadas, mesmo sem HERMES_WRITE_SAFE_ROOT definido:

CategoriaExemplos
Credenciais do sistema~/.ssh/ (chaves, authorized_keys), ~/.aws/, ~/.kube/, /etc/sudoers, ~/.netrc
Credenciais do Hermesauth.json, .env, .anthropic_oauth.json, mcp-tokens/, pairing/ dentro do HERMES_HOME (perfil ativo e raiz global)
Arquivos de segredo do projeto.env, .env.local, .env.production, .envrc, em qualquer lugar do disco

Caminhos sensíveis dentro da raiz segura continuam bloqueados — apontar o HERMES_WRITE_SAFE_ROOT para $HOME não libera escrever em ~/.ssh/id_rsa.

Violações da raiz segura devolvem Write denied: '…' is outside HERMES_WRITE_SAFE_ROOT (…). Bloqueios de caminho de credencial usam Write denied: '…' is a protected system/credential file.

Exceção — o ~/.ssh/config passa por aprovação, não por bloqueio. O arquivo de configuração do cliente SSH não guarda chave privada, e editá-lo (apelidos de host, ProxyJump, destinos do Remote-SSH do VS Code) é tarefa rotineira. Então o write_file e o patch mandam esse caminho pela mesma aprovação de uma vez, sessão ou sempre que a ferramenta de terminal já usa para escritas em ~/.ssh — em vez da recusa seca de antes. Ele ainda pode carregar diretivas ProxyCommand e Match exec, que rodam comandos, então a escrita nunca é silenciosa. Chamadores não interativos (a ponte de arquivos do ACP, tarefas de fundo sem canal humano) falham fechado. Chaves privadas, authorized_keys e todo o resto de ~/.ssh/ seguem bloqueados.

HERMES_WRITE_SAFE_ROOT (sandbox opcional)​

Quando definida, o write_file e o patch só podem escrever dentro dos prefixos de diretório listados. Fora disso, é bloqueio duro — não passa pela aprovação de comando perigoso.

  • Vem definida na imagem Docker oficial (HERMES_WRITE_SAFE_ROOT=/opt/data)
  • Aceita várias raízes, separadas por : no Unix ou ; no Windows
  • Não acrescente no ~/.hermes/.env sem pensar. Se você apontar para um diretório de projeto, o agente não consegue escrever em ~/.hermes/cron/jobs.json, nas skills do perfil, nem em outro estado do Hermes fora daquele prefixo

Para liberar o espaço de trabalho e o diretório do Hermes:

export HERMES_WRITE_SAFE_ROOT=/caminho/do/projeto:/home/voce/.hermes

Apague a variável para voltar à escrita sem restrição (ainda sujeita à lista de caminhos protegidos). Referência completa: HERMES_WRITE_SAFE_ROOT.

Cron e o resto do estado do Hermes​

Não peça ao agente para dar patch direto no ~/.hermes/cron/jobs.json. Use a ferramenta cronjob, o hermes cron ou o /cron — eles atualizam o repositório de tarefas pela API suportada. O mesmo vale para os outros arquivos de controle do Hermes quando a proteção de escrita bloquear a edição direta.

Defesa em camadas, não uma fronteira dura

As proteções de escrita valem só para write_file e patch. A ferramenta terminal roda como o mesmo usuário do sistema e ainda consegue ler ou sobrescrever caminhos negados por comando de shell. A lista de bloqueio reduz dano acidental e dá ao modelo um sinal claro de parada; ela não coloca em sandbox um agente hostil ou comprometido.

Autorização de usuários (gateway)​

Com o gateway de mensagens rodando, o Hermes controla quem pode interagir com o bot por um sistema de autorização em camadas.

Ordem das checagens​

O método _is_user_authorized() confere nesta ordem:

  1. Liberação geral por plataforma (por exemplo, DISCORD_ALLOW_ALL_USERS=true)
  2. Lista de aprovados por pareamento (usuários aprovados por código)
  3. Listas de permissão da plataforma (por exemplo, TELEGRAM_ALLOWED_USERS=12345,67890)
  4. Lista de permissão global (GATEWAY_ALLOWED_USERS=12345,67890)
  5. Liberação geral global (GATEWAY_ALLOW_ALL_USERS=true)
  6. Padrão: negar

Listas por plataforma​

Defina os IDs permitidos, separados por vírgula, em ~/.hermes/.env:

# Listas por plataforma
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=111222333444555666
WHATSAPP_ALLOWED_USERS=15551234567
SLACK_ALLOWED_USERS=U01ABC123

# Lista válida para todas as plataformas
GATEWAY_ALLOWED_USERS=123456789

# Liberação geral por plataforma (use com cuidado)
DISCORD_ALLOW_ALL_USERS=true

# Liberação geral global (use com muito cuidado)
GATEWAY_ALLOW_ALL_USERS=true
aviso

Se nenhuma lista estiver configurada e o GATEWAY_ALLOW_ALL_USERS não estiver definido, todos os usuários são negados. O gateway avisa na inicialização:

No user allowlists configured. All unauthorized users will be denied.
Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access,
or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id).

Pareamento por mensagem direta​

Para uma autorização mais flexível, o Hermes tem um sistema de pareamento por código. Em vez de exigir os IDs de antemão, usuários desconhecidos recebem um código de uso único, que o dono do bot aprova pela CLI.

Como funciona:

  1. Um usuário desconhecido manda uma mensagem direta ao bot
  2. O bot responde com um código de 8 caracteres
  3. O dono do bot roda hermes pairing approve <plataforma> <código> na CLI
  4. O usuário fica aprovado permanentemente naquela plataforma

Controle o que acontece com mensagens diretas não autorizadas em ~/.hermes/config.yaml:

unauthorized_dm_behavior: pair

whatsapp:
unauthorized_dm_behavior: ignore
  • pair é o padrão nas plataformas de conversa. Mensagens não autorizadas recebem um código.
  • ignore descarta em silêncio.
  • O e-mail usa ignore por padrão, a não ser que platforms.email.unauthorized_dm_behavior: pair seja definido, porque caixas de entrada costumam ter mensagens não lidas sem relação nenhuma.
  • As seções de plataforma sobrepõem o padrão global, então dá para manter o pareamento no Telegram e o silêncio no WhatsApp.

Recursos de segurança (baseados em orientações da OWASP e do NIST SP 800-63-4):

RecursoDetalhe
Formato do código8 caracteres, de um alfabeto de 32 sem ambiguidade (sem 0/O nem 1/I)
AleatoriedadeCriptográfica (secrets.choice())
Validade1 hora
Limite de taxa1 pedido por usuário a cada 10 minutos
Limite de pendentesNo máximo 3 códigos pendentes por plataforma
Bloqueio5 tentativas de aprovação erradas geram 1 hora de bloqueio
Permissão dos arquivoschmod 0600 em todos os arquivos de pareamento
RegistroOs códigos nunca vão para o log

Comandos de pareamento:

# Lista pendentes e aprovados
hermes pairing list

# Aprova um código
hermes pairing approve telegram ABC12DEF

# Revoga o acesso de um usuário
hermes pairing revoke telegram 123456789

# Limpa todos os códigos pendentes
hermes pairing clear-pending
No Docker: rode os comandos de pareamento como usuário hermes

A imagem Docker oficial roda o gateway como o usuário sem privilégios hermes (uid 10000), via gosu, mas o docker exec entra como root por padrão. Arquivos de aprovação criados pelo root ficam com modo 0600 root:root e o gateway não consegue lê-los — a aprovação é ignorada em silêncio.

Sempre passe -u hermes:

docker exec -u hermes hermes-agent hermes pairing approve telegram ABC12DEF

Se você já rodou como root e o usuário continua sem autorização, reinicie o container — o ponto de entrada corrige a propriedade dos arquivos na próxima inicialização.

Armazenamento: os dados de pareamento ficam em ~/.hermes/pairing/, em arquivos JSON por plataforma:

  • {plataforma}-pending.json — pedidos pendentes
  • {plataforma}-approved.json — usuários aprovados
  • _rate_limits.json — controle de limite de taxa e bloqueio

Isolamento em container​

No backend de terminal docker, o Hermes aplica endurecimento de segurança em todo container.

Opções de segurança do Docker​

Todo container roda com estas opções (definidas em tools/environments/docker.py):

_BASE_SECURITY_ARGS = [
"--cap-drop", "ALL", # remove TODAS as capacidades do Linux
"--cap-add", "DAC_OVERRIDE", # o root pode escrever nos diretórios montados
"--cap-add", "CHOWN", # gerenciadores de pacote precisam disso
"--cap-add", "FOWNER", # gerenciadores de pacote precisam disso
"--security-opt", "no-new-privileges", # bloqueia escalada de privilégio
"--pids-limit", "256", # limita a quantidade de processos
"--tmpfs", "/tmp:rw,nosuid,size=512m", # /tmp com tamanho limitado
"--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m", # /var/tmp sem execução
]

SETUID e SETGID não estão na lista base — eles entram só quando o container começa como root e um processo de inicialização precisa abrir mão de privilégio (o caminho do s6). São pulados quando o container já roda com um --user sem privilégio. O tmpfs de /run também fica fora da lista base e é montado conforme a imagem (endurecido com noexec por padrão, com exec só nas imagens de s6-overlay, que executam a partir de /run).

Limites de recurso​

Configuráveis em ~/.hermes/config.yaml:

terminal:
backend: docker
docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
docker_forward_env: [] # só lista explícita; vazio mantém os segredos fora do container
container_cpu: 1 # núcleos de CPU
container_memory: 5120 # MB (padrão 5 GB)
container_disk: 51200 # MB (padrão 50 GB, exige overlay2 em XFS)
container_persistent: true # mantém o sistema de arquivos entre sessões

Persistência do sistema de arquivos​

  • Modo persistente (container_persistent: true): monta /workspace e /root a partir de ~/.hermes/sandboxes/docker/<id_da_tarefa>/
  • Modo efêmero (container_persistent: false): usa tmpfs para o espaço de trabalho — tudo se perde na limpeza
dica

Em gateways de produção, use os backends docker, modal, daytona ou vercel_sandbox para isolar os comandos do agente do seu sistema. Isso elimina de vez a necessidade de aprovação de comando perigoso.

aviso

Se você acrescentar nomes a terminal.docker_forward_env, essas variáveis são injetadas de propósito no container. É útil para credenciais específicas da tarefa, como GITHUB_TOKEN, mas também significa que o código rodando lá dentro pode lê-las e vazá-las.

Comparação de segurança entre backends de terminal​

BackendIsolamentoChecagem de comando perigosoMelhor para
localNenhum — roda na sua máquinaSimDesenvolvimento, usuários de confiança
sshMáquina remotaSimRodar num servidor separado
dockerContainerPulada (o container é a fronteira)Gateway em produção
singularityContainerPuladaAmbientes de computação de alto desempenho
modalSandbox na nuvemPuladaIsolamento escalável na nuvem
daytonaSandbox na nuvemPuladaEspaços de trabalho persistentes na nuvem
vercel_sandboxMicroVM na nuvemPuladaExecução na nuvem com retratos persistentes

Repasse de variáveis de ambiente​

Tanto o execute_code quanto o terminal removem as variáveis de ambiente sensíveis dos processos filhos, para evitar que código gerado pelo modelo vaze credenciais. Só que skills que declaram required_environment_variables precisam legitimamente daquelas variáveis.

Como funciona​

Dois mecanismos deixam variáveis específicas passarem pelos filtros.

1. Repasse por skill (automático)

Quando uma skill é carregada (por skill_view ou pelo comando /skill) e declara required_environment_variables, as variáveis que de fato existem no ambiente são registradas para repasse automaticamente. As que faltam (ainda em estado de configuração pendente) não são registradas.

# no frontmatter do SKILL.md
required_environment_variables:
- name: TENOR_API_KEY
prompt: Tenor API key
help: Get a key from https://developers.google.com/tenor

Depois de carregar essa skill, o TENOR_API_KEY passa para o execute_code, para o terminal local e para os backends remotos (Docker, Modal) — sem configuração manual.

Docker e Modal

Antes da v0.5.1, o forward_env do Docker era um sistema separado do repasse de skills. Agora eles estão unidos — as variáveis declaradas por skill são encaminhadas para containers Docker e sandboxes Modal automaticamente, sem precisar acrescentá-las ao docker_forward_env na mão.

2. Repasse por configuração (manual)

Para variáveis que nenhuma skill declara, acrescente-as em terminal.env_passthrough:

terminal:
env_passthrough:
- MINHA_CHAVE
- OUTRO_TOKEN

Repasse de arquivos de credencial​

Algumas skills precisam de arquivos no sandbox, não só de variáveis — o Google Workspace, por exemplo, guarda os tokens OAuth num google_token.json dentro do HERMES_HOME do perfil ativo. As skills declaram isso no frontmatter:

required_credential_files:
- path: google_token.json
description: Google OAuth2 token (created by setup script)
- path: google_client_secret.json
description: Google OAuth2 client credentials

Ao carregar, o Hermes confere se esses arquivos existem no HERMES_HOME do perfil ativo e os registra para montagem:

  • Docker: montagem só de leitura (-v host:container:ro)
  • Modal: montado na criação do sandbox e sincronizado antes de cada comando (cobre configuração de OAuth feita no meio da sessão)
  • Local: nada a fazer, os arquivos já estão acessíveis

Também dá para listar os arquivos na mão, no config.yaml:

terminal:
credential_files:
- google_token.json
- meu_token_oauth.json

Os caminhos são relativos a ~/.hermes/. Os arquivos são montados em /root/.hermes/ dentro do container. Essa lista é lida por tools/credential_files.py (terminal.credential_files) — ela mora dentro do bloco terminal:, mas é carregada pelo módulo de arquivos de credencial, não pelo backend de terminal, então não faz parte do DEFAULT_CONFIG que vem no pacote.

O que cada sandbox filtra​

SandboxFiltro padrãoComo liberar
execute_codeBloqueia variáveis com KEY, TOKEN, SECRET, PASSWORD, CREDENTIAL, PASSWD ou AUTH no nome; só deixa passar as de prefixo seguroVariáveis de repasse ignoram as duas checagens
terminal (local)Bloqueia as variáveis de infraestrutura do Hermes (chaves de provedor, tokens de gateway, chaves de ferramenta)Variáveis de repasse ignoram a lista de bloqueio
terminal (Docker)Nenhuma variável do host, por padrãoVariáveis de repasse mais docker_forward_env, encaminhadas por -e
terminal (Modal)Nenhuma variável nem arquivo do host, por padrãoArquivos de credencial montados; variáveis por sincronização
MCPBloqueia tudo, exceto as variáveis de sistema seguras e o env configurado explicitamenteNão é afetado pelo repasse (use o env do MCP)

Considerações de segurança​

  • O repasse só afeta variáveis que você ou suas skills declaram explicitamente — a postura padrão para código arbitrário gerado pelo modelo continua a mesma
  • Arquivos de credencial são montados só para leitura nos containers Docker
  • O Skills Guard varre o conteúdo das skills atrás de padrões suspeitos de acesso a variáveis, antes da instalação
  • Variáveis ausentes nunca são registradas (não dá para vazar o que não existe)
  • Segredos de infraestrutura do Hermes (chaves de provedor, tokens de gateway) nunca devem entrar no env_passthrough — eles têm mecanismos próprios

Credenciais no MCP​

Os subprocessos de servidor MCP recebem um ambiente filtrado, para evitar vazamento acidental de credencial.

Variáveis consideradas seguras​

Só estas passam do host para os subprocessos MCP por stdio:

PATH, HOME, USER, LANG, LC_ALL, TERM, SHELL, TMPDIR

Mais as variáveis XDG_*. Todas as outras (chaves de API, tokens, segredos) são removidas.

As variáveis definidas explicitamente no env do servidor MCP passam:

mcp_servers:
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..." # só esta passa

Ocultação de credenciais​

As mensagens de erro das ferramentas MCP são higienizadas antes de voltarem ao modelo. Estes padrões viram [REDACTED]:

  • tokens pessoais do GitHub (ghp_...)
  • chaves no estilo da OpenAI (sk-...)
  • tokens Bearer
  • parâmetros token=, key=, API_KEY=, password= e secret=

Política de acesso a sites​

Você pode restringir quais sites o agente alcança pelas ferramentas de web e de navegador. Serve para impedir acesso a serviços internos, painéis administrativos e outras URLs sensíveis.

# em ~/.hermes/config.yaml
security:
website_blocklist:
enabled: true
domains:
- "*.internal.empresa.com"
- "admin.exemplo.com"
shared_files:
- "/etc/hermes/blocked-sites.txt"

Quando uma URL bloqueada é pedida, a ferramenta devolve um erro explicando que o domínio está bloqueado por política. A lista vale para web_search, web_extract, browser_navigate e todas as ferramentas que aceitam URL.

Proteção contra SSRF​

Todas as ferramentas que aceitam URL (busca na web, extração, visão, navegador) validam o endereço antes de buscar, para evitar ataques de falsificação de requisição do lado do servidor. Endereços bloqueados:

  • Redes privadas (RFC 1918): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
  • Loopback: 127.0.0.0/8, ::1
  • Link-local: 169.254.0.0/16 (inclui os metadados de nuvem em 169.254.169.254)
  • CGNAT, espaço compartilhado (RFC 6598): 100.64.0.0/10 (Tailscale, VPNs WireGuard)
  • Nomes de metadados de nuvem: metadata.google.internal, metadata.goog
  • Endereços reservados, multicast e não especificados

A proteção fica sempre ativa para uso voltado à internet, e falhas de DNS contam como bloqueio (falha fechada). Cadeias de redirecionamento são revalidadas em cada salto, para impedir desvios por redirecionamento.

Permitir URLs privadas de propósito​

Algumas instalações precisam mesmo de acesso interno — redes domésticas que resolvem home.arpa para faixas privadas, endpoints de Ollama ou llama.cpp só na rede local, wikis internas, depuração de metadados de nuvem. Para esses casos existe uma saída global:

security:
allow_private_urls: true # padrão: false

Ligada, as ferramentas de web, o navegador, as buscas de imagem por URL e os downloads de mídia do gateway deixam de recusar destinos privados, de loopback, link-local, CGNAT e de metadados de nuvem. Isso é uma fronteira de confiança deliberada — só ligue em máquinas onde o agente acessar a rede local a partir de URLs injetadas em prompt seja um risco aceitável. Gateways expostos à internet devem deixar desligado.

A proteção contra domínios parecidos por truque de Unicode continua ativa, independentemente dessa opção.

Varredura com o Tirith antes da execução​

O Hermes integra o tirith para varredura do conteúdo dos comandos antes da execução. Ele detecta ameaças que a comparação de padrões sozinha não pega:

  • falsificação de URL por homógrafos (ataques com domínios internacionalizados)
  • padrões de canalizar para interpretador (curl | bash, wget | sh)
  • ataques de injeção no terminal

O tirith se instala sozinho, a partir das releases do GitHub, no primeiro uso, com verificação de soma SHA-256 (e verificação de procedência por cosign, se o cosign estiver disponível).

# em ~/.hermes/config.yaml
security:
tirith_enabled: true # liga e desliga a varredura (padrão: true)
tirith_path: "tirith" # caminho do binário (padrão: buscar no PATH)
tirith_timeout: 5 # tempo limite do subprocesso, em segundos
tirith_fail_open: true # permite executar quando o tirith não está disponível (padrão: true)

Com tirith_fail_open: true (o padrão), os comandos seguem quando o tirith não está instalado ou estoura o tempo. Coloque false em ambientes de segurança alta, para bloquear comandos quando ele não estiver disponível.

O tirith publica binários prontos para Linux (x86_64 e aarch64) e macOS (x86_64 e arm64). Em plataformas sem binário pronto (Windows, por exemplo), ele é pulado em silêncio — as proteções por padrão continuam rodando, e a CLI não mostra aviso de indisponibilidade. Para usar o tirith no Windows, rode o Hermes no WSL.

O veredito do tirith entra no fluxo de aprovação: comandos seguros passam, e tanto os suspeitos quanto os bloqueados vão para aprovação do usuário, com os achados completos (severidade, título, descrição, alternativas mais seguras). Você pode aprovar ou negar — a escolha padrão é negar, para manter cenários sem supervisão seguros.

Proteção contra injeção em arquivos de contexto​

Arquivos de contexto (AGENTS.md, .cursorrules, SOUL.md) são varridos atrás de injeção de prompt antes de entrarem no prompt de sistema. A varredura procura:

  • instruções mandando ignorar ou desconsiderar instruções anteriores
  • comentários HTML escondidos com palavras suspeitas
  • tentativas de ler segredos (.env, credentials, .netrc)
  • vazamento de credencial por curl
  • caracteres Unicode invisíveis (espaços de largura zero, inversões de direção)

Arquivos bloqueados mostram um aviso:

[BLOCKED: AGENTS.md contained potential prompt injection (prompt_injection). Content not loaded.]

Boas práticas para produção​

Lista de conferência do gateway​

  1. Defina listas de permissão explícitas — nunca use GATEWAY_ALLOW_ALL_USERS=true em produção
  2. Use um backend de container — defina terminal.backend: docker no config.yaml
  3. Limite os recursos — defina CPU, memória e disco adequados
  4. Guarde os segredos direito — mantenha as chaves em ~/.hermes/.env, com permissão correta
  5. Use pareamento por mensagem — códigos em vez de IDs fixos, sempre que possível
  6. Revise a lista de comandos permitidos — audite o command_allowlist de tempos em tempos
  7. Defina o terminal.cwd — não deixe o agente operar a partir de diretórios sensíveis
  8. Não rode como root — nunca suba o gateway como root
  9. Acompanhe os logs — confira ~/.hermes/logs/ atrás de tentativas de acesso não autorizado
  10. Mantenha atualizado — rode hermes update com regularidade, por causa das correções de segurança

Proteger as chaves de API​

# Permissão correta no arquivo .env
chmod 600 ~/.hermes/.env

# Mantenha chaves separadas por serviço
# Nunca versione arquivos .env

Isolamento de rede​

Para segurança máxima, rode o gateway numa máquina ou VM separada. Defina terminal.backend: ssh no config.yaml e passe os dados do host por variáveis de ambiente, em ~/.hermes/.env:

# ~/.hermes/config.yaml
terminal:
backend: ssh
# ~/.hermes/.env
TERMINAL_SSH_HOST=agent-worker.local
TERMINAL_SSH_USER=hermes
TERMINAL_SSH_KEY=~/.ssh/hermes_agent_key

Os dados da conexão SSH ficam no .env, e não no config.yaml, para não serem versionados nem compartilhados junto com exportações de perfil. Isso mantém as conexões de mensagem do gateway separadas da execução de comandos do agente.

Alertas de cadeia de suprimentos​

O Hermes traz um verificador de alertas embutido, que aponta pacotes Python no ambiente virtual ativo que batem com um catálogo curado de versões sabidamente comprometidas (vermes de cadeia de suprimentos, como o envenenamento do mistralai 2.4.6 em maio de 2026). A implementação está em hermes_cli/security_advisories.py.

Quando ele roda:

  • No banner da CLI. Um aviso de uma linha é impresso se houver correspondência, apontando para o hermes doctor, que traz a correção completa.
  • No hermes doctor. Mostra cada alerta ativo, com as versões e de 2 a 4 passos de correção.
  • Na inicialização do gateway. Registrado no gateway.log; a primeira mensagem interativa recebe um aviso curto para quem opera.

Cada alerta tem um identificador estável. Depois de ler e resolver, dá para dispensá-lo de vez:

hermes doctor --ack <id-do-alerta>

A dispensa fica gravada em config.security.acked_advisories e sobrevive a reinícios. Alertas antigos não são removidos do catálogo de propósito — deixá-los ali mantém instalações novas avisadas sobre versões historicamente envenenadas, que ainda podem estar em cache num espelho privado.

A checagem em si usa só a biblioteca padrão e faz uma consulta de importlib.metadata.version() por alerta, então é segura de rodar em toda inicialização.

Instalação sob demanda de dependências opcionais​

Vários recursos (TTS da Mistral, ElevenLabs, memória Honcho, Bedrock, Slack, Matrix e outros) dependem de pacotes Python que nem todo mundo precisa. O Hermes instala esses pacotes sob demanda, no primeiro uso, em vez de já no hermes-agent[all]. A implementação está em tools/lazy_deps.py.

O que isso resolve:

  • Fragilidade. Quando uma dependência indireta de um extra some do PyPI (quarentena por malware, remoção, upload quebrado), a resolução do [all] inteiro falhava e instalações novas caíam em silêncio para um nível reduzido — perdendo mais de 10 extras sem relação de uma vez. A instalação sob demanda isola cada backend, então uma dependência envenenada não quebra recursos alheios.
  • Peso. Quem só conversa com um provedor deixa de baixar centenas de pacotes que nunca vai importar.

Como funciona:

  1. Um módulo de backend chama ensure("nome.do.recurso") no início do primeiro import.
  2. Se faltam dependências, o ensure confere security.allow_lazy_installs no config.yaml (padrão true) e roda um pip install restrito ao ambiente virtual, com as versões da lista de permissão.
  3. Se a instalação falha, ou se o usuário desligou a instalação sob demanda, a chamada levanta FeatureUnavailable, com o erro real do pip e um apontamento para o hermes tools.

Garantias de segurança do tools/lazy_deps.py:

GarantiaO que significa
Só no ambiente virtualA instalação usa o sys.executable do ambiente ativo — nunca o Python do sistema
PyPI, só por nomeAs especificações aceitam a sintaxe "pacote>=1.0,<2". Sem --index-url, sem git+https://, sem caminhos de arquivo — um config.yaml malicioso não consegue redirecionar a instalação
Lista de permissãoSó o que aparece no mapa LAZY_DEPS, dentro do código, pode ser instalado por esse caminho. Um erro de digitação no nome do recurso NÃO vira permissão de instalar qualquer coisa
DesligávelDefina security.allow_lazy_installs: false para desligar as instalações em tempo de execução. Útil em redes restritas ou posturas de segurança rígidas
Sem repetição silenciosaFalhas aparecem como FeatureUnavailable — sem guardar estado ruim, sem tempestade de tentativas

Para desligar:

# ~/.hermes/config.yaml
security:
allow_lazy_installs: false

Desligado, os backends que precisam de dependências opcionais avisam o usuário para instalar na mão (pip install …) ou escolher outro backend com hermes tools.