Vous voulez qu'Hermes modifie votre config SSH ? Il demande désormais d'abord

Vous demandez à Hermes d’ajouter un nouveau serveur à votre config SSH — un alias d’hôte, un bastion ProxyJump, pour que ssh work se connecte en une seule ligne à partir de maintenant. write_file claque la porte : « Écriture refusée : ~/.ssh/config est un fichier système/d’identifiants protégé. » Vous basculez alors sur l’outil terminal et printf les mêmes lignes dans le même fichier — et là, une simple boîte de confirmation apparaît : vous cliquez sur approuver, terminé. Même fichier, deux outils, deux règlements contradictoires. Lequel a raison ? La PR #84663, fusionnée le 12 août, tranche : la configuration du client SSH n’est plus une zone « n’y pense même pas » — elle passe sous un approval gate qui vous demande d’abord.
La cause racine : un fichier, deux règles contradictoires
La contradiction venait de deux couches de sécurité indépendantes :
write_file/patchvérifient la liste des refus catégoriques dansagent/file_safety.py.~/.ssh/configse heurtait autrefois au refus sur le chemin exact comme au refus sur le préfixe~/.ssh/: il était donc rejeté d’office, sans aucune possibilité de négociation.terminalpasse par la logique d’approbation des commandes dangereuses danstools/approval.py. Écrire dans~/.sshétait seulement signalé comme « nécessite une approbation » — approuvez et l’écriture passe.
Ainsi, le même ~/.ssh/config était une impasse côté outils de fichiers et une confirmation en un clic côté terminal. Ce grand écart déroutait les utilisateurs — et l’agent lui-même rapportait d’abord « écriture échouée », puis « réussie » via l’autre chemin, laissant derrière lui une traînée de journaux contradictoires.
Le correctif : ~/.ssh/config rétrogradé de refus catégorique à approbation requise
Le raisonnement de la PR #84663 : la configuration client SSH n’est pas un matériel d’identification. Elle ne contient aucun octet de clé privée, et l’éditer (alias d’hôtes, ProxyJump, cibles VS Code Remote-SSH) est une tâche courante, initiée par l’utilisateur — un refus sec serait une erreur. Mais elle peut transporter des directives ProxyCommand / Match exec qui exécutent des commandes : une écriture libre et silencieuse serait tout aussi erronée. L’approbation est la bonne politique — en cohérence avec ce que l’outil terminal faisait déjà pour les écritures dans ~/.ssh.
Côté code, sur origin/main :
agent/file_safety.py:~/.ssh/configretiré du refus d’identifiants plat ; les nouveauxbuild_write_approval_paths()etis_write_approval_required()soustraient les chemins soumis à approbation au refus sur le préfixe~/.ssh/dans_classify_write_denial.- Les clés privées et fichiers d’authentification restent intouchables :
id_rsa,id_ed25519,authorized_keys, et tout le reste sous~/.ssh/— aucune exception. tools/file_tools.py:write_fileetpatchroutent désormais les écritures de config SSH via le_run_approval_gatepartagé, juste après la porte de protection contre les instructions.- Les appelants non interactifs échouent en fail-closed : le pont de fichiers ACP (
agent/copilot_acp_client.py) rejette d’office les chemins soumis à approbation ; le sélecteur de chemin de sortie TTS (tools/tts_tool.py) les refuse également.
Le modèle de sécurité par paliers des fichiers d’Hermes
Ce changement révèle aussi l’ensemble du modèle de sécurité des écritures, qui compte trois paliers à retenir :
Palier 1 : refus catégorique — n’y pense même pas. Les identifiants et les fichiers système critiques ne sont inscriptibles par aucun outil, quel que soit le mode, yolo compris : ~/.ssh/authorized_keys, id_rsa, id_ed25519, .env, .anthropic_oauth.json, .netrc, .pgpass, .npmrc, .pypirc, .git-credentials, /etc/sudoers, /etc/passwd, /etc/shadow, ainsi que les préfixes de répertoires comme ~/.ssh/, ~/.aws/, ~/.gnupg/, ~/.kube/, ~/.docker/, ~/.config/gh/. C’est le plancher qui ne bouge jamais.
Palier 2 : soumis à approbation — demandez-moi d’abord. Un seul membre pour l’instant : ~/.ssh/config. Inscriptible, mais uniquement via une invite d’approbation humaine, car il peut faire passer en contrebande des directives exécutant des commandes. La porte propose trois portées de persistance : once (cette fois-ci), session (mémorisé pour la session), always (mémorisé pour toujours).
Palier 3 : écriture libre — tout le reste. Les fichiers de travail ordinaires et le code des projets : l’agent peut écrire librement.
Comment se comporte réellement l’approval gate
En lisant la porte partagée (_run_approval_gate dans tools/approval.py), l’ordre de décision est le suivant :
--yolopasse en premier : le mode yolo (au niveau du processus,HERMES_YOLO_MODE, ou au niveau de la session) va droit au but — mais les refus catégoriques du palier 1 sont vérifiés avant la porte : yolo ne peut donc toujours pas y toucher ;- Cache de session en court-circuit : une écriture déjà approuvée (portée session/always) passe en silence ;
- Branche interactive / gateway / cron : les sessions interactives reçoivent une invite ; les sessions cron suivent
approvals.cron_mode(par défaut : refus) ; - Fail-closed sans canal humain : ni terminal interactif, ni session gateway (scripts d’arrière-plan, appels ACP par exemple) → BLOQUÉ, sans appel. Le refus précise même à l’agent de ne pas réessayer via terminal ou execute_code, sauf consentement explicite de l’utilisateur.
Pour conclure
Déplacer ~/.ssh/config du refus catégorique vers l’approbation requise, c’est avant tout rendre la décision à l’humain : les modifications courantes ne sont plus bloquées d’office, tandis que les écritures capables d’exécuter des commandes passent toujours par une porte humaine. Pour en savoir plus sur la machinerie d’approbation d’Hermes, consultez notre guide Smart Approvals à trois portes et la façon dont la fatigue d’approbation se résout ; les modes --yolo et les lignes rouges sont détaillés dans cet article ; les commandes de configuration figurent sur la page de référence hermes-config.