# Gestionnaire de secrets pour agents IA : le stockage ne contrôle pas les actions

Un gestionnaire de secrets pour agents IA est nécessaire, mais il ne règle pas la partie la plus dangereuse : que se passe-t-il après qu’un agent demande un identifiant ? Si le gestionnaire renvoie la valeur en clair au processus de l’agent, le secret a déjà franchi la frontière que vous cherchiez à protéger.

Cette distinction peut sembler tatillonne jusqu’au jour où un agent suit une instruction piégée, appelle un outil inattendu, écrit un fichier de diagnostic ou contacte le mauvais hôte. Un coffre peut conserver un jeton chiffré pendant des années, puis annuler une grande partie de cette protection en un seul appel de récupération. Pour le travail des agents, une conception plus sûre conserve l’identifiant dans un exécuteur de confiance et permet à l’agent de demander une action authentifiée.

## Un coffre protège le stockage, pas l’utilisation d’un identifiant

Un gestionnaire de secrets traditionnel répond à une question de stockage : qui peut récupérer cette valeur ? Les agents autonomes soulèvent une question d’utilisation : ce processus peut-il effectuer cette action précise avec cette valeur, à cet instant, vers cette destination ?

La plupart des équipes ont commencé avec de bonnes pratiques. Elles ont retiré les jetons des dépôts, les ont chiffrés au repos, les ont renouvelés après un commit accidentel et les ont injectés dans un environnement de build. Ces habitudes conviennent bien au déploiement d’applications ordinaires. Un job de build utilise généralement un script fixe, une durée limitée et un ensemble connu de points de terminaison. Son environnement est peut-être encore trop large, mais une personne a écrit les commandes avant le démarrage du job.

Un agent est différent. Il génère des commandes, choisit des outils, suit le texte des tickets, lit des fichiers écrits par d’autres personnes et peut revoir son propre plan. Lui donner `DEPLOY_TOKEN` dans une variable d’environnement signifie que chaque sous-processus qu’il lance peut lire le jeton. Il en va de même pour une commande shell qui affiche l’environnement, un débogueur, un script d’installation de paquet ou un outil non fiable que l’agent sélectionne parce qu’un document le lui a demandé.

Le gestionnaire de secrets n’a pas échoué dans ce cas. Il a fait exactement ce pour quoi il avait été configuré : livrer un secret à une charge de travail autorisée. Le modèle d’autorisation était simplement trop grossier pour un acteur capable de choisir lui-même sa prochaine opération.

Gardez ces deux permissions séparées :

- La permission d’identifier un identifiant par une référence stable.
- La permission d’utiliser cet identifiant pour une action sortante décrite.

La première permission peut être sûre pour un agent. La seconde doit être encadrée par des contraintes adaptées à l’action. Une requête comme `POST https://deploy.example.internal/releases` peut être évaluée par un responsable. Une requête comme `read production token` remet un actif dont les utilisations futures ne peuvent pas être évaluées au moment de la récupération.

Cela corrige aussi une affirmation courante, mais peu utile : « l’agent dispose déjà de l’exécution de code, donc cacher le secret ne change rien ». L’exécution de code sur le poste d’un développeur est déjà suffisamment grave. Un jeton bearer copié depuis ce poste crée un second problème : il peut quitter la machine, survivre à la fin de la session et fonctionner depuis un ordinateur complètement différent. Le garder hors du processus ne supprime pas tous les risques. Cela retire une forme d’accès portable et durable que les attaquants recherchent particulièrement.

La RFC 6750 le montre clairement pour les jetons bearer OAuth. Sa définition indique que toute partie qui possède un jeton bearer peut l’utiliser. Le protocole ne demande pas si cette partie est votre agent autorisé, un plugin malveillant ou quelqu’un qui a trouvé un fichier journal. Traiter les jetons bearer comme une sortie d’outil ordinaire revient à ignorer leur propriété fondamentale.

## L’accès en lecture transforme les erreurs d’instructions en fuites d’identifiants

Un agent qui dispose d’un secret en clair peut divulguer un identifiant sans chercher à l’exfiltrer. Le chemin dangereux paraît souvent banal jusqu’à sa dernière étape.

Imaginez qu’un agent soit chargé de diagnostiquer pourquoi une API de déploiement a rejeté une mise en production. Il lit un document du dépôt qui lui demande de collecter un bundle de support. Le script du bundle exécute `env`, copie des fichiers de configuration et archive le résultat. L’agent crée consciencieusement l’archive et la joint à un ticket ou la téléverse dans un système de discussion. Le jeton n’a jamais été affiché dans le terminal, mais il est passé par l’environnement, l’archive, le ticket et tous les systèmes de sauvegarde ou de notification associés à ce ticket.

Il ne s’agit pas seulement d’injection de prompt. L’injection de prompt est une voie vers la mauvaise action, mais un identifiant copié crée son propre rayon d’impact après la disparition de l’instruction initiale. Un lecteur ultérieur du ticket peut le télécharger. Un scanner automatisé peut l’indexer. Un destinataire peut le réutiliser longtemps après la fin de l’exécution de l’agent.

Le même problème apparaît sous des formes plus discrètes :

- Un client HTTP verbeux affiche un en-tête `Authorization` lors d’une nouvelle tentative.
- Un fichier d’historique shell capture un jeton passé sur la ligne de commande.
- Un scénario de test enregistre un en-tête de requête et est commité.
- Un processus enfant hérite d’une variable d’environnement dont il n’avait pas besoin.
- Un modèle inclut un secret dans son explication parce que le secret apparaissait dans la sortie de l’outil.

La réponse la plus courante est la rédaction. Elle aide après une erreur, mais ne permet pas de prouver que chaque chemin de sortie, format d’archive, trace, sous-processus et service distant a correctement traité la valeur. Elle échoue aussi face aux formats de secrets inconnus et aux jetons répartis entre plusieurs champs. Ne faites pas de la rédaction la principale barrière autour d’identifiants qu’un agent n’aurait jamais dû lire.

Une meilleure organisation réduit les entrées de l’agent. Celui-ci envoie une intention qui ne contient aucune donnée d’identification : méthode HTTP, URL autorisée, corps de requête et référence d’identifiant. Un composant de confiance vérifie la requête, ajoute les données d’authentification en interne, l’envoie et renvoie une réponse dont les en-têtes sensibles ont été supprimés.

Cette frontière fournit une meilleure réponse lors d’un incident. Si un agent se comporte mal, révoquez sa session ou refusez les actions suivantes. Si vous découvrez une instruction malveillante après l’exécution, vous ne devez pas commencer par supposer que chaque transcription, fichier temporaire et artefact distant contient désormais un jeton de production.

## L’exécution d’actions est une interface différente de la récupération de secrets

Une frontière d’exécution doit accepter une opération, et non remettre à l’agent une valeur opaque en espérant qu’il l’utilisera prudemment. C’est la distinction que les équipes brouillent le plus souvent. Lorsqu’elle est mal faite, le coffre devient un distributeur d’identifiants.

Pour HTTP, l’agent peut devoir exprimer une requête comme celle-ci :

```json
{
  "credential": "release-api",
  "method": "POST",
  "url": "https://deploy.example.internal/releases",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
    "version": "2025.03.8",
    "environment": "staging"
  }
}
```

L’exécuteur résout `release-api` dans son coffre protégé, injecte le mécanisme d’authentification correspondant et effectue la requête. L’agent peut recevoir un résultat de cette forme :

```json
{
  "status": 201,
  "headers": {
    "content-type": "application/json"
  },
  "body": {
    "release_id": "rel_4821",
    "state": "queued"
  }
}
```

La réponse ne doit pas inclure l’en-tête `Authorization` injecté, une copie de l’identifiant ou des informations de diagnostic du transport qui l’exposeraient. Cela paraît évident, mais la conception des frontières échoue lorsque les développeurs considèrent les journaux de requêtes et de réponses comme une plomberie inoffensive.

Pour SSH, l’agent doit envoyer un hôte, un compte, une commande et éventuellement une référence d’identifiant. L’exécuteur utilise la clé privée pendant l’échange d’authentification SSH et renvoie la sortie standard, la sortie d’erreur et le code de sortie. L’agent ne reçoit jamais de bloc PEM ni de socket d’agent qu’il pourrait réutiliser ailleurs.

Ne confondez pas cela avec un proxy généraliste. Un proxy transmet des flux arbitraires et peut les inspecter ou les modifier. Un exécuteur d’actions a un rôle plus étroit : il détient les identifiants, effectue des opérations HTTP et SSH nommées, enregistre la décision et renvoie un résultat limité. Cette étroitesse est un avantage. Chaque fonctionnalité qui accepte un autre protocole ou introduit un contournement générique donne à l’agent davantage de moyens de transformer une autorité en requête impossible à examiner.

L’interface de requête doit malgré tout rester rigoureuse. Une simple référence d’identifiant ne suffit pas. Si `release-api` peut authentifier des requêtes auprès de nombreux hôtes ou accepter une URL arbitraire, l’agent peut diriger un identifiant valide vers un point de terminaison contrôlé par un attaquant ou vers un service interne moins protégé. Associez les identifiants au mécanisme d’authentification et aux destinations prévus. Refusez les pièges d’URL comme les segments userinfo inattendus, les redirections vers de nouveaux hôtes ou un hôte qui ressemble seulement à celui qui est autorisé.

L’exécuteur doit aussi décider ce qu’il renvoie. Une réponse API complète peut contenir des données utilisateur, des jetons d’accès créés par un service en aval ou des informations de configuration qui ne devraient pas revenir dans le contexte du modèle. Ne renvoyer que les champs nécessaires à l’opération suivante rend souvent l’agent plus fiable et plus sûr.

## SSH a besoin d’un contrôle des commandes, pas seulement d’une protection des clés privées

Cacher une clé privée SSH aide, mais ne rend pas sûres les commandes distantes arbitraires. L’authentification indique à un serveur qui s’est connecté. Elle ne limite pas ce que ce compte peut faire après l’ouverture du shell.

La RFC 4252 décrit l’authentification SSH par clé publique comme une signature portant sur les données de l’échange. La clé privée reste privée pendant cet échange, ce qui explique pourquoi SSH est souvent considéré comme plus sûr qu’un jeton API placé dans une variable d’environnement. C’est vrai dans un sens précis : le client prouve qu’il possède la clé au lieu de l’envoyer sur le réseau. Pourtant, un processus qui peut demander à un agent SSH de signer peut toujours accéder aux systèmes qui font confiance à cette clé.

Le transfert de l’agent SSH mérite une explication directe. Il permet à un serveur distant d’utiliser votre agent d’authentification local pendant la connexion. Le serveur distant ne reçoit pas le fichier de clé privée, mais il peut demander des signatures via le socket transféré. Cela n’est acceptable que si vous faites confiance à cette machine distante et acceptez qu’elle puisse, en pratique, s’authentifier en votre nom pendant toute la durée du transfert. Ce n’est pas une frontière fiable pour un agent autonome qui peut choisir où se connecter et quelle commande de configuration exécuter.

Une interface d’actions SSH plus sûre nomme explicitement la destination et la commande. Elle doit enregistrer les deux, car `ssh deploy@host \"./deploy staging\"` et `ssh deploy@host \"cat /etc/shadow\"` produisent le même événement d’authentification, mais ont des conséquences très différentes.

Les restrictions côté distant restent nécessaires. Utilisez des comptes distincts pour des fonctions distinctes. Donnez à un compte de déploiement l’accès aux répertoires de déploiement plutôt que des droits d’administrateur. Lorsque le serveur le permet, configurez des commandes forcées ou des enveloppes de commandes restreintes pour les identifiants d’automatisation. Évitez une clé d’administrateur personnelle partagée pour les tâches d’un agent simplement parce qu’elle se trouve déjà sur un poste de travail.

Une requête SSH pratique peut être aussi limitée que celle-ci :

```json
{
  "credential": "staging-deployer",
  "host": "staging-runner.internal",
  "user": "deploy",
  "command": "./release apply 2025.03.8",
  "timeout_seconds": 120
}
```

Cette requête donne à un responsable quelque chose de concret à approuver. Elle fournit aussi un élément concret à auditer ensuite. Si l’agent a besoin d’un shell interactif, arrêtez-vous et demandez pourquoi. Les shells interactifs sont utiles aux humains qui réparent un système. Ils constituent un mauvais choix par défaut pour un agent, car ils transforment une action limitée en session ouverte où chaque commande suivante hérite de la même autorité.

La vérification de l’hôte est également importante. Un exécuteur doit utiliser la vérification des hôtes connus au lieu d’accepter n’importe quelle clé présentée parce qu’un modèle lui a demandé de se connecter. Si l’agent peut désactiver cette vérification, un attaquant présent sur le réseau peut recevoir les commandes, observer la sortie et éventuellement capturer les données envoyées par l’agent après la connexion.

## L’autorisation doit identifier le processus et la portée de l’action

Un bouton d’autorisation est faible s’il indique seulement qu’« un agent » a demandé un accès. Vous devez savoir quel processus local a fait la demande, quelle autorité signée l’a lancé et si l’autorisation s’applique à cette exécution ou à toutes les exécutions futures.

Les demandes à chaque appel semblent les plus sûres, si bien que les équipes commencent souvent ainsi. Puis l’agent enchaîne une longue série de lectures inoffensives, de vérifications d’état et d’appels de suivi. La personne qui surveille reçoit un mur de boîtes de dialogue presque identiques et commence à approuver machinalement. Cette réaction est compréhensible, mais elle détruit le contrôle recherché.

L’autorisation par session fonctionne mieux pour le travail courant lorsqu’elle lie une autorisation à la durée de vie d’un processus d’agent. La personne autorise une exécution précise après avoir vu son identité. L’agent peut terminer la séquence prévue jusqu’à sa fermeture. Un nouveau processus reçoit une nouvelle décision. Cela limite l’effet d’un processus redémarré ou remplacé qui utiliserait par hasard le même répertoire de projet.

Réservez l’autorisation à chaque appel aux identifiants dont l’utilisation entraîne une conséquence irréversible ou coûteuse. Les identifiants de mise en production, les jetons de suppression de compte ou une identité SSH donnant accès à des systèmes sensibles entrent dans cette catégorie. Les appels de consultation en lecture seule ne le nécessitent généralement pas.

La portée de l’autorisation doit répondre à quatre questions en termes simples :

1. Quelle autorité de signature du code ou quel exécutable a lancé cette requête ?
2. Quelle référence d’identifiant sera utilisée ?
3. Quelle destination ou quel hôte recevra l’action ?
4. La décision expire-t-elle à la fermeture du processus, ou cet appel exige-t-il sa propre confirmation ?

Évitez un langage de politique, sauf si vous disposez de personnes capables de le maintenir et de tests qui démontrent ses effets. Les moteurs de politiques séduisent les ingénieurs parce qu’ils promettent une réponse précise à chaque situation. En pratique, un ensemble de règles devient un programme d’autorisation non documenté, puis les équipes accordent une exception large lorsqu’il bloque le travail. Un petit ensemble de contrôles visibles est plus facile à inspecter et plus difficile à affaiblir par accident.

Sallyport utilise une séquence de décision fixe : un coffre verrouillé refuse toutes les actions, un nouveau processus d’agent exige par défaut une autorisation de session et certains identifiants peuvent exiger une approbation à chaque utilisation. Le modèle reste volontairement réduit, car une frontière d’identifiants pour agents doit rendre l’état de l’autorisation évident plutôt que forcer les administrateurs à déboguer une syntaxe d’autorisation.

## Un journal d’audit doit répondre aux questions après l’exécution

Vous avez besoin de deux vues de l’activité des agents : une pour l’exécution et une pour chaque appel utilisant un identifiant. Un enregistrement de session indique qui a exécuté l’agent et quand son autorité a pris fin. Un enregistrement d’action indique quelle destination, quelle méthode ou commande, quelle référence d’identifiant, quelle décision et quel résultat ont été associés à l’opération.

Les équipes ne journalisent souvent qu’une transcription du terminal. Ce n’est pas suffisant. Une transcription indique ce que l’agent a affiché, pas nécessairement ce qu’un exécuteur a envoyé. Elle peut omettre les appels en arrière-plan, contenir une sortie modifiée ou révéler des secrets si l’agent y avait accès. À l’inverse, un journal brut des paquets ou des requêtes peut conserver trop de données sensibles pour être stocké en sécurité.

Journalisez le point de décision, pas chaque octet. Pour HTTP, capturez une méthode normalisée, l’hôte, le chemin, la référence d’identifiant, le statut de réponse, l’horodatage, l’identité de session et le résultat de l’autorisation. En cas de besoin de corrélation sans conserver de données client, enregistrez une empreinte du corps de la requête ou un résumé limité. Pour SSH, capturez l’hôte vérifié, l’utilisateur distant, la commande, le code de sortie ainsi que les mêmes champs de session et d’autorisation.

Un enregistrement doit permettre de répondre à la question suivante : « Quel processus autorisé a utilisé l’identifiant de déploiement pour effectuer quelle opération, et un humain a-t-il approuvé cette utilisation ? » Si ce n’est pas possible, il peut aider au débogage, mais il sera peu utile après un incident.

Les preuves d’altération augmentent la valeur de cet enregistrement. Un simple fichier journal sur la même machine peut être modifié par un processus disposant de suffisamment de droits locaux. Une chaîne de hachage relie chaque événement à l’événement précédent : la suppression ou la modification d’un enregistrement antérieur rompt donc la vérification des suivants. Le chiffrement protège le contenu, tandis que le chaînage fournit la preuve que la séquence a changé.

Sallyport produit les journaux de sessions et d’actions depuis un journal d’audit chiffré et chaîné par hachage, et `sp audit verify` peut vérifier la chaîne hors ligne sur le texte chiffré sans clé de coffre. Cette séparation est importante pendant une enquête : un responsable peut tester l’intégrité du journal sans obtenir l’accès aux identifiants ou au contenu des requêtes conservés dans le coffre.

Une chaîne ne rend pas miraculeusement fiable un point de terminaison compromis. Un attaquant qui contrôle l’application en cours d’exécution peut toujours tenter des actions avant votre intervention, et un attaquant qui contrôle le système avant l’écriture d’une entrée peut influencer ce qui est enregistré. La chaîne fournit des preuves solides sur l’historique conservé. Associez-la à une révocation rapide, à un stockage local protégé et aux journaux côté serveur si vous avez besoin d’une vue plus complète de l’événement.

## Le problème commence lorsque l’agent obtient une échappatoire pratique

Les conceptions les plus dangereuses commencent souvent par une exception raisonnable. Quelqu’un estime que la passerelle est trop restrictive pour une intégration, alors l’agent reçoit une commande shell générique avec un jeton dans son environnement. Ou un outil de déploiement a besoin de SSH, alors l’équipe active le transfert au lieu de définir l’action SSH précise. Ou un ingénieur ajoute un point de terminaison de débogage qui renvoie les en-têtes parce que cela facilite les tests.

Chaque exception résout un problème local et rouvre la récupération des secrets sous un autre nom.

Prenons une configuration familière. Un agent de programmation possède un jeton de service dans son environnement afin de pouvoir exécuter une commande de mise en production. Il lit un commentaire de pull request demandant une enquête sur un déploiement échoué. Un script auxiliaire du dépôt appelle une commande de diagnostic. Cette commande exporte les variables d’environnement dans une archive de support. L’agent téléverse l’archive vers un outil de suivi de tickets tiers parce que le commentaire demandait de créer un ticket.

Aucun attaquant n’avait besoin de connaître la valeur du jeton à l’avance. Il lui suffisait d’influencer un texte que l’agent traiterait comme une instruction et de l’orienter vers un outil qui exposait l’état hérité. Révoquer la session de l’agent après la découverte ne retire pas l’archive. Le renouvellement du jeton devient obligatoire et l’équipe doit désormais retrouver tous les endroits où l’archive a circulé.

Un exécuteur contrôlé modifie cette séquence. Le script auxiliaire peut toujours s’exécuter et l’agent peut toujours obtenir l’état d’un déploiement via une action API approuvée. Mais il ne peut pas lire le jeton dans son environnement, car le jeton n’y a jamais été placé. Si le script tente un nouvel appel utilisant un identifiant, l’exécuteur l’enregistre et applique la décision de session ou par appel. L’archive de support contient moins de choses à voler.

N’accordez pas de contournement simplement parce qu’un agent affirme avoir besoin d’un outil pour terminer une tâche. Demandez quelle est l’opération minimale dont l’outil a besoin. S’il doit appeler un seul point de terminaison API, exposez cette opération. S’il a besoin d’une seule commande de déploiement, exposez cette commande. Si son besoin est réellement large, considérez la demande comme une autorité large et exigez qu’un humain utilise un processus distinct et délibéré.

## Construisez la frontière autour des actions qui comptent

Commencez par les identifiants dont la perte imposerait un renouvellement d’urgence ou permettrait une modification significative de la production. Vous n’avez pas besoin de déplacer tous les jetons de développement dès le premier jour. Une frontière partielle autour des identifiants les plus dangereux vaut mieux qu’une migration complète que personne ne termine.

Commencez par inventorier la manière dont les agents obtiennent leur autorité aujourd’hui. Examinez les scripts de lancement des agents, la configuration du shell, les fichiers de transmission CI, la configuration des outils, les fichiers `.env` locaux et les enveloppes de commandes. Repérez chaque endroit qui fournit à un agent un jeton, un mot de passe, une clé privée, une session cloud ou un socket d’authentification transféré. Cet inventaire révèle souvent que l’agent dispose de plus d’autorité via les paramètres hérités du développeur que quiconque ne le pensait.

Remplacez ensuite la transmission en clair par des requêtes d’action. Définissez les références d’identifiants selon leur fonction, et non selon une personne ou une étiquette d’environnement vague. `staging-deployer` est plus explicite que `shared-key-2`. Créez des références distinctes lorsque deux usages nécessitent des règles d’autorisation ou des destinations différentes.

Pour chaque référence, décidez des points suivants avant de l’automatiser :

- Quels hôtes, méthodes et chemins HTTP, ou quels hôtes, utilisateurs et commandes SSH correspondent à sa fonction ?
- Un nouveau processus d’agent doit-il obtenir une autorisation explicite avant de pouvoir l’utiliser ?
- Chaque utilisation doit-elle être confirmée parce que l’action est destructive ou difficile à annuler ?
- Quels champs de résultat l’agent doit-il recevoir pour poursuivre son travail ?
- Quels enregistrements permettront d’expliquer l’action plus tard sans conserver de données secrètes ?

Testez les refus avec autant de soin que le parcours nominal. Lancez un processus d’agent sans autorisation et vérifiez qu’il ne peut pas agir. Essayez un nom d’hôte ressemblant au bon, une redirection vers un autre hôte, une commande en dehors du chemin de déploiement prévu et une tentative d’afficher la référence d’identifiant comme s’il s’agissait d’une valeur. Vérifiez qu’une session révoquée ne peut plus continuer à envoyer des requêtes.

Testez aussi l’aspect humain. Si les demandes d’autorisation arrivent si souvent que vous cessez de les lire, vos portées sont mal définies. Si une boîte de dialogue ne peut pas vous indiquer quel processus a effectué la demande, l’identité de votre processus est trop faible. Si l’agent ne peut pas terminer une tâche normale sans demander sans cesse une échappatoire générique, l’interface d’action a besoin d’une autre opération soigneusement limitée, pas d’une divulgation de secret.

Le critère de réussite est simple : un agent peut accomplir le travail autorisé, mais une transcription, un sous-processus, un plugin ou une instruction piégée ne peut pas transformer ce travail en possession d’un identifiant réutilisable. Construisez cette protection avant que le prochain déploiement urgent ne rende l’exception pratique inoffensive en apparence.
