# Transfert de port SSH pour les agents autonomes : des tunnels plus sûrs

Le transfert de port SSH pour les agents autonomes demande une conception plus stricte que l'accès habituel des développeurs. Un tunnel peut transformer une base de données accessible uniquement depuis localhost, un panneau d'administration ou un service de préproduction en ressource qu'un agent atteint avec une simple commande SSH. Si l'agent possède des identifiants SSH trop larges, le tunnel n'est qu'un symptôme. Le vrai problème est que personne n'a limité la destination, la durée ou l'autorité associée à cette commande.

J'ai vu des équipes qualifier un tunnel de « temporaire » parce que quelqu'un avait saisi `ssh -L` dans un terminal. Le terminal est ensuite resté actif pendant plusieurs jours, son port est devenu une dépendance pour un autre processus et la personne qui l'avait ouvert était absente lorsque l'équipe de sécurité a demandé pourquoi un service proche de la production possédait un écouteur inexpliqué. Le travail autonome aggrave ce schéma, car l'agent peut réessayer, se reconnecter et utiliser un tunnel beaucoup plus régulièrement qu'un humain distrait.

L'approche sûre est simple : donner à l'agent un chemin de connexion étroitement limité, approuver une exposition temporaire précise, conserver assez de contexte pour la reconstituer plus tard et faire de la fermeture un événement contrôlable plutôt qu'une simple intention.

## Un tunnel modifie l'accessibilité réseau, pas seulement le comportement de SSH

Un transfert de port SSH crée un écouteur à une extrémité de la connexion SSH et transporte son trafic vers une destination accessible depuis l'autre extrémité. Cet écouteur est un chemin d'accès. Le traiter comme un simple « accès SSH » fait disparaître la partie qui crée le risque : qui peut se connecter, où le trafic aboutit et ce qui peut y circuler.

Imaginons un agent exécuté sur un hôte de compilation qui doit interroger `db-admin.internal.example` sur le port 5432. Un transfert local peut rendre ce service accessible sous `127.0.0.1:15432` sur l'hôte de compilation. La base reste privée vis-à-vis d'Internet, mais chaque processus de cet hôte capable d'atteindre l'écouteur peut maintenant tenter une connexion. Le serveur SSH devient aussi une route autorisée vers la base.

Cela peut être acceptable. Ce n'est pas équivalent à autoriser l'agent à « utiliser SSH pour la maintenance ». L'approbation doit décrire exactement cette route :

- la charge de travail et l'hôte à l'origine de la connexion
- l'adresse et le port d'écoute
- l'hôte et le port de destination
- la raison de la route
- l'expiration et la personne qui approuve

Un transfert distant inverse l'emplacement de l'écouteur. Si un agent se connecte à une bastion et demande à celle-ci d'écouter sur le port 18080, un processus qui atteint cet écouteur peut recevoir du trafic renvoyé vers l'hôte de l'agent. Les transferts distants surprennent souvent les équipes, car ils peuvent exposer un service situé derrière un pare-feu sans ouvrir de règle entrante.

OpenSSH documente séparément ces modes dans `ssh(1)` : `-L` crée des transferts locaux, `-R` des transferts distants et `-D` un proxy SOCKS. Cette distinction est utile pour l'exploitation. N'approuvez pas le « transfert de port » comme une permission unique. Chaque mode expose un écouteur différent et exige des restrictions différentes.

## Les transferts locaux, distants et dynamiques appellent des décisions différentes

Un transfert local convient généralement à une tâche d'agent lorsque celui-ci doit atteindre un service précis par l'intermédiaire d'un hôte relais contrôlé. L'écouteur se trouve du côté de l'agent et le serveur SSH se connecte à la destination interne. La commande est connue :

```sh
ssh -N \
  -L 127.0.0.1:15432:db-admin.internal.example:5432 \
  agent-db-tunnel@bastion.internal.example
```

`-N` demande à SSH de ne pas exécuter de commande distante. Cela ne rend pas la connexion inoffensive. L'adresse `127.0.0.1` limite l'écouteur à l'hôte local, tandis que `db-admin.internal.example:5432` indique la destination demandée. Ce sont deux propriétés distinctes et elles doivent toutes deux figurer dans l'enregistrement d'approbation.

Un transfert distant ne convient que si quelqu'un a explicitement choisi de créer un écouteur du côté distant. La commande suivante demande à la bastion d'écouter sur sa propre interface de bouclage et de transporter le trafic vers le port 8080 de la machine de l'agent :

```sh
ssh -N \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  agent-return-path@bastion.internal.example
```

Ne considérez pas automatiquement comme sûr un écouteur distant lié à l'interface de bouclage. Un processus local sur la bastion peut avoir bien plus de droits que la machine source de l'agent, et un autre utilisateur d'une bastion partagée peut l'atteindre. Examinez les utilisateurs et les processus locaux de l'hôte distant avant de l'autoriser.

Le transfert dynamique devrait être refusé par défaut aux agents autonomes :

```sh
ssh -N -D 127.0.0.1:1080 agent@bastion.internal.example
```

Cette commande démarre un écouteur SOCKS. Le client choisit ensuite les destinations au moyen de requêtes SOCKS, au lieu d'en nommer une dans la commande SSH. Les équipes apprécient cette solution parce qu'elle permet rapidement de faire fonctionner une interface web interne dans un navigateur ou un banc de test. Mais cette commodité supprime la limite de destination nécessaire au travail sans surveillance. Une personne chargée de la revue ne peut pas approuver une route unique et les restrictions SSH habituelles ne permettent pas de définir une liste utile pour un trafic SOCKS arbitraire.

Ne résolvez pas ce problème avec un formulaire d'approbation plus long. Refusez le transfert dynamique au compte de l'agent. Si la tâche exige plusieurs destinations, définissez chaque transfert séparément ou placez devant les services un proxy conçu pour cet usage, avec sa propre authentification et ses propres journaux.

## L'adresse de liaison détermine qui peut utiliser l'écouteur

L'adresse de liaison fait partie de la décision de sécurité, car elle détermine quelles machines peuvent se connecter à l'écouteur du tunnel. Un transfert lié à l'interface de bouclage et un transfert lié à toutes les interfaces peuvent utiliser la même cible tout en créant des expositions complètement différentes.

Pour un transfert local, utilisez une adresse de bouclage explicite :

```sh
-L 127.0.0.1:15432:db-admin.internal.example:5432
```

Évitez de dépendre de la valeur par défaut. OpenSSH lie normalement les transferts locaux à l'interface de bouclage lorsqu'aucune adresse n'est indiquée, mais une adresse explicite rend la revue, les journaux et l'analyse d'incident moins ambigus. Elle évite aussi qu'une modification ultérieure de la configuration client change discrètement la portée de l'écouteur.

Voici la version dangereuse :

```sh
-L 0.0.0.0:15432:db-admin.internal.example:5432
```

Elle expose le port 15432 sur toutes les interfaces IPv4 de l'hôte client. Tout hôte capable d'atteindre le client peut tenter d'utiliser le tunnel. Un exécuteur de compilation situé dans un sous-réseau partagé peut ainsi devenir un pont vers une base interne, même si cette base n'accepte des connexions que depuis la bastion.

IPv6 demande la même attention. `::1` est l'adresse de bouclage, tandis que `::` écoute sur toutes les interfaces IPv6. Vérifiez les deux familles d'adresses. J'ai vu des équipes confirmer que `127.0.0.1` était sûr, puis découvrir que l'automatisation avait aussi ouvert un écouteur IPv6 par une autre voie de configuration.

Pour les transferts distants, c'est le serveur SSH qui contrôle les adresses de liaison acceptées. Dans `sshd_config`, `GatewayPorts` influence le comportement de liaison des transferts distants. OpenSSH précise que ces transferts sont sinon liés à l'interface de bouclage par défaut, sous réserve de l'adresse demandée et de la politique du serveur. Conservez `GatewayPorts no`, sauf raison examinée d'autoriser des écouteurs distants plus larges. `GatewayPorts clientspecified` donne trop de contrôle aux clients pour un compte d'agent.

Après l'ouverture d'un transfert, inspectez l'écouteur sur la machine qui le possède. Sous macOS ou Linux, ce contrôle local est utile :

```sh
lsof -nP -iTCP:15432 -sTCP:LISTEN
```

La sortie doit afficher un processus `ssh` avec une adresse telle que `127.0.0.1:15432` ou `::1:15432`. Si elle affiche `*:15432`, arrêtez la tâche et examinez la commande ainsi que la configuration client. Cette commande prouve uniquement l'adresse de l'écouteur local. Elle ne prouve pas que l'extrémité distante est limitée à la destination prévue.

## Un compte SSH dédié doit exprimer la route autorisée

Un compte SSH dédié, accompagné de restrictions côté serveur, constitue la limite minimale raisonnable pour les tunnels d'agents. Un fichier de configuration client ne peut rien imposer à un agent capable de modifier ses propres arguments. Placez les limites là où le serveur SSH accepte la connexion.

Pour un compte limité aux transferts locaux, commencez par un bloc de correspondance comme celui-ci dans `sshd_config` :

```text
Match User agent-db-tunnel
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    PubkeyAuthentication yes
    PermitTTY no
    X11Forwarding no
    AllowAgentForwarding no
    AllowStreamLocalForwarding no
    AllowTcpForwarding local
    PermitOpen db-admin.internal.example:5432
    GatewayPorts no
    PermitUserEnvironment no
```

`AllowTcpForwarding local` autorise les transferts locaux et refuse les transferts distants. `PermitOpen` indique la destination que ce compte peut demander. Cette restriction est importante, car une commande de transfert local peut sinon viser n'importe quel hôte et port accessibles depuis la bastion. Sans `PermitOpen`, un agent qui a légitimement besoin de la base peut aussi demander un transfert vers une API d'administration, un cache, un service de métadonnées ou un autre serveur SSH.

Utilisez les noms d'hôte avec prudence. Le serveur SSH résout la destination, donc le nom de `PermitOpen` doit être résolu de son côté. Faites en sorte que ce nom soit stable et géré par votre infrastructure. Si un nom DNS peut pointer vers des adresses arbitraires, la configuration semble restrictive alors que sa destination réelle change. Une adresse IP peut être plus claire pour un équipement fixe, mais un nom d'hôte convient lorsque le contrôle du DNS interne et la propriété du service sont rigoureux.

Le manuel `sshd_config(5)` d'OpenSSH décrit `PermitOpen` comme une restriction de destination sous la forme `host:port`. Cette directive ne transforme pas le compte en moteur de politiques. Elle remplit précisément une fonction : refuser toute demande de transfert dont la destination ne figure pas dans la liste. Gardez la même précision dans la conception du compte.

Ne donnez pas de shell à ce compte sous prétexte que « l'agent ne l'utilisera pas ». Supprimez réellement cette possibilité. Un compte dédié doit correspondre à un seul rôle de connexion. Vous pouvez configurer sa clé publique autorisée avec une commande forcée qui quitte immédiatement si le compte a besoin d'une protection supplémentaire contre l'utilisation interactive, mais testez cette décision avec les transferts de votre environnement. Certains modèles de commande forcée et certains wrappers perturbent les sessions SSH attendues. Une restriction défectueuse conduit souvent quelqu'un à supprimer tout le bloc dans l'urgence.

Évitez également le transfert d'agent. `AllowAgentForwarding no` empêche le serveur connecté de demander à l'agent SSH du client de signer des demandes d'authentification. Un compte de tunnel SSH doit transporter du trafic, pas devenir un point de passage vers d'autres hôtes.

Pour une cible qui expose deux services légitimes, indiquez deux entrées `PermitOpen` explicites. N'utilisez pas de caractère générique et ne remplacez pas le compte par une bastion générale simplement pour éviter de créer un compte supplémentaire. Un compte de plus coûte moins cher que l'explication d'un agent de développement capable d'atteindre tout un sous-réseau interne.

## Temporaire signifie expiration imposée et fermeture propre

Un tunnel n'est temporaire que si quelque chose, en plus des bonnes intentions, le ferme. SSH maintient une connexion jusqu'à ce que le client la ferme, que le réseau tombe ou qu'un délai d'attente l'interrompe. Un agent autonome peut laisser un processus actif après la réussite, l'échec ou l'expiration d'un test.

Imposez à chaque tâche une durée maximale de session, hors du contrôle de l'agent. Un exécuteur peut utiliser `timeout` lorsque cette commande est disponible :

```sh
timeout 20m ssh -N \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -L 127.0.0.1:15432:db-admin.internal.example:5432 \
  agent-db-tunnel@bastion.internal.example
```

`ExitOnForwardFailure=yes` empêche la tâche de continuer lorsque SSH ne peut pas créer l'écouteur demandé. Sans cette option, l'agent peut continuer et signaler une erreur applicative confuse au lieu d'un échec de configuration du tunnel. Les paramètres de maintien de connexion font quitter SSH après plusieurs réponses manquantes, ce qui aide lorsqu'une machine cliente perd silencieusement sa connectivité réseau. Ils ne remplacent pas la limite de 20 minutes.

macOS ne fournit pas par défaut la commande GNU `timeout`. Utilisez le mécanisme de délai de votre exécuteur, un processus supervisé avec une échéance ou un petit wrapper qui envoie `TERM` puis vérifie la fermeture du processus. Ne remplacez pas une échéance absente par une tâche cron qui tue les « anciens SSH ». Elle interromprait des travaux sans rapport et vous empêcherait de prouver quelle connexion a été arrêtée.

Demandez aussi à la tâche de fermer son tunnel dans son propre nettoyage. Pour un processus qu'elle a démarré, conservez son PID, envoyez `TERM` à la fin de la tâche, attendez brièvement puis vérifiez que l'écouteur a disparu avec `lsof` ou `ss`. L'échéance couvre les plantages ; le nettoyage gère la fin normale.

Le multiplexage des connexions exige une attention particulière. Avec `ControlMaster` et un `ControlPath` partagé, plusieurs appels SSH peuvent réutiliser une même connexion maître. Cela accélère une session dans un terminal humain. Pour un agent, cela brouille la propriété et l'expiration : une tâche peut hériter d'une connexion approuvée pour une autre et la fermeture d'un client peut ne pas fermer le maître. Désactivez le multiplexage pour le compte du tunnel ou générez un chemin de contrôle unique pour chaque tâche approuvée.

Un processus de maintenance qui a besoin d'un accès récurrent doit demander des sessions récurrentes mais limitées séparément. Ne laissez pas un tunnel ouvert parce qu'il serait pénible de le recréer. Les quelques secondes économisées au démarrage deviennent une source d'ambiguïté chaque fois qu'une personne enquête sur une connexion inattendue.

## Une approbation doit décrire une connexion reconstituable

Un enregistrement d'approbation doit permettre de répondre à trois questions : qui a autorisé la route, ce qui l'a ouverte et si elle est restée dans les limites annoncées. Un commentaire générique tel que « accès agent approuvé » n'est pas une preuve. Il n'identifie ni l'écouteur, ni la destination, ni la durée.

Enregistrez l'approbation avant le démarrage du tunnel. Capturez l'identité de la personne qui approuve, l'horodatage, l'identifiant de la tâche ou de l'exécution, l'identité du processus agent, la machine source, le compte SSH dédié et l'empreinte de la clé publique. Enregistrez ensuite le transfert demandé en entier : direction, adresse de liaison, port d'écoute, hôte de destination, port de destination, objectif et expiration.

L'enregistrement du résultat doit comporter ses propres champs. Indiquez si SSH a créé le transfert, l'identifiant du processus client, l'heure de début, le statut de sortie final, l'heure de fermeture et la raison de celle-ci. Une expiration, un nettoyage normal, une révocation par un opérateur et une défaillance réseau n'ont pas le même sens lors d'une revue.

Utilisez des enregistrements structurés plutôt qu'un texte qui oblige quelqu'un à déduire les champs. Cette structure suffit à de nombreux systèmes internes :

```json
{
  "run_id": "run-7c31",
  "approved_by": "oncall-engineer",
  "approved_at": "2025-04-12T14:03:00Z",
  "ssh_account": "agent-db-tunnel",
  "direction": "local",
  "listen": "127.0.0.1:15432",
  "destination": "db-admin.internal.example:5432",
  "expires_at": "2025-04-12T14:23:00Z",
  "purpose": "run migration compatibility check"
}
```

Les identifiants de cet exemple sont des valeurs fictives, pas un schéma à recopier exactement. La rigueur compte davantage que le nom des champs : un même enregistrement doit relier une personne, un processus, une route et une période.

Ne laissez pas le même agent générer l'approbation et la considérer comme une autorisation humaine. L'agent peut expliquer pourquoi il souhaite une connexion, mais une personne ou un processus gouverné séparément doit décider de l'autoriser. Si l'action est assez routinière pour être approuvée automatiquement, encodez cette décision dans une définition de tâche étroite avec une expiration et conservez la version de la règle qui l'a autorisée.

La fatigue liée aux approbations est un défaut de conception. Demander à quelqu'un de valider chaque paquet crée des clics dépourvus de sens. Une approbation par route temporaire et précise donne à la personne un objet concret à évaluer. Gardez la demande courte, mais incluez l'adresse de liaison et la destination exacte. Ces deux chaînes détectent un nombre étonnant de mauvaises demandes.

## Gardez les identifiants hors de l'agent et l'autorité hors de son prompt

Un agent autonome doit demander une connexion, pas recevoir une clé privée capable de créer ensuite des connexions arbitraires. Dès qu'un agent possède une clé SSH générale dans son environnement de processus ou son espace de travail, il peut la copier, appeler un autre client SSH, l'utiliser après la fin de la tâche approuvée ou la transmettre à un outil que vous n'aviez pas prévu de faire fonctionner avec cette confiance. Un prompt qui dit « utilise-la uniquement pour la base » ne limite aucune de ces actions.

Une meilleure conception place les identifiants SSH dans une frontière d'exécution distincte. L'agent envoie une action demandée avec les champs de route. La frontière vérifie que le coffre est disponible, que la session est autorisée et que l'identifiant demandé dispose de l'approbation nécessaire. Elle effectue l'action SSH et renvoie le résultat sans transmettre la clé privée à l'agent.

Sallyport utilise ce modèle sous macOS : son utilitaire `sp-ssh` intégré effectue les actions SSH tandis que les clés restent dans le coffre chiffré de l'application et ne parviennent pas à l'agent MCP.

Cette distinction compte lors de l'analyse d'un échec. La conservation des identifiants répond à la question « l'agent pouvait-il réutiliser le secret ailleurs ? ». La restriction de route répond à « ce compte pouvait-il atteindre une destination non approuvée ? ». L'approbation répond à « qui a autorisé cette utilisation précise ? ». Les équipes réunissent souvent ces trois contrôles en un seul, puis découvrent que leur audit indique qu'une clé a été utilisée sans préciser la route qu'elle a rendue possible.

Rendez l'interface de demande plus étroite que les arguments SSH bruts. Acceptez des champs tels que `destination_host`, `destination_port`, `listen_address`, `listen_port` et `max_duration`. Refusez les options fournies par le client qui modifient le proxy, la direction du transfert, l'exécution de commandes distantes, les fichiers d'identité ou les sockets de contrôle. Si vous acceptez une chaîne de commande `ssh` arbitraire, vous déléguez l'interprétation de la politique à l'analyse de chaînes. Cette approche échoue dès que quelqu'un ajoute `-R`, `-D`, `ProxyCommand` ou un second transfert.

N'offrez pas de shell général comme solution de secours à un workflow de tunnel. L'agent n'a pas besoin d'exécuter `ssh` en mode interactif lorsque l'action consiste à créer un canal limité. Les actions étroites sont plus faciles à approuver et à révoquer.

## Les preuves d'audit doivent résister à une revue hostile

Les journaux de tunnel ne sont utiles que si un agent compromis ou un processus local ne peut pas les réécrire après coup. Des journaux en texte brut à côté de l'espace de travail de l'agent sont pratiques, mais ils prouvent peu de choses lorsque le même acteur peut modifier à la fois la commande du tunnel et son historique.

Enregistrez séparément l'événement d'approbation, la demande, le résultat d'exécution et le résultat de fermeture, avec un identifiant d'exécution commun. Conservez les champs de la demande originale au lieu d'enregistrer uniquement une ligne de commande générée. Une ligne de commande peut dissimuler des valeurs dans un fichier de configuration ou omettre des valeurs par défaut qui ont influencé l'écouteur.

Recueillez des éléments de corroboration à plusieurs endroits. Les journaux SSH de la bastion peuvent montrer les authentifications acceptées et les échecs de transfert. L'exécuteur peut montrer la création et la fin du processus. Le service de destination peut montrer une connexion depuis la bastion. Aucun de ces éléments n'explique seul tout l'événement, mais leur rapprochement révèle les contradictions.

Par exemple, une approbation peut autoriser `127.0.0.1:15432` vers une base pendant 20 minutes. Si le journal client indique une fermeture à 14 h 23, mais que la destination observe du trafic à 15 h 10 depuis la bastion, enquêtez immédiatement. L'agent a peut-être ouvert une autre connexion, réutilisé une session maître partagée, ou quelqu'un utilise peut-être un autre chemin avec le même compte.

Sallyport projette les journaux de session et d'appels individuels depuis un journal d'audit chiffré et chaîné par hachage, et `sp audit verify` vérifie cette chaîne hors ligne sans exiger la clé du coffre. C'est une propriété plus solide qu'un fichier journal qu'un processus agent peut modifier, même si elle ne dispense pas de restrictions de compte strictes.

Effectuez la vérification pendant les revues, et pas uniquement après un incident. Une chaîne détectant les altérations indique si l'historique enregistré est continu. Elle ne peut pas vous dire si vous avez enregistré assez de champs au départ. Décidez de ce que vous devrez savoir lors d'une mauvaise journée, puis assurez-vous que ces champs entrent dans l'événement avant le début de la connexion.

## Révoquez le chemin avant d'examiner l'histoire

Lorsque vous soupçonnez un tunnel incorrect ou détourné, interrompez d'abord le chemin de connexion. L'enquête peut attendre quelques minutes ; une route exposée peut transférer des données ou permettre un déplacement latéral pendant que les équipes débattent du journal faisant autorité.

Terminez le processus client et confirmez que son écouteur a disparu. Désactivez le compte SSH dédié ou supprimez sa clé publique du serveur qui accepte la connexion. Si le service cible utilise un identifiant qui a transité par le tunnel et qui a pu être exposé à un processus non fiable, faites tourner cet identifiant selon la procédure du service. Ne faites pas tourner des identifiants sans rapport simplement parce qu'un tunnel existait.

Préservez ensuite les preuves. Sauvegardez l'approbation, la route demandée, les journaux de processus, les journaux du serveur SSH, les enregistrements de connexion du service de destination et le résultat de la fermeture. Enregistrez les heures dans un même fuseau, de préférence UTC. Les revues d'incident perdent beaucoup de temps lorsqu'un exécuteur utilise l'heure locale, une bastion UTC et une application des horodatages sans fuseau.

Vérifiez l'existence d'un second écouteur avant de déclarer l'événement contenu. Sur l'hôte de l'agent, examinez le port demandé et les processus de transfert voisins. Sur la bastion, examinez les sessions SSH actives et les journaux d'authentification du compte dédié. À la destination, recherchez les connexions commencées après l'expiration prévue. Le but est de trouver les chemins d'accès encore actifs, pas de produire rapidement un récit bien ordonné.

Si une approbation humaine a permis la mauvaise route, corrigez d'abord la surface de demande. Un meilleur écran de revue qui accepte toujours des options SSH arbitraires reproduira la même erreur. Si une restriction serveur a échoué, testez la commande exacte qui devait être refusée après l'avoir corrigée et conservez la sortie d'échec. Les contrôles gagnent la confiance lorsque vous confirmez qu'ils refusent bien la route qu'ils doivent refuser.

## Testez les refus avant qu'un agent ne dépende du tunnel

Une conception de tunnel n'est prête qu'après le test de la connexion approuvée et des variantes interdites. Les tests du parcours nominal détectent les fautes de frappe. Les tests de refus montrent si la frontière existe réellement.

Utilisez un compte hors production et tentez un transfert qui doit fonctionner :

```sh
ssh -N -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:15432:db-admin.internal.example:5432 \
  agent-db-tunnel@bastion.internal.example
```

Tentez ensuite une destination que `PermitOpen` n'autorise pas :

```sh
ssh -N -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:15433:admin-api.internal.example:443 \
  agent-db-tunnel@bastion.internal.example
```

La seconde commande doit échouer pendant la configuration du transfert. Capturez la sortie client et l'entrée du journal serveur. Tentez ensuite `-R` et `-D` ; les deux doivent échouer pour un compte configuré avec `AllowTcpForwarding local`. Enfin, demandez un écouteur non local avec `0.0.0.0` et vérifiez que votre frontière de demande le refuse avant l'exécution de SSH, ou que l'environnement local empêche l'exposition que vous voulez éviter.

Testez l'expiration avec une durée courte. Démarrez le tunnel approuvé, laissez l'échéance du superviseur se déclencher et vérifiez trois éléments : le processus SSH quitte, `lsof` ne trouve plus l'écouteur et l'enregistrement de fermeture indique l'échéance au lieu de prétendre que la tâche s'est terminée normalement. Ce petit exercice révèle le problème des processus orphelins avant qu'une tâche de production ne dépende d'un tunnel qui ne se ferme jamais.

Gardez une règle simple lorsque la pression monte : un agent autonome peut recevoir uniquement la route nécessaire à sa tâche actuelle, pendant une durée limitée et sous une approbation nominative. Si votre configuration ne peut pas énoncer cette route en une ligne et prouver ensuite sa fermeture, l'agent dispose de plus d'autorité réseau que la tâche ne le justifie.
