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:
- Autorização de usuário — quem pode falar com o agente (listas de permissão, pareamento por DM)
- Aprovação de comandos perigosos — uma pessoa no circuito para operações destrutivas
- Segurança na escrita de arquivos — lista de bloqueio e sandbox opcional para
write_fileepatch - Isolamento em container — sandbox com Docker, Singularity ou Modal, com opções endurecidas
- Filtragem de credenciais no MCP — isolamento de variáveis de ambiente nos subprocessos MCP
- Varredura de arquivos de contexto — detecção de injeção de prompt nos arquivos do projeto
- 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
- 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:
| Chave | Padrão | O que controla |
|---|---|---|
mode | smart | Política de aprovação para comandos de shell perigosos — veja a tabela abaixo. |
timeout | 300 | Segundos que o Hermes espera por uma resposta antes de desistir. |
cron_mode | deny | Como 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_mode | deny | Como 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_confirm | true | Quando 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_confirm | true | Quando 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. |
| Modo | Comportamento |
|---|---|
| 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. |
| manual | Sempre pergunta ao usuário diante de um comando perigoso. |
| off | Desliga todas as checagens de aprovação — equivalente a rodar com --yolo. Tudo executa sem pergunta. |
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:
- Opção de linha de comando: abra a sessão com
hermes --yoloouhermes chat --yolo - Comando de barra: digite
/yolodurante a sessão para ligar e desligar - 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
⚠ YOLOna barra de status, em todas as larguras, atualizado na hora em que você liga ou desliga.
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:
--yoloou/yololigadosapprovals.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ão | Por que é absoluto |
|---|---|
rm -rf / e variantes óbvias | Apaga 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 montado | Formata 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 raiz | Vetor 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 comgit push --force origin main, mas não comgit 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.
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ão | Descrição |
|---|---|
rm -r / rm --recursive | Remoção recursiva |
rm ... / | Remoção em caminho raiz |
chmod 777/666 / o+w / a+w | Permissão de escrita para todo mundo |
chmod --recursive com permissão insegura | Escrita para todos, recursiva |
chown -R root / chown --recursive root | Mudança recursiva de dono para root |
mkfs | Formatar sistema de arquivos |
dd if= | Cópia de disco |
> /dev/sd | Escrita em dispositivo de bloco |
DROP TABLE/DATABASE | DROP em SQL |
DELETE FROM sem WHERE | DELETE em SQL sem WHERE |
TRUNCATE TABLE | TRUNCATE em SQL |
> /etc/ | Sobrescrever configuração de sistema |
systemctl stop/restart/disable/mask | Parar, reiniciar ou desabilitar serviços |
kill -9 -1 | Matar todos os processos |
pkill -9 | Matar processos à força |
| Padrões de bomba de fork | Bombas de fork |
bash -c / sh -c / zsh -c / ksh -c | Execução via -c (inclusive combinações como -lc) |
python -e / perl -e / ruby -e / node -c | Execução de script via -e ou -c |
curl ... | sh / wget ... | sh | Canalizar conteúdo remoto para o shell |
bash <(curl ...) / sh <(wget ...) | Executar script remoto por substituição de processo |
tee para /etc/, ~/.ssh/, ~/.hermes/.env | Sobrescrever arquivo sensível via tee |
> / >> para /etc/, ~/.ssh/, ~/.hermes/.env | Sobrescrever arquivo sensível por redirecionamento |
xargs rm | xargs com rm |
find -exec rm / find -delete | find 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 gateway | Evita que o agente se mate |
gateway run com &, disown, nohup ou setsid | Evita subir o gateway fora do gerenciador de serviços |
docker stop/kill/restart, docker compose down/stop/kill/restart | Ciclo 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 use | Troca o daemon padrão de todos os comandos docker seguintes |
podman --remote, -r, --url, --connection, --identity, CONTAINER_HOST= | Redirecionamento de daemon remoto do Podman |
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.
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 noconfig.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 entradarm. - Propostas já cobertas pelo seu
command_allowlistsã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:
| Categoria | Exemplos |
|---|---|
| Credenciais do sistema | ~/.ssh/ (chaves, authorized_keys), ~/.aws/, ~/.kube/, /etc/sudoers, ~/.netrc |
| Credenciais do Hermes | auth.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/.envsem 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.
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:
- Liberação geral por plataforma (por exemplo,
DISCORD_ALLOW_ALL_USERS=true) - Lista de aprovados por pareamento (usuários aprovados por código)
- Listas de permissão da plataforma (por exemplo,
TELEGRAM_ALLOWED_USERS=12345,67890) - Lista de permissão global (
GATEWAY_ALLOWED_USERS=12345,67890) - Liberação geral global (
GATEWAY_ALLOW_ALL_USERS=true) - 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
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:
- Um usuário desconhecido manda uma mensagem direta ao bot
- O bot responde com um código de 8 caracteres
- O dono do bot roda
hermes pairing approve <plataforma> <código>na CLI - 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.ignoredescarta em silêncio.- O e-mail usa
ignorepor padrão, a não ser queplatforms.email.unauthorized_dm_behavior: pairseja 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):
| Recurso | Detalhe |
|---|---|
| Formato do código | 8 caracteres, de um alfabeto de 32 sem ambiguidade (sem 0/O nem 1/I) |
| Aleatoriedade | Criptográfica (secrets.choice()) |
| Validade | 1 hora |
| Limite de taxa | 1 pedido por usuário a cada 10 minutos |
| Limite de pendentes | No máximo 3 códigos pendentes por plataforma |
| Bloqueio | 5 tentativas de aprovação erradas geram 1 hora de bloqueio |
| Permissão dos arquivos | chmod 0600 em todos os arquivos de pareamento |
| Registro | Os 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
hermesA 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/workspacee/roota 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
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.
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
| Backend | Isolamento | Checagem de comando perigoso | Melhor para |
|---|---|---|---|
| local | Nenhum — roda na sua máquina | Sim | Desenvolvimento, usuários de confiança |
| ssh | Máquina remota | Sim | Rodar num servidor separado |
| docker | Container | Pulada (o container é a fronteira) | Gateway em produção |
| singularity | Container | Pulada | Ambientes de computação de alto desempenho |
| modal | Sandbox na nuvem | Pulada | Isolamento escalável na nuvem |
| daytona | Sandbox na nuvem | Pulada | Espaços de trabalho persistentes na nuvem |
| vercel_sandbox | MicroVM na nuvem | Pulada | Execuçã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.
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
| Sandbox | Filtro padrão | Como liberar |
|---|---|---|
| execute_code | Bloqueia variáveis com KEY, TOKEN, SECRET, PASSWORD, CREDENTIAL, PASSWD ou AUTH no nome; só deixa passar as de prefixo seguro | Variá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ão | Variáveis de repasse mais docker_forward_env, encaminhadas por -e |
| terminal (Modal) | Nenhuma variável nem arquivo do host, por padrão | Arquivos de credencial montados; variáveis por sincronização |
| MCP | Bloqueia tudo, exceto as variáveis de sistema seguras e o env configurado explicitamente | Nã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=esecret=
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 em169.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
- Defina listas de permissão explícitas — nunca use
GATEWAY_ALLOW_ALL_USERS=trueem produção - Use um backend de container — defina
terminal.backend: dockerno config.yaml - Limite os recursos — defina CPU, memória e disco adequados
- Guarde os segredos direito — mantenha as chaves em
~/.hermes/.env, com permissão correta - Use pareamento por mensagem — códigos em vez de IDs fixos, sempre que possível
- Revise a lista de comandos permitidos — audite o
command_allowlistde tempos em tempos - Defina o
terminal.cwd— não deixe o agente operar a partir de diretórios sensíveis - Não rode como root — nunca suba o gateway como root
- Acompanhe os logs — confira
~/.hermes/logs/atrás de tentativas de acesso não autorizado - Mantenha atualizado — rode
hermes updatecom 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:
- Um módulo de backend chama
ensure("nome.do.recurso")no início do primeiro import. - Se faltam dependências, o
ensureconferesecurity.allow_lazy_installsnoconfig.yaml(padrãotrue) e roda umpip installrestrito ao ambiente virtual, com as versões da lista de permissão. - 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 ohermes tools.
Garantias de segurança do tools/lazy_deps.py:
| Garantia | O que significa |
|---|---|
| Só no ambiente virtual | A instalação usa o sys.executable do ambiente ativo — nunca o Python do sistema |
| PyPI, só por nome | As 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ão | Só 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ável | Defina 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 silenciosa | Falhas 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.