Caches de segredos na raiz do perfil: o buraco no sandbox que um rename de pasta podia abrir

Você configurou o Hermes com o Bitwarden para que ele busque a senha do banco sozinho, em vez de você colar tudo a cada vez. Sendo cuidadoso, colocou tudo dentro de um sandbox e marcou cache/bws_cache.json como caminho negado — afinal, esse arquivo é o cache de chaves descriptografadas, em texto puro.
Aí descobre que a negação cai com um empurrão: renomeie a pasta cache/ para cache2/ e o arquivo bloqueado viaja com ela. Leia no caminho novo e a proteção não reconhece mais nada.
Isso não é distração de uma pessoa. É um descompasso de fronteira clássico: o sandbox nega por caminho, mas a pasta-mãe desse caminho está dentro do escopo de escrita do agente. Em 2026-10-31 o Hermes moveu os quatro arquivos de cache de credenciais para o topo da home do perfil, eliminando o bypass em vez de remendar em volta dele.
O problema não era “escondido raso demais” — era “escondido numa pasta que pode ser renomeada”
Vale entender isso direito, porque a lição sobrevive ao bug.
No Windows, o sandbox MXC (o que o plugin OpenShell usa) bloqueia arquivos por deniedPaths. Esse mecanismo pressupõe que o arquivo negado esteja diretamente na home concedida. Se ele mora dentro de um subdiretório, e o agente pode criar, escrever e renomear esse subdiretório, a negação é decorativa:
- O sandbox diz que
home/cache/bws_cache.jsonnão pode ser lido. - O agente tem escrita em
home/cache/, então renomeia o diretório parahome/cache-old/. - O arquivo vai junto com o diretório: agora é
home/cache-old/bws_cache.json. - Esse caminho não está na lista de negação. Leia. Pronto.
O pull request diz isso sem rodeios: o bypass se sustenta no MXC 1.0.0 e no 1.0.1, conforme medido. Ou seja, a promessa anterior — “este caminho é negado” — não sobrevive à condição “o arquivo negado mora num subdiretório com escrita liberada”, e os quatro caches de credenciais cumpriam essa condição.
A conclusão é seca: não dá para consertar isso com uma lista de negação maior. Os arquivos precisam parar de morar num subdiretório com escrita liberada.
Para onde foram os quatro arquivos
| O que guarda | Caminho antigo (dentro de um subdiretório com escrita) | Caminho novo (topo da home do perfil) |
|---|---|---|
| Cache em texto puro do Bitwarden | cache/bws_cache.json |
bws_cache.json |
| Cache criptografado do Bitwarden | cache/bws_cache.enc.json |
bws_cache.enc.json |
| Cache do 1Password | cache/op_cache.json |
op_cache.json |
| Registro do sidecar Photon (porta, token, pid) | runtime/photon-sidecar.json |
photon-sidecar.json |
Os nomes de arquivo não mudaram; só o nível de diretório mudou. Você não consegue mover esses arquivos renomeando uma pasta, porque eles não são arquivos dentro de uma pasta — são filhos diretos da própria home, e um sandbox que os nega nega exatamente aquele caminho.
Uma lacuna antiga foi fechada na mesma passada: o cache do 1Password não era coberto por nenhuma proteção antes disso. Então a mudança não é só um movimento; os quatro nomes agora vivem numa única lista compartilhada (SECRET_CACHE_FILES em hermes_cli/home_data_layout.py), e todo lugar que precisa saber “estes arquivos são credenciais” lê essa mesma lista.
As cópias antigas: apagadas sem serem lidas
A pergunta natural depois de atualizar é o que acontece com cache/bws_cache.json.
Ele é apagado no primeiro uso, sem ser lido. O motivo é simples: esse caminho fica dentro de um diretório em que o agente pode escrever, então o conteúdo já não é confiável. Lê-lo seria convidar de volta dados possivelmente adulterados. A remoção é idempotente, e o próximo uso do Bitwarden ou do 1Password simplesmente busca de novo.
O custo é mínimo porque os quatro caches são regeneráveis:
- Caches do Bitwarden e do 1Password: no máximo uma busca extra depois da atualização.
- O registro do sidecar Photon: reconstruído na próxima vez que o gateway reiniciar.
Existe um caso de borda honesto. Um perfil que nunca mais toca na fonte Bitwarden ou 1Password mantém o arquivo antigo em cache/, sem ser lido. Ele continua coberto por todas as proteções, porque a lista mantém os caminhos legados também. Um plugin de sandbox pode apagar esses caminhos legados antes de subir um sandbox — o plugin OpenShell faz exatamente isso.
As proteções que foram junto
Mover é a parte fácil. A parte difícil é cada lugar que assumia o caminho antigo. Esta mudança atualizou:
- A lista de caminhos de credenciais (
PROFILE_CREDENTIAL_PATHS): as checagens de segurança por perfil conhecem os quatro nomes novos. - A camada de segurança de arquivos (
agent/file_safety): negação de leitura e de escrita, não só de leitura. - A lista de negação de entrega de mídia do gateway: para que um arquivo de credencial não possa sair por um anexo ou por um caminho de mídia.
- A Files API do dashboard: os quatro nomes são nomes ocultos, nunca listados no navegador de arquivos.
- Backups: os backups pulam os quatro nomes de topo. Esse importa — um cache de credenciais não deve pegar carona num backup para outra máquina.
- O
.gitignoreda instalação flat: os quatro nomes de topo são ignorados, então não podem ser commitados por acidente.
A escolha de design aqui merece ser copiada: não “adicione um caso especial”, mas “faça de uma lista a única fonte de verdade”. Adicione outro cofre de credenciais depois e todas as proteções o pegam automaticamente, o que é bem mais confiável do que repetir a lista em seis lugares.
O que você vai ver depois de atualizar
~/.hermes/bws_cache.json,~/.hermes/op_cache.jsone companhia aparecem no topo da home (ou em~/.hermes/profiles/<nome>/quando você roda vários perfis).- No primeiro uso do Bitwarden ou do 1Password, as cópias antigas em
cache/somem e o cache novo aparece no caminho novo. - Os backups não contêm mais esses arquivos.
- A lista de arquivos do dashboard não os mostra.
- Dentro de um sandbox, renomear um subdiretório não os move mais.
Se você tem um script que lê cache/bws_cache.json direto, ele precisa do caminho novo. É uma breaking change — mas o que ela quebra é justamente o local inseguro.
Já dá para usar hoje
A mudança entrou na main em 2026-10-31 (PR #146715, com a etiqueta type/security) e já está na v0.22.6 (2026-11-02): depois de atualizar pelo instalador ou hermes update, os caches vão para a raiz do perfil.
Enquanto não sair, se você roda código não confiável pelo sandbox MXC — especialmente no Windows com o plugin OpenShell —, não trate “adicionei o caminho do cache ao deniedPaths” como garantia de segurança. O truque do rename acima derruba isso. A resposta robusta é não manter credenciais valiosas e código não confiável de sandbox no mesmo perfil.
Para o contexto mais amplo de como o Hermes lida com credenciais, dois textos companheiros ajudam: como as chaves são criptografadas e onde fica o trade-off do keychain, e como segredos são redigidos no caminho até logs e mensagens. Aqueles cobrem como as chaves são guardadas e como vazam; este cobre onde mora o arquivo que as contém.