Sua exportação de perfil estava enviando suas credenciais de nuvem

Você passa uma manhã ajustando um perfil e depois o entrega a um colega de equipe: hermes profile export work -o work.tar.gz, com um recado dizendo “extraia isto e você tem a minha configuração”. Ele extrai e de fato tem a sua configuração — mais as credenciais do seu registro privado. O tar.gz contém skills/, config.yaml, state.db e um .docker/config.json no qual você nunca pensou.
Isso não era uma falha hipotética. O hermes profile export excluía alguns arquivos de credenciais, mas ignorava completamente os diretórios de credenciais no nível do sistema operacional. Dois PRs mesclados em 3 e 4 de outubro (#132201 e #132654) fecharam a brecha. Veja a seguir o que vazou, por quê, e o que você deve verificar hoje.
Primeira etapa: .ssh, .aws, .gnupg, .kube
Uma correção anterior (#130525) já mantinha os segredos do próprio Hermes — auth.json, .env, tokens OAuth — fora das exportações. Mas o PROFILE_CREDENTIAL_PATHS cobria apenas a subárvore home/ do subprocesso; os armazenamentos do sistema operacional simplesmente não estavam na lista.
O #132201 adicionou o primeiro lote: .ssh, .aws, .gnupg, .kube e o .envrc do direnv. Ele também corrigiu dois problemas vizinhos:
- Aninhamento. Um diretório de skill copiado de um home real pode carregar
skills/some-skill/.ssh/id_rsa. O antigo filtro de exportação casava apenas nomes de nível superior, então um.sshaninhado era enviado de qualquer forma. Após a correção, esses armazenamentos são descartados em qualquer profundidade. - Instalações de distribuição. Quando você instala uma distribuição de perfil, esses caminhos são marcados como pertencentes ao usuário (
profile_path_is_private), de modo que um pacote não pode mais plantá-los ou sobrescrevê-los na sua máquina.
Segunda etapa: .docker, .azure, .config/gh, .config/gcloud
Um dia depois, o #132654 percebeu que quatro diretórios irmãos ainda estavam descobertos — e eram exatamente os diretórios que o agent/file_safety.py já negava a escrita. O Hermes se recusava a escrever neles e depois os exportava alegremente:
| Diretório | O que costuma ficar lá |
|---|---|
.docker/config.json |
Autenticação de registro para imagens privadas |
.azure/accessTokens.json |
Tokens de acesso do Azure CLI |
.config/gh/hosts.yml |
Seu token do GitHub CLI |
.config/gcloud/ |
ADC do Google Cloud e credentials.db |
A referência de contexto @file piorava a situação: escrever @file:~/.config/gcloud/application_default_credentials.json em uma mensagem expandia o token no contexto. E o gateway passa texto remoto não confiável pela expansão @file, então era a mesma classe de vazamento.
Por que aconteceu: três listas copiadas à mão
A causa raiz não foi um diretório esquecido. A mesma lista havia sido copiada à mão em três lugares, e as cópias divergiram:
- a lista de negação de escrita em
agent/file_safety.py; - o filtro de exportação em
hermes_cli/profiles.py; - a lista de recusa do
@fileemagent/context_references.py.
A correção dá à lista uma única fonte. O agent/file_safety.py agora contém:
HOME_CREDENTIAL_DIRS = (".ssh", ".aws", ".gnupg", ".kube",
".docker", ".azure", ".config/gh", ".config/gcloud")
O build_write_denied_prefixes deriva dela, e o hermes_cli/profiles.py desempacota a mesma tupla em _OS_CREDENTIAL_STORES, de modo que as listas de exportação (PROFILE_CREDENTIAL_PATHS, _EXPORT_CREDENTIAL_FILES) não podem mais divergir da lista de negação de escrita. O agent/context_references.py também a importa, e é assim que o @file ganhou .config/gcloud.
O filtro de exportação também mudou a forma de casar — por componentes finais do caminho, em vez de nomes de nível superior:
def _export_credential_entries(directory: str, contents: list) -> set:
"""Entries of *directory* that are an _EXPORT_CREDENTIAL_FILES store: matched on trailing path
components, so ``.config/gh`` drops at any depth while the rest of ``.config`` ships."""
Então .config/gh e .config/gcloud são descartados onde quer que estejam, enquanto conteúdo não relacionado, como .config/other, .config/ghost ou .config/gh.bak, ainda é exportado normalmente.
Já exportou algum? Considere-o exposto
Esta correção só protege exportações feitas depois dela. Se você compartilhou um arquivo de perfil — com um colega, um serviço de hospedagem de arquivos ou um repositório git — a resposta certa é rotação, não apagar o arquivo:
- Primeiro veja o que realmente há dentro do arquivo (um tar.gz lista sem extrair):
tar -tzf work.tar.gz | grep -E '(^|/)\.(ssh|aws|gnupg|kube|docker|azure|envrc)/|\.config/(gh|gcloud)/'
- Qualquer coisa que casar está queimada: substitua as chaves SSH, faça rotação das credenciais AWS / Azure / gcloud, revogue e reemita o token do GitHub, troque a senha do registro privado.
- Exporte novamente, confirme que está limpo (o mesmo grep não retorna nada) e compartilhe essa versão.
Resumindo
- O
hermes profile exportproduz um tar.gz e agora exclui os próprios arquivos de credenciais do Hermes, além de oito diretórios de credenciais no nível do home; - a brecha existia porque três listas copiadas à mão divergiram; a correção torna uma lista autoritativa e casa por componentes finais do caminho;
- a expansão
@filee as instalações de distribuição compartilham essa lista, então as três foram apertadas em conjunto; - a correção é voltada para o futuro — faça rotação de tudo o que você já enviou.
Para um panorama mais amplo de isolamento, veja as três vias de saída para redação de segredos e renomear um perfil sem perder sua identidade; toda a superfície do subcomando está na referência de hermes profile.