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 .ssh aninhado 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:

  1. a lista de negação de escrita em agent/file_safety.py;
  2. o filtro de exportação em hermes_cli/profiles.py;
  3. a lista de recusa do @file em agent/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:

  1. 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)/'
  1. 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.
  2. Exporte novamente, confirme que está limpo (o mesmo grep não retorna nada) e compartilhe essa versão.

Resumindo

  • O hermes profile export produz 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 @file e 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.