# Les risques du transfert d'agent SSH dans le développement assisté par IA

Les risques du transfert d'agent SSH sont faciles à sous-estimer parce que la clé privée reste sur la machine du développeur. C'est vrai, mais cela ne suffit pas. Un hôte distant qui reçoit un socket d'agent transféré peut demander à l'agent local de signer des défis d'authentification SSH. Pour chaque système qui accepte cette identité, l'hôte distant peut être capable d'agir en votre nom aussi longtemps que cet accès reste disponible.

Le développement assisté par IA rend cette erreur plus facile à commettre et plus difficile à remarquer. Un agent de programmation peut ouvrir des shells distants, exécuter des commandes de déploiement, inspecter des dépôts et suivre une configuration SSH que vous avez écrite il y a des années pour une session interactive. Si l'agent atteint une machine qui possède votre socket transféré, la frontière a déjà bougé. Désactivez le transfert par défaut et donnez aux tâches distantes leurs propres identités limitées lorsqu'elles ont réellement besoin d'un saut supplémentaire.

## Un agent transféré peut s'authentifier au-delà du premier hôte

Le transfert d'agent SSH expose un service de signature, pas une copie de clé privée. Votre `ssh-agent` local conserve une ou plusieurs identités privées. Lorsque vous vous connectez avec le transfert activé, SSH crée un socket sur la machine distante et relaie les demandes de signature via la connexion chiffrée jusqu'à l'agent local.

Cette distinction entraîne souvent de mauvaises décisions en matière de risque. On entend « la clé ne quitte jamais mon ordinateur portable » et on en déduit que la machine distante n'a rien d'intéressant à attaquer. Les octets privés peuvent rester en local, mais l'authentification SSH n'exige pas que la machine distante les possède. Elle a besoin d'une signature valide sur une demande d'authentification. Le socket transféré lui permet d'en obtenir une.

Le manuel OpenSSH `ssh_config(5)` décrit clairement `ForwardAgent` et avertit que les utilisateurs capables de contourner les permissions de fichiers sur l'hôte distant peuvent accéder à l'agent transféré. En pratique, cela signifie que le compte distant lui-même, un processus exécuté sous ce compte, un administrateur ou un attaquant qui prend suffisamment le contrôle peut utiliser le socket. Un accès root sur l'hôte distant met fin à toute discussion sur les permissions du socket.

La RFC 4252 décrit l'authentification SSH par clé publique comme une signature sur des données propres à la connexion. Le serveur valide cette signature avec une clé publique autorisée. Un hôte distant compromis ne peut pas transformer l'agent en oracle de signature générique pour des documents arbitraires, mais il peut demander les signatures SSH nécessaires pour tenter des connexions SSH ailleurs. C'est exactement la capacité recherchée par un attaquant si votre identité fonctionne sur le contrôle de code source, des serveurs de production ou des systèmes d'administration internes.

L'échec courant ressemble à ceci :

1. Un développeur se connecte à `build.example.net` avec `ssh -A` parce que cette machine doit atteindre un dépôt privé.
2. L'agent local du développeur apparaît sur l'hôte de compilation via `SSH_AUTH_SOCK`.
3. Un script de compilation, une dépendance compromise ou un autre utilisateur disposant de droits suffisants demande à ce socket de signer pour `git.example.net` ou un hôte interne.
4. La destination accepte la clé publique du développeur et journalise la nouvelle connexion comme venant du développeur.

Le serveur SSH de destination voit une signature cryptographique légitime. Il ne peut pas déterminer si une personne a lancé la connexion depuis son ordinateur portable ou si un processus de la première machine distante l'a demandée via le transfert. Les journaux du serveur indiquent la clé acceptée et l'adresse source, mais ils ne rétablissent pas cette distinction perdue.

## L'héritage de configuration crée une exposition accidentelle

La plupart des transferts dangereux commencent dans la configuration SSH, pas par une décision consciente prise pendant une session risquée. Une seule ancienne section comme `Host *` avec `ForwardAgent yes` peut s'appliquer à chaque hôte contacté par un développeur, un outil de terminal ou un agent de programmation. Elle s'applique aussi lorsque l'outil appelle `ssh` depuis un script, sans que personne ne voie d'avertissement.

La configuration OpenSSH présente un autre piège : pour chaque paramètre, le client utilise généralement la première valeur obtenue. Une règle générale placée avant une règle plus précise peut annuler l'exception que vous pensiez avoir écrite. Ne faites pas confiance à un rapide examen visuel de `~/.ssh/config` : demandez à SSH quelle configuration il utilisera.

Exécutez cette commande sur la machine locale avant de vous connecter :

```sh
ssh -G deploy.internal.example | grep '^forwardagent '
```

Un résultat sûr est :

```text
forwardagent no
```

`ssh -G` affiche la configuration effective du client après le traitement des fichiers et des options de ligne de commande. Il n'ouvre pas de connexion. C'est donc utile dans les contrôles de configuration et dans un script de test qui lit une liste d'hôtes sensibles.

Définissez une règle explicite de refus par défaut vers la fin du fichier de configuration concerné. Placez les exceptions précises avant elle, car OpenSSH utilise la première valeur correspondante :

```sshconfig
Host docs-bastion.example
    ForwardAgent yes

Host *
    ForwardAgent no
```

Cet exemple accorde toujours une exception dangereuse, qui doit donc avoir une raison et un responsable. L'objectif est de rendre cette exception visible et limitée à un seul hôte, au lieu de l'appliquer silencieusement à chaque nouvel environnement.

Pour une connexion ponctuelle, imposez le réglage sûr même si un fichier de configuration indique le contraire :

```sh
ssh -o ForwardAgent=no developer@host.example
```

La forme plus courte `ssh -a developer@host.example` fait la même chose. Utilisez l'une ou l'autre pour vous connecter à une machine que vous n'avez pas contrôlée, à un hôte de support temporaire, à un environnement de formation ou à un système géré par un fournisseur.

Ne confondez pas `IdentitiesOnly yes` avec un contrôle du transfert. Cette option détermine les identités que le client SSH propose lorsqu'il s'authentifie auprès d'un serveur. Elle n'empêche pas SSH de créer un socket d'agent sur le système distant. De même, supprimer `SSH_AUTH_SOCK` d'un shell ne constitue pas une politique complète. Un processus enfant peut hériter d'un autre environnement et une configuration SSH peut réactiver le transfert à la connexion suivante.

## Les flux de travail IA multiplient les chemins à contrôler

Un agent de programmation IA n'a pas besoin d'intentions malveillantes pour rendre le transfert dangereux. Il lui suffit d'avoir la permission d'exécuter une commande qui rencontre vos habitudes SSH existantes. Les agents suivent les instructions des dépôts, lancent des scripts de compilation, utilisent des hôtes de développement distants et réessaient des commandes avec de petites variations. Ce sont des comportements normaux. Ils deviennent risqués lorsque l'environnement leur donne une identité de développeur réutilisable.

Le chemin préoccupant est souvent indirect. Votre agent local démarre une session SSH vers une machine de développement. Cette session transfère votre agent à cause d'une règle de configuration globale. L'agent de programmation exécute un script du dépôt sur la machine de développement. Le script récupère une dépendance ou ouvre une seconde connexion SSH. Cette seconde connexion peut utiliser le socket placé là par la première session.

C'est plus risqué qu'un humain qui saisit une seule commande `git fetch`, car un agent peut effectuer de nombreux appels d'outils sans s'arrêter pour se demander pourquoi un shell distant doit accéder à une destination sans rapport. Il peut aussi rencontrer dans un dépôt des instructions lui demandant d'utiliser un alias d'hôte particulier. Cet alias peut dissimuler une `ProxyCommand`, une règle `Match` ou une règle de transfert héritée à la personne qui supervise l'opération.

Traitez ces éléments comme des permissions distinctes :

- Permission accordée à un agent d'ouvrir un shell distant.
- Permission accordée à ce shell distant d'atteindre une autre destination SSH.
- Permission accordée à la seconde destination d'accepter l'identité du développeur.
- Permission accordée à l'agent de déclencher la seconde connexion.

Les équipes regroupent régulièrement les quatre sous la formule « l'agent a besoin de SSH ». Cette phrase ne décrit pas la frontière. Un shell distant et une capacité de signature réutilisable ont des conséquences différentes et nécessitent des décisions séparées.

Vérifiez comment le processus de l'agent démarre. S'il hérite de `SSH_AUTH_SOCK` depuis un terminal interactif, il peut déjà accéder aux identités de l'agent local pour une authentification SSH directe. S'il se connecte ensuite à un hôte avec le transfert activé, cet hôte obtient un second chemin vers ces identités. Un lanceur qui efface la variable d'environnement peut réduire les utilisations locales accidentelles, mais il ne remplace ni une configuration SSH sûre ni une identité distincte pour l'automatisation.

Inspectez aussi les instructions de l'agent et les lanceurs d'automatisation à la recherche de `ssh -A`, `scp -A` ou d'un alias qui les développe. `scp` et `sftp` s'appuient sur les paramètres du transport SSH, donc une habitude de transfert peut se propager bien au-delà de la commande qui l'a créée. Placez la décision de transfert là où elle doit être prise : dans la définition de la connexion, avec une valeur explicite par défaut à non.

## Un bastion n'a pas besoin de votre agent

De nombreux développeurs activent le transfert parce qu'ils doivent traverser un bastion avant d'atteindre un système interne. C'était une solution courante lorsque le seul modèle pratique consistait à « se connecter au bastion, puis lancer un autre SSH ». Elle reste populaire parce qu'elle fonctionne rapidement lors de la configuration. Elle transforme aussi le bastion en machine capable de réutiliser votre identité.

Utilisez `ProxyJump` lorsque le premier hôte doit seulement transporter le trafic. Le client local peut s'authentifier auprès de l'hôte final tout en passant par le bastion, sans transférer de socket d'agent vers ce dernier.

```sshconfig
Host engineering-bastion
    HostName bastion.example
    User developer
    ForwardAgent no

Host release-host
    HostName release.internal.example
    User deploy
    ProxyJump engineering-bastion
    ForwardAgent no
```

Avec cette configuration, le client SSH local établit la connexion SSH finale à travers le bastion. Le bastion transporte le trafic chiffré. Il ne reçoit pas de `SSH_AUTH_SOCK` distant qu'il pourrait utiliser pour demander des signatures à votre agent.

Testez le chemin au lieu de supposer que la configuration fait ce qu'elle doit faire :

```sh
ssh -vvv release-host
```

Dans la sortie de débogage, recherchez la connexion via le proxy et vérifiez qu'aucun transfert d'agent n'est signalé. Après vous être connecté à l'hôte final, inspectez l'environnement distant :

```sh
printf 'SSH_AUTH_SOCK=%s\n' "${SSH_AUTH_SOCK:-}"
ssh-add -l
```

Une variable de socket vide est le résultat attendu lorsque vous avez volontairement désactivé le transfert. Si `ssh-add -l` indique qu'aucune connexion à un agent d'authentification n'est disponible, cela concorde aussi avec l'absence d'agent transféré. Lors d'un dépannage, n'effectuez pas ces vérifications uniquement sur l'hôte final. Faites-les sur chaque étape interactive où quelqu'un aurait pu activer le transfert.

Il existe des cas légitimes où le bastion doit lui-même s'authentifier auprès d'un autre hôte, par exemple pour une mise en production contrôlée. Dans ce cas, le bastion a besoin de sa propre identité de déploiement ou d'une identité de tâche à courte durée de vie. Cela ne signifie pas qu'il doit emprunter toutes les identités actuellement chargées dans l'agent de l'ordinateur portable d'un développeur.

## L'automatisation distante a besoin de sa propre identité

Une machine de compilation distante, un hôte de déploiement ou un outil d'IA doit s'authentifier en tant que charge de travail qui lui est attribuée, pas en tant que développeur qui a simplement démarré la session. C'est ce changement de conception qui élimine le besoin de transfert au lieu de le rendre seulement moins pratique.

Une identité de charge de travail ne doit donner accès qu'aux services nécessaires à cette charge. Pour un dépôt source, utilisez une identité de déploiement limitée au dépôt lorsque le service le permet. Pour une destination SSH, autorisez une clé publique dédiée sur un compte dédié et limitez les commandes ou les permissions de ce compte lorsque le serveur le permet. Dans une configuration SSH fondée sur des certificats, émettez des certificats à courte durée de vie avec des principals correspondant au rôle de la charge de travail.

Les certificats SSH peuvent réduire l'accès, mais ne sont pas magiques. Le principal d'un certificat ne contrôle les comptes qui l'acceptent que si le serveur vérifie correctement les principals. Une durée de validité courte limite la période pendant laquelle une signature peut authentifier une connexion, mais un processus compromis peut toujours l'utiliser pendant cette période. Examinez la configuration du serveur qui fait confiance à l'autorité de certification et les règles de compte qui utilisent ces principals.

Ne réutilisez pas la clé publique personnelle d'un développeur comme identité « temporaire » pour un exécuteur CI ou un agent distant. La trace d'audit resterait ambiguë. Lorsqu'un enregistrement d'authentification indique que la clé personnelle s'est connectée, il devient difficile de déterminer si l'action venait du développeur, d'une tâche de compilation ou d'un shell distant compromis qui utilisait le transfert.

Pour les actions HTTP et SSH contrôlées par un agent, une autre approche consiste à conserver les identifiants dans une passerelle d'actions locale et à ne renvoyer à l'agent que la commande ou le résultat de l'API. Sallyport utilise ce modèle sur macOS : son coffre conserve la clé SSH et son assistant `sp-ssh` exécute l'action SSH sans donner l'identifiant à l'agent.

La frontière utile est opérationnelle, pas théorique. L'agent demande une action nommée, la passerelle applique l'identifiant et l'agent reçoit le résultat. L'agent ne reçoit pas de socket d'agent qu'il pourrait transmettre à un processus distant sans rapport. Les approbations et les journaux deviennent ainsi significatifs, car ils correspondent à une action et non à une capacité ouverte de demander ultérieurement des signatures.

## Les demandes de confirmation réduisent l'exposition sans rétablir la confiance

Une tâche de maintenance courte peut parfois nécessiter réellement un transfert alors qu'aucune identité dédiée n'est prête. Dans ce cas, transférez le moins d'autorité de signature possible et rendez chaque utilisation visible. C'est un contrôle temporaire, pas une architecture permanente.

Démarrez un socket d'agent séparé au lieu de transférer l'agent qui contient votre collection quotidienne d'identités. Ajoutez uniquement l'identité nécessaire à la tâche de maintenance, avec une durée de vie courte et une confirmation obligatoire :

```sh
ssh-agent -a "$HOME/.ssh/maintenance-agent.sock" > "$HOME/.ssh/maintenance-agent.env"
. "$HOME/.ssh/maintenance-agent.env"
ssh-add -c -t 900 ~/.ssh/maintenance_ed25519
ssh -o ForwardAgent=yes operator@maintenance.example
```

`ssh-add -c` demande une confirmation avant que l'agent signe. `-t 900` supprime l'identité après 15 minutes. Consultez le manuel `ssh-add(1)` correspondant à votre version installée d'OpenSSH, car le comportement de confirmation dépend de l'agent local et de son interface utilisateur.

Utilisez un shell séparé pour cette tâche. À la fin du travail, supprimez l'identité et arrêtez cet agent :

```sh
ssh-add -D
ssh-agent -k
```

Cette séquence empêche les demandes ultérieures depuis ce socket temporaire. Elle ne révoque pas les signatures déjà émises, les connexions déjà authentifiées ni les données qu'un processus distant aurait déjà obtenues. Fermez la session distante et vérifiez les destinations auxquelles cette identité pouvait accéder.

OpenSSH prend également en charge les contraintes de destination via `ssh-add -h` dans les versions qui incluent cette fonction. Ces contraintes peuvent limiter les hôtes pour lesquels un agent signera, à partir des clés d'hôtes présentes dans `known_hosts`. Elles méritent d'être évaluées dans un environnement contrôlé, mais ajoutent des dépendances de configuration que les équipes ne maintiennent souvent pas. Un fichier `known_hosts` obsolète ou incomplet peut transformer une mesure de sécurité en panne au pire moment. Testez-la avec le chemin de saut et les alias d'hôtes exacts utilisés par votre automatisation.

Ne comptez pas sur la lassitude face aux demandes comme frontière de sécurité. Un hôte compromis peut demander des signatures de manière répétée et nommer des destinations qui ressemblent à l'infrastructure habituelle. Si les opérateurs approuvent rapidement les demandes pour terminer un incident, la confirmation protège moins qu'ils ne le pensent. Une identité limitée et une courte durée de vie réduisent l'ampleur d'une erreur d'approbation.

## Les journaux doivent distinguer les sessions des actions SSH

Les journaux d'authentification SSH indiquent qu'une clé s'est authentifiée auprès d'un serveur. Ils indiquent rarement pourquoi la signature a été demandée ou si le transfert d'agent a permis le chemin. Si vous autorisez des actions distantes automatisées, collectez suffisamment d'informations pour reconstituer qui a lancé l'agent, quel processus a reçu l'approbation, quelle destination il a contactée et quelle action il a demandée.

Conservez les enregistrements de session séparément des enregistrements d'action. Un enregistrement de session indique quel processus d'agent a reçu l'accès et quand cet accès a pris fin. Un enregistrement d'action indique quelle commande SSH ou quel appel d'API a été exécuté avec cet accès. Mélanger les deux dans un journal générique ralentit les investigations, car l'opérateur doit déduire la chaîne des causes et des effets à partir de fragments.

Sur un serveur SSH, examinez les journaux normaux d'authentification après tout événement suspect de transfert. L'emplacement exact dépend du système d'exploitation et de la configuration du service, mais les entrées du journal système et le journal d'authentification du démon SSH sont des endroits courants. Recherchez l'empreinte de la clé publique acceptée, le nom du compte, l'adresse source et l'heure. Comparez-les avec l'historique de session du premier hôte distant.

Ne tirez pas de conclusion certaine d'une adresse IP. Une connexion transférée peut provenir d'une machine de compilation, d'un bastion, d'une passerelle de traduction d'adresses réseau ou d'un réseau privé superposé. Le serveur peut déterminer l'origine de la connexion TCP, pas la personne ou le processus qui a lancé la demande de signature sur l'ordinateur du développeur.

Les enregistrements résistants à la falsification ne sont utiles que si le système les écrit en dehors du contrôle de l'agent. Un processus capable de modifier son propre historique d'actions peut effacer le second saut suspect avant que quiconque ne l'examine. Sallyport enregistre les sessions des agents et les appels individuels dans un journal d'audit chiffré et chaîné par hachage. `sp audit verify` peut vérifier cette chaîne hors ligne sans clé du coffre.

Quel que soit l'outil choisi, testez ses enregistrements avec une autorisation réellement refusée et une commande SSH réellement exécutée. Vérifiez qu'ils indiquent l'exécution de l'agent, la destination, la référence ou l'empreinte de l'identifiant, le résultat et l'heure. Des journaux qui disent seulement « outil terminé » ne peuvent pas répondre à une question d'incident sur la réutilisation d'une identité.

## Traitez une exposition comme un abus d'autorisation, pas comme un vol automatique de clé

Si vous découvrez que vous avez transféré un agent vers un hôte auquel vous ne faites pas confiance, réagissez comme si cet hôte avait pu utiliser votre identité pendant la durée de la session. N'attendez pas la preuve qu'il a extrait une clé privée. Le risque important est l'authentification non autorisée, et la clé privée peut ne jamais avoir quitté votre machine.

Commencez par empêcher toute utilisation future. Fermez toutes les sessions SSH vers cet hôte, retirez l'identité exposée de l'agent et désactivez la règle de transfert propre à cet hôte. Si l'identité était chargée dans un agent partagé, ne lancez pas automatiquement `ssh-add -D` au milieu d'une journée de travail en pensant avoir réglé le problème. Cela peut interrompre des sessions légitimes tout en laissant la clé publique concernée autorisée sur les serveurs.

Retirez ou révoquez ensuite l'autorisation sur les destinations que l'identité peut atteindre. Dans une configuration classique de clés autorisées, supprimez la clé publique des comptes auxquels elle ne doit plus donner accès et remplacez-la par une nouvelle là où c'est nécessaire. Pour les certificats SSH, révoquez le certificat selon les procédures de votre autorité de certification et de votre serveur, ou laissez expirer un certificat à courte durée si vous pouvez vérifier que la fenêtre d'exposition est acceptable. Pour l'accès aux dépôts, révoquez ou remplacez l'identifiant de déploiement ou d'utilisateur correspondant avec les contrôles habituels du service.

Analysez une fenêtre temporelle limitée : depuis le moment où le transfert est devenu disponible jusqu'à la fin de la session distante ou l'arrêt des demandes acceptées par l'agent local. Examinez les journaux d'authentification des destinations, l'historique du shell distant lorsqu'il est fiable, les enregistrements des tâches et les journaux d'actions. Conservez les journaux avant de nettoyer l'hôte distant si vous soupçonnez une compromission.

Enfin, corrigez le chemin qui l'a permis. Si un `ForwardAgent yes` global a causé l'événement, modifier seulement l'entrée de l'hôte compromis laisse le prochain hôte inconnu exposé. Si un flux de travail IA a hérité d'un socket personnel, donnez-lui une configuration de connexion explicite et une identité de charge de travail dédiée. La correction doit rendre le chemin dangereux impossible par défaut, pas seulement rappeler aux utilisateurs de penser à une option.

## Le réglage sûr par défaut doit résister au travail précipité

Le transfert persiste parce qu'il réduit les frictions sur le moment. Un développeur a besoin d'un saut supplémentaire, une compilation doit récupérer des éléments privés ou un agent doit terminer une tâche avant une échéance. Ces besoins sont réels. Ils ne justifient pas de placer une identité de développeur largement approuvée sur chaque machine présente sur le chemin.

Définissez `ForwardAgent no` globalement. Utilisez `ProxyJump` pour les bastions qui servent uniquement au transport. Attribuez à l'automatisation distante une identité qui indique ce qu'elle est autorisée à faire. Lorsqu'une exception temporaire est inévitable, isolez une identité, exigez une confirmation, définissez une expiration courte et supprimez-la à la fin de la tâche.

Exécutez `ssh -G` sur les alias d'hôtes utilisés par votre équipe cette semaine. Cette petite vérification détecte une erreur de configuration discrète avant qu'un processus distant n'obtienne un service de signature qu'il n'était pas censé recevoir.
