Déplacer les caches de secrets à la racine du profil : la faille de sandbox qu'un simple renommage de dossier peut ouvrir


Vous avez configuré Hermes avec Bitwarden pour qu’il aille chercher lui-même le mot de passe de la base au lieu de le coller à chaque fois. Par prudence, vous l’avez enfermé dans un sandbox et marqué cache/bws_cache.json comme chemin interdit — ce fichier est bien le cache de clés déchiffrées en clair, après tout.

Puis il s’avère que l’interdiction tombe au premier coup de pouce : renommez le dossier cache/ en cache2/, et le fichier bloqué part avec lui. Lisez-le à son nouveau chemin, et la garde ne le reconnaît plus.

Ce n’est pas la négligence d’une personne. C’est un décalage de périmètre de manuel : le sandbox interdit par chemin, alors que le dossier parent de ce chemin se trouve dans la zone d’écriture de l’agent. Le 2026-10-31, Hermes a déplacé les quatre fichiers de cache d’identifiants à la racine du home du profil, supprimant le contournement plutôt que de le rafistoler.

Le problème n’était pas « caché trop près de la surface », mais « caché dans un dossier renommable »

Ce point mérite d’être bien compris, parce que la leçon survit au bug.

Sous Windows, le sandbox MXC (celui qu’utilise le plugin OpenShell) bloque des fichiers via deniedPaths. Ce mécanisme suppose que le fichier interdit se trouve directement dans le home autorisé. S’il vit dans un sous-dossier que l’agent peut créer, écrire et renommer, l’interdiction est purement décorative :

  1. Le sandbox dit que home/cache/bws_cache.json ne doit pas être lu.
  2. L’agent a le droit d’écrire dans home/cache/, il renomme donc le dossier en home/cache-old/.
  3. Le fichier suit son dossier : il est maintenant home/cache-old/bws_cache.json.
  4. Ce chemin n’est pas sur la liste des interdits. Lisez-le. Terminé.

La pull request le dit sans détour : le contournement tient sur MXC 1.0.0 et 1.0.1, mesures à l’appui. La promesse initiale — « ce chemin est interdit » — ne survit donc pas à la condition « le fichier interdit vit dans un sous-dossier accessible en écriture », et les quatre caches d’identifiants remplissaient cette condition.

La conclusion est nette : on ne corrige pas ça avec une liste d’interdits plus longue. Les fichiers doivent cesser de vivre dans un sous-dossier accessible en écriture.

Où sont partis les quatre fichiers

Ce qu’il contient Ancien chemin (dans un sous-dossier accessible en écriture) Nouveau chemin (racine du home du profil)
Cache Bitwarden en clair cache/bws_cache.json bws_cache.json
Cache Bitwarden chiffré cache/bws_cache.enc.json bws_cache.enc.json
Cache 1Password cache/op_cache.json op_cache.json
Enregistrement du sidecar Photon (port, token, pid) runtime/photon-sidecar.json photon-sidecar.json

Les noms de fichiers ne changent pas ; seul le niveau de dossier change. Impossible de les déplacer en renommant un dossier, puisqu’ils ne sont pas des fichiers à l’intérieur d’un dossier mais des enfants directs du home, et qu’un sandbox qui les interdit interdit ce chemin exact.

Une lacune de longue date a été comblée au passage : le cache 1Password n’était couvert par aucune garde avant ce changement. L’opération n’est donc pas qu’un déplacement ; les quatre noms vivent désormais dans une liste partagée unique (SECRET_CACHE_FILES dans hermes_cli/home_data_layout.py), et tout endroit qui a besoin de savoir « ces fichiers sont des identifiants » lit cette même liste.

Les anciennes copies : supprimées sans être lues

La question naturelle après une mise à jour, c’est ce qui arrive à cache/bws_cache.json.

Il est supprimé à la première utilisation, sans être lu. La raison est simple : ce chemin se trouve dans un dossier où l’agent peut écrire, donc son contenu n’est plus digne de confiance. Le lire reviendrait à réintroduire des données potentiellement altérées. La suppression est idempotente, et la prochaine utilisation de Bitwarden ou 1Password récupère simplement les données à nouveau.

Le coût est minime, car les quatre caches sont régénérables :

  • Caches Bitwarden et 1Password : au pire une récupération supplémentaire après la mise à jour.
  • L’enregistrement du sidecar Photon : reconstruit au prochain redémarrage du gateway.

Un cas limite mérite d’être signalé. Un profil qui ne retouche jamais sa source Bitwarden ou 1Password garde l’ancien fichier dans cache/, non lu. Il reste couvert par toutes les gardes, car la liste conserve aussi les chemins historiques. Un plugin de sandbox peut supprimer ces chemins historiques avant de démarrer un sandbox — c’est exactement ce que fait le plugin OpenShell.

Les gardes qui ont suivi

Le déplacement, c’est la partie facile. La partie difficile, c’est chaque endroit qui supposait l’ancien emplacement. Ce changement a mis à jour :

  • La liste des chemins d’identifiants (PROFILE_CREDENTIAL_PATHS) : les contrôles de sécurité par profil connaissent les quatre nouveaux noms.
  • La couche de sécurité des fichiers (agent/file_safety) : refus en lecture et en écriture, pas seulement en lecture.
  • La liste de refus de livraison de médias du gateway : pour qu’un fichier d’identifiants ne puisse pas être distribué via une pièce jointe ou un chemin média.
  • L’API Files du dashboard : les quatre noms sont des noms masqués, jamais listés dans le navigateur de fichiers.
  • Les sauvegardes : elles ignorent les quatre noms de premier niveau. Ce point compte — un cache d’identifiants ne doit pas voyager dans une sauvegarde vers une autre machine.
  • Le .gitignore de l’installation à plat : les quatre noms de premier niveau sont ignorés, pour qu’ils ne soient jamais commités par accident.

Le choix de conception vaut d’être repris : non pas « ajouter un cas particulier » mais « faire d’une liste l’unique source de vérité ». Ajoutez plus tard un autre stockage d’identifiants et toutes les gardes le prennent en compte automatiquement, ce qui est nettement plus fiable que de l’écrire à six endroits.

Ce que vous verrez après la mise à jour

  • ~/.hermes/bws_cache.json, ~/.hermes/op_cache.json et leurs voisins apparaissent à la racine du home (ou sous ~/.hermes/profiles/<name>/ si vous utilisez plusieurs profils).
  • À la première utilisation de Bitwarden ou 1Password, les anciennes copies sous cache/ disparaissent et le nouveau cache apparaît à son nouvel emplacement.
  • Les sauvegardes ne contiennent plus ces fichiers.
  • La liste de fichiers du dashboard ne les affiche pas.
  • Dans un sandbox, renommer un sous-dossier ne les déplace plus.

Si vous avez un script qui lit cache/bws_cache.json directement, il lui faut le nouveau chemin. C’est un changement cassant — mais ce qu’il casse est précisément l’emplacement non sécurisé.

Peut-on l’utiliser dès maintenant

Le changement est arrivé sur main le 2026-10-31 (PR #146715, étiquetée type/security) et figure dans la v0.22.6 (2026-11-02) : après mise à jour via l’installeur ou hermes update, les caches vont à la racine du profil.

En attendant la publication, si vous exécutez du code non fiable via le sandbox MXC — surtout sous Windows avec le plugin OpenShell — ne considérez pas « j’ai ajouté le chemin du cache à deniedPaths » comme une garantie de sécurité. L’astuce du renommage ci-dessus la réduit à néant. La réponse robuste est de ne pas garder des identifiants sensibles et du code de sandbox non fiable dans le même profil.

Pour le contexte plus large sur la gestion des identifiants par Hermes, deux articles complémentaires sont utiles : comment les clés sont chiffrées et où se situe le compromis du trousseau, et comment les secrets sont masqués à l’entrée des logs et des messages. Ceux-là traitent du stockage des clés et de leurs fuites ; celui-ci traite de l’endroit où vit le fichier qui les contient.