Votre export de profil expédiait vos identifiants cloud

Vous passez une matinée à peaufiner un profil, puis vous le confiez à un collègue : hermes profile export work -o work.tar.gz, avec un mot disant « décompresse ça et tu as ma configuration ». Il le décompresse et il obtient bel et bien votre configuration — plus vos identifiants de registre privé. Le tar.gz contient skills/, config.yaml, state.db et un .docker/config.json auquel vous n’avez jamais pensé.
Ce n’était pas un trou hypothétique. hermes profile export excluait bien quelques fichiers d’identifiants, mais passait complètement à côté des répertoires d’identifiants du système d’exploitation. Deux PR, fusionnées les 3 et 4 octobre (#132201 et #132654), ont colmaté la brèche. Voici ce qui a fuité, pourquoi, et ce que vous devriez vérifier dès aujourd’hui.
Première étape : .ssh, .aws, .gnupg, .kube
Un correctif antérieur (#130525) tenait déjà les secrets propres à Hermes — auth.json, .env, les jetons OAuth — hors des exports. Mais PROFILE_CREDENTIAL_PATHS ne couvrait que le sous-arbre home/ du sous-processus ; les stockages du système d’exploitation n’étaient tout simplement pas dans la liste.
#132201 a ajouté le premier lot : .ssh, .aws, .gnupg, .kube et le .envrc de direnv. Il a aussi corrigé deux problèmes voisins :
- Imbrication. Un répertoire de compétences copié depuis un vrai home peut transporter
skills/some-skill/.ssh/id_rsa. L’ancien filtre d’export ne comparait que les noms de premier niveau, si bien qu’un.sshimbriqué partait quand même. Après le correctif, ces stockages sont écartés à n’importe quelle profondeur. - Installations de distribution. Quand vous installez une distribution de profil, ces chemins sont marqués comme appartenant à l’utilisateur (
profile_path_is_private), de sorte qu’un paquet ne peut plus les implanter ni les écraser sur votre machine.
Deuxième étape : .docker, .azure, .config/gh, .config/gcloud
Un jour plus tard, #132654 a remarqué que quatre répertoires voisins restaient non couverts — et c’étaient précisément les répertoires que agent/file_safety.py refusait déjà d’écrire. Hermes refusait de les écrire, puis les exportait allègrement :
| Répertoire | Ce qu’on y trouve généralement |
|---|---|
.docker/config.json |
Authentification de registre pour les images privées |
.azure/accessTokens.json |
Jetons d’accès d’Azure CLI |
.config/gh/hosts.yml |
Votre jeton GitHub CLI |
.config/gcloud/ |
Google Cloud ADC et credentials.db |
La référence de contexte @file aggravait les choses : écrire @file:~/.config/gcloud/application_default_credentials.json dans un message développait le jeton dans le contexte. Et comme la passerelle fait passer du texte distant non fiable par l’expansion @file, il s’agissait de la même catégorie de fuite.
Pourquoi c’est arrivé : trois listes recopiées à la main
La cause racine n’était pas un répertoire oublié. La même liste avait été recopiée à la main à trois endroits, et les copies avaient divergé :
- la liste de refus d’écriture dans
agent/file_safety.py; - le filtre d’export dans
hermes_cli/profiles.py; - la liste de refus de
@filedansagent/context_references.py.
Le correctif donne à la liste une source unique. agent/file_safety.py contient désormais :
HOME_CREDENTIAL_DIRS = (".ssh", ".aws", ".gnupg", ".kube",
".docker", ".azure", ".config/gh", ".config/gcloud")
build_write_denied_prefixes en dérive, et hermes_cli/profiles.py déplie le même tuple dans _OS_CREDENTIAL_STORES, si bien que les listes d’export (PROFILE_CREDENTIAL_PATHS, _EXPORT_CREDENTIAL_FILES) ne peuvent plus s’éloigner de la liste de refus d’écriture. agent/context_references.py l’importe aussi, et c’est ainsi que @file a gagné .config/gcloud.
Le filtre d’export a également changé sa façon de comparer — sur les composants de chemin de fin plutôt que sur les noms de premier niveau :
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."""
Ainsi .config/gh et .config/gcloud sont écartés où qu’ils se trouvent, tandis que du contenu sans rapport comme .config/other, .config/ghost ou .config/gh.bak continue d’être exporté normalement.
Vous en avez déjà exporté un ? Considérez-le comme exposé
Ce correctif ne protège que les exports effectués après son déploiement. Si vous avez partagé une archive de profil — avec un collègue, un hébergeur de fichiers ou un dépôt git — la bonne réponse est la rotation, pas la suppression du fichier :
- Regardez d’abord ce qu’il y a réellement dans l’archive (un tar.gz se liste sans être décompressé) :
tar -tzf work.tar.gz | grep -E '(^|/)\.(ssh|aws|gnupg|kube|docker|azure|envrc)/|\.config/(gh|gcloud)/'
- Tout ce qui correspond est brûlé : remplacez les clés SSH, faites tourner les identifiants AWS / Azure / gcloud, révoquez et réémettez le jeton GitHub, changez le mot de passe du registre privé.
- Exportez à nouveau, confirmez que c’est propre (le même grep ne renvoie rien), puis partagez cette version.
En bref
hermes profile exportproduit un tar.gz et exclut désormais les fichiers d’identifiants propres à Hermes ainsi que huit répertoires d’identifiants au niveau du home ;- la brèche existait parce que trois listes recopiées à la main avaient divergé ; le correctif fait d’une seule liste la source d’autorité et compare sur les composants de chemin de fin ;
- l’expansion
@fileet les installations de distribution partagent cette liste, si bien que les trois se sont resserrés ensemble ; - le correctif est tourné vers l’avenir — faites tourner tout ce que vous avez déjà partagé.
Pour le tableau plus large de l’isolation, voir les trois voies sortantes de rédaction des secrets et renommer un profil sans perdre son identité ; toute la surface des sous-commandes se trouve dans la référence hermes profile.