Secrets, tokens et agents IA : comment éviter la catastrophe
J’ai passé pas mal de temps ces derniers mois à construire des agents IA, sur le modèle de ce que je décris dans mes articles sur les agents autonomes en Node.js. Chaque fois, la même question finit par arriver au bout de quelques semaines : comment donner à cet agent accès à mes outils et à mes systèmes sans que ça finisse en catastrophe de sécurité ?
La tentation est grande de faire simple : remplir une variable d’environnement, la passer au LLM, et voilà. Mais confier des secrets à un agent IA, ce n’est pas comme les confier à un process classique. Un agent est une boîte noire probabiliste qui peut fuiter ce que vous lui donnez, être manipulé, ou pire, invoquer des outils avec des identifiants qu’il n’aurait jamais dû voir. Cet article explore pourquoi c’est dangereux, et comment concevoir un système qui fait le job sans se tirer une balle dans le pied.
Pourquoi c’est différent d’un process classique
Avant de parler solutions, il faut comprendre le fond du problème. Les règles que vous appliquez déjà pour sécuriser vos services (moindre privilège, rotation, scoping) restent valables. Mais les agents IA ajoutent une couche de risque que les logiciels classiques n’ont pas.
Le problème du contexte
Un LLM fonctionne par contexte. Tout ce que vous lui passez, que ce soit dans le message système, dans les messages utilisateur, ou dans les retours d’outils, finit dans le contexte. Et le contexte est exactement ce que l’agent “voit” et “raisonne sur”. Si vous mettez un secret dans le contexte pour que l’agent puisse l’utiliser, alors :
- Le secret est copié dans chaque requête envoyée au fournisseur LLM, que ce soit un service cloud ou un modèle local. Si le modèle est en local, c’est déjà mieux, mais le secret traîne quand même dans les logs, les traces, les buffers.
- Le secret peut être recraché par le modèle. C’est la fuite directe : l’agent, en discutant avec un utilisateur ou en écrivant du code, peut reproduire le secret qu’il a en contexte, sans le vouloir.
- Le secret peut être redirigé. Une injection de prompt bien placée peut amener l’agent à “révéler” les secrets de son contexte à l’attaquant qui a réussi à injecter.
C’est exactement la différence avec un process classique : un serveur Node qui lit process.env.DATABASE_URL ne “répète” jamais ce secret dans une réponse. L’agent, lui, le peut. Tout ce qui entre dans le contexte est potentiellement une fuite.
Le problème de l’action
Un process classique exécute un chemin d’action déterministe. Un agent décide lui-même quels outils invoquer, dans quel ordre, avec quels paramètres. Donner un token puissant à un agent, c’est donner les clés à quelqu’un qui peut les utiliser de façon imprévisible, le jour où il est manipulé.
La combinaison est dévastatrice : des secrets dans le contexte plus une capacité d’action. Un agent avec accès à un token d’API et la possibilité d’exécuter du code, c’est un agent capable de se servir du token lui-même pour exfiltrer d’autres secrets.
Variable d’environnement ou gestionnaire de secrets ?
La première décision, c’est où stocker les secrets. Beaucoup de développeurs (moi compris, au début) se contentent de variables d’environnement. Voyons les deux mondes.
La variable d’environnement, pratique mais limitée
La variable d’environnement est un standard, simple, et tout le monde la comprend. Un fichier .env chargé au démarrage :
DATABASE_URL=postgres://user:pass@db:5432/mabase |
Ça fonctionne très bien pour un service qui lit ses propres secrets une fois au démarrage. Mais pour un agent IA, ça montre vite ses limites :
- Tout le contexte du process en hérite. Si l’agent lance un sous-processus, ce sous-processus voit les variables d’environnement. Un tool qui exécute un script shell hérite de tous les secrets du parent. Le moindre outil mal configuré peut les exposer.
- Le granularité est binaire. Tout ou rien. Soit le process a le secret, soit non. Impossible de dire “l’agent peut se connecter à la base mais pas en écriture” au niveau de la variable.
- Pas d’audit. On ne sait pas qui a lu quoi, quand.
- Pas de rotation automatique. Changer un secret impose de redéployer.
Le gestionnaire de secrets
Vault, Doppler, AWS Secrets Manager, GCP Secret Manager… tous répondent au même triple besoin :
- Centralisation : les secrets vivent dans un endroit unique, chiffré, versionné.
- Contrôle d’accès : on peut scoper qui (quelle identité, quel rôle) a le droit de lire quel secret.
- Rotation et audit : on change les secrets régulièrement, et chaque lecture est tracée.
La grande idée, c’est de ne plus donner les secrets au process, mais de lui donner le droit de les récupérer au moment où il en a besoin. La différence paraît subtile mais elle est énorme.
Comparons explicitement :
| Critère | Fichier .env |
Gestionnaire de secrets (Vault, Doppler…) |
|---|---|---|
| Stockage | En clair dans un fichier | Chiffré, centralisé, versionné |
| Rotation | Manuelle, redéploiement requis | Automatisée, souvent transparente |
| Audit | Aucun | Chaque lecture tracée |
| Scoping | Binaire (process entier) | Fin (identité, rôle, champ) |
| Fuite dans le contexte | Risque élevé si lu pour l’agent | Contrôlable, à la demande |
| Complexité | Nulle | Faible à modérée |
Pour un agent IA, le gestionnaire de secrets est la bonne réponse la plupart du temps, parce qu’il permet la chose la plus importante : ne lire le secret qu’au moment de l’action, dans un outil spécifique, sans le mettre dans le contexte du LLM.
Ne jamais laisser le LLM lire les secrets
C’est la règle d’or. Le LLM ne doit jamais recevoir la valeur d’un secret dans son contexte. Il ne doit pas le voir, parce que ce qu’il voit, il peut le fuiter. Ce qu’il doit avoir, c’est une capacité à agir avec le secret, déléguée à un outil qui, lui, est déterministe et verrouillé.
Prenons l’exemple Node.js. Le mauvais pattern : on met le token dans le contexte pour que l’agent puisse composer ses requêtes HTTP.
// MAUVAIS : le secret entre dans le contexte du LLM |
Ici, dès que l’outil mentionne le token dans un prompt ou un contexte, c’est foutu : le token est maintenant “visible” et peut être recopié dans une réponse.
Le bon pattern : l’outil agit comme une passerelle aveugle. Il lit le secret lui-même, fait l’appel, et ne renvoie au LLM que le résultat utile, jamais le secret.
// BON : le secret ne quitte jamais l'outil |
La différence est fondamentale : token vit dans la portée de l’outil, jamais dans le contexte du LLM. Le LLM ne sait même pas qu’un token existe. Il sait seulement qu’il peut appeler call_api et obtenir une réponse.
Moindre privilège : scoper les tokens
Le principe du moindre privilège s’applique de façon encore plus stricte pour un agent. Tornade dans une porcelaine : un agent avec un token “admin” qui est manipulé, c’est un désastre. Alors on scope, et on scope serré.
Des tokens dédiés, à périmètre réduit
Ne réutilisez jamais un token “humain” ou un token de service partagé pour un agent. Créez un token dédié, avec exactement les droits dont l’agent a besoin, rien de plus.
Prenons un exemple de token API avec des scopes. Supposons qu’on ait une API interne avec des scopes read, write, delete :
// Un token pour l'agent : lecture seule |
Si l’agent ne fait que lire de la documentation, il n’a pas besoin de write. Un token en lecture seule, c’est déjà un gros amortisseur : même si l’agent est manipulé et tente d’écrire, l’API lui répondra 403.
Déléguer via un service tiers avec droits limités
Le pattern le plus sain, c’est de ne jamais donner au LLM les identifiants de la vraie ressource, mais de le faire passer par un service qui porte les identifiants et applique une politique. C’est en réalité le pattern “ outil passeur “ vu plus haut, généralisé : le LLM parle à votre orchestration, votre orchestration parle aux ressources avec les vrais secrets.
Utilisateur -> Agent (LLM) -> Outil (orchestration) -> Ressource avec ses secrets |
Ne jamais laisser transparaître les credentials à travers la frontière LLM/orchestration.
Masking et redaction
Même avec une bonne architecture, des secrets peuvent transiter dans des résultats d’outils, des logs, ou des traces. C’est ici qu’intervient la redaction : on remplace les valeurs sensibles par des masques avant qu’elles n’atteignent le contexte du LLM ou les logs.
Un utilitaire de masking simple
Voici une fonction Node.js qui masque les occurrences de secrets dans un texte. C’est utile à appliquer systématiquement sur les résultats d’outils avant de les renvoyer au LLM.
function maskSecrets(text, secrets) { |
Ce masquage est une ceinture de sécurité. Il ne remplace pas la règle d’or (“ne pas mettre les secrets en contexte”), mais il rattrape les erreurs : un résultat d’outil qui, sans le faire exprès, contiendrait une valeur de secret, sera nettoyé avant d’atteindre le LLM.
Je recommande d’appliquer ce masquage à deux niveaux :
- Sur tous les résultats d’outils retournés au LLM (interception systématique)
- Sur tous les logs et traces (pour ne pas écrire les secrets en clair sur disque)
Un intercepteur de contexte
On peut aller plus loin avec une couche d’interception qui passe le contexte au crible avant que chaque message ne soit envoyé au modèle. C’est un garde-fou qui s’applique indépendamment de chaque outil :
function sanitizeContext(messages, secrets) { |
Cette interception est le filet final : même si un secret a transité quelque part, il n’entrera jamais dans le contexte effectivement envoyé au modèle.
Rotation des secrets
Aucun secret n’est éternel. Les fuites arrivent. La rotation, c’est le fait de changer régulièrement les secrets pour que ceux qui ont fuit deviennent inutiles.
Pour un agent IA, la rotation a deux dimensions :
- Rotation régulière : les tokens d’agent ont une durée de vie courte. Un token valable 24 h plutôt que 6 mois, c’est un risque qui se réduit de plusieurs ordres de grandeur. Si un token fuite, il n’est exploitable que pendant sa courte fenêtre de vie.
- Rotation accélérée en cas de suspicion : si vous détectez une injection de prompt, un comportement anormal de l’agent, ou une fuite dans les logs, vous faites tourner immédiatement tous les secrets qui ont pu être exposés.
La rotation est l’assurance de la catastrophe : elle ne l’empêche pas, mais elle en limite sévèrement la portée.
Voici une approche avec un gestionnaire de secrets qui supporte la rotation :
// Token éphémère avec TTL court pour chaque session d'agent |
Le but : le secret a une vie courte, est attaché à une identité précise, et est remplacé automatiquement.
Le sandboxing
Dernier pilier : même avec les secrets bien protégés, l’agent peut exécuter du code. C’est le cas des agents capables de créer leurs propres outils. Il faut alors sandboxer l’exécution, pour que même un code malveillant ou buggé n’ait pas accès aux secrets du process parent.
Le principe : l’environnement d’exécution du code généré est isolé, sans réseau vers l’intérieur du système, sans accès aux fichiers sensibles, avec ses propres (fausses) variables d’environnement.
// le code généré tourne dans un conteneur éphémère, réseau coupé vers l'intérieur |
Les points clés du sandbox :
--network=none: pas de sortie réseau, donc pas d’exfiltration. Le code généré ne peut pas joindre les API internes ni exfiltrer quoi que ce soit.--env-file=/dev/nullet volumes montés : le code généré ne voit ni les variables d’environnement du parent ni les fichiers du système.- Timeout : il faut toujours borner le temps d’exécution.
- Limites de ressources : mémoire et CPU plafonnées.
Le sandbox est la dernière ligne de défense. Même si toutes les protections précédentes échouent et que l’agent génère un code malveillant, ce code tourne dans un coin isolé où il ne peut rien atteindre.
Conclusion et checklist
Gérer les secrets pour un agent IA, ce n’est pas une option. C’est le cœur de la sécurité. Voici la checklist que j’applique maintenant à chaque projet d’agent :
- Aucun secret dans le contexte du LLM, jamais. L’agent a des capacités, pas des valeurs secrètes.
- Un gestionnaire de secrets (Vault, Doppler, Secrets Manager) plutôt que des
.envpour les secrets sensibles exposés aux agents. - Moindre privilège : token dédié à l’agent, scopes réduits, TTL court.
- L’agent passe par des outils-passeurs qui portent les secrets côté orchestration.
- Masking et redaction systématiques des résultats d’outils et des logs.
- Interception du contexte pour purger les secrets avant envoi au modèle.
- Rotation régulière et accélérée en cas de suspicion.
- Sandboxing de tout code généré par l’agent.
J’ai appris ces leçons à la dure, en voyant des tokens fuiter dans des logs ou des agents trop indiscrets. L’architecture des agents IA est puissante, mais elle exige de traiter les secrets comme des explosifs : confinement, scoping, et rotation. Faites-le avant la catastrophe, pas après.