Comment garder vos secrets hors des agents IA
Une clé API dans l’environnement d’un agent peut fuiter en un appel d’outil. Utilisez un token court et limité via une passerelle de credentials, jamais la clé réelle.
Ce que signifie garder les secrets hors des agents IA
Un agent IA est un processus Linux normal qui exécute des commandes. Le code qu’il exécute peut lire toutes les variables d’environnement détenues par ce processus. Une clé d’API présente dans l’environnement de l’agent est donc une clé que l’agent peut envoyer à n’importe quel hôte qu’il peut atteindre. Garder les secrets hors de l’agent consiste à lui fournir un handle plutôt que la clé : un token à durée de vie courte et limité à un périmètre, ou un placeholder qu’un autre composant remplace par la valeur réelle à la frontière réseau.
Il ne s’agit pas d’un modèle qui devient hostile. Le mécanisme est plus simple. Un agent lit une page web, un README ou un commentaire d’issue contenant des instructions, puis les suit, car un modèle de langage ne fait pas la différence entre le texte que vous avez écrit et celui qu’il a récupéré. C’est une prompt injection. Une fois qu’elle s’est produite, les dommages sont limités par un seul élément : ce que le processus peut lire. Si vous n’avez pas encore défini de boundary, exécuter un agent de programmation en toute sécurité sur un serveur présente les niveaux d’isolation sur lesquels ce guide s’appuie.
Le modèle de menace en termes simples
Exécutez cette commande avec le compte utilisateur sous lequel votre agent s’exécute.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'Chaque ligne qu’il affiche suffit à effectuer une requête HTTP vers le serveur d’un tiers. Examinez maintenant ce qui se trouve sur le disque à proximité de l’agent.
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'Un agent disposant d’un shell n’a pas besoin d’une exploitation sophistiquée pour exfiltrer ces données. Quatre méthodes courantes suffisent, et elles ressemblent toutes à des opérations normales dans les journaux :
- Un
curloufetchsortant vers n’importe quel hôte, avec la valeur dans une chaîne de requête. - Un
git commitetgit pushvers un dépôt dans lequel l’agent peut écrire. - Un script d’installation de paquet, qui exécute du code arbitraire avec le compte utilisateur de l’agent.
- Une résolution DNS d’un nom d’hôte contenant la valeur, qui fonctionne même lorsque le trafic HTTP sortant est bloqué.
Vous ne pouvez pas résoudre ce problème par une simple revue. La solution consiste à vous assurer qu’aucune donnée sensible n’est accessible.
Un secret dans l’arborescence de travail est un secret dans la fenêtre de contexte
Un agent lit les fichiers. Un fichier .env présent dans le dépôt dans lequel il travaille sera lu. Dès qu’il est lu, il se trouve dans la fenêtre de contexte. Il figure donc dans la transcription, dans les journaux que vous conservez et dans tout ce que l’agent écrit ensuite.
Avant, lorsque la clé se trouvait dans l’arborescence utilisée par l’agent :
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingAprès avoir déplacé le fichier hors de sa portée :
sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.envL’utilisateur de l’agent ne peut plus ouvrir le fichier, car l’arborescence de travail ne le contient plus. Les règles de refus de la configuration de l’agent constituent une deuxième couche, et non la première. Claude Code lit les règles d’autorisation dans .claude/settings.json du projet :
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}Cela empêche l’agent d’ouvrir un fichier par erreur pendant son exploration. Cela n’empêche pas une instruction injectée d’exécuter base64 .env, car il s’agit d’une commande shell et non d’une lecture de fichier. Considérez la configuration comme une barrière de protection et les permissions du système de fichiers comme le mur. La même séparation s’applique dans les conteneurs : fichiers d’environnement et secrets dans Docker Compose couvre la version de ce problème située un niveau plus bas.
Donnez à chaque agent son propre utilisateur sans privilèges
Si l’agent s’exécute avec votre compte, il hérite de vos clés SSH, de vos identifiants cloud et de l’historique de votre shell. Un utilisateur distinct nécessite une seule commande et supprime tous ces accès.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519La dernière ligne doit échouer avec cat: /home/you/.ssh/id_ed25519: Permission denied. Si elle affiche une clé, votre répertoire personnel est lisible par le groupe ou par tous les utilisateurs, et chmod 700 ~ corrige ce problème. N’ajoutez pas l’utilisateur de l’agent à sudo et ne lui accordez pas de règle NOPASSWD plus large que la seule commande dont il a réellement besoin. Utilisateurs avec le principe du moindre privilège sur un VPS détaille la configuration du groupe et de sudoers.
Une autre limite mérite d’être ajoutée sur un VPS cloud. Le service de métadonnées de l’instance répond sur une adresse link-local fixe et fournit souvent des identifiants de rôle à tout ce qui les demande.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTVérifiez ce point depuis le compte de l’agent. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ ne doit rien afficher et doit se terminer avec un code différent de zéro, car le paquet est rejeté avant de quitter la machine.
Injecter l’identifiant au niveau de la frontière
Le modèle qui résout réellement ce problème est l’injection d’identifiants. L’agent ne détient jamais de clé réelle. Il envoie sa requête via une passerelle locale, qui remplace un espace réservé par le secret réel lors de l’envoi. Le secret est stocké dans la passerelle, dans un autre processus, appartenant à un autre utilisateur.
OneCLI est une implémentation open source de ce modèle, sous licence Apache-2.0. Il s’exécute dans un conteneur à côté de l’agent. En juillet 2026, le projet documente cette configuration :
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitLe dashboard écoute sur le port 10254 et la passerelle sur le port 10255. Vous stockez l’identifiant réel une seule fois, puis vous fournissez à chaque agent une valeur d’espace réservé à la place de la clé, ainsi que son propre access token limité, qu’il envoie dans un en-tête Proxy-Authorization. La passerelle identifie la requête sortante à partir de l’hôte et du chemin, déchiffre l’identifiant correspondant et le substitue. L’environnement de l’agent ne contient rien d’intéressant à dérober.
L’intérêt de cette approche ne réside pas dans le chiffrement. Il réside dans le fait que la question « qu’a utilisé cet agent, et quand ? » devient une requête dans les logs. Vous consultez une seule piste d’audit au lieu de chercher dans lequel de six environnements une copie de la clé a été conservée.
Transmettre le secret au processus, pas à l’environnement
Si vous exécutez l’agent avec systemd, vous n’avez pas besoin de variables d’environnement. LoadCredential= place le secret dans un répertoire privé que seul ce service peut lire. Le secret est exposé sous la forme %d dans le fichier d’unité et sous la forme $CREDENTIALS_DIRECTORY dans le processus. La valeur n’apparaît jamais dans /proc/<pid>/environ. ps eww ne peut donc pas l’afficher. Le répertoire disparaît lorsque le service s’arrête.
Chiffrez d’abord l’identifiant pour la machine. Ces commandes proviennent de la documentation de systemd. Elles fonctionnent avec systemd 250 ou une version ultérieure, notamment avec Ubuntu 24.04 et Debian 13 :
echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
systemd-creds cat api_keyLa dernière commande affiche sk-example-value. Cela confirme que le fichier chiffré peut être déchiffré sur cet hôte. Référencez-le ensuite dans l’unité :
[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-workerVotre code d’agent ouvre le fichier à l’emplacement $AGENT_KEY_FILE lorsqu’il a besoin de la valeur. La lecture d’un fichier est ponctuelle. Une variable d’environnement reste présente pendant toute la durée de vie du processus, dans chaque processus enfant qu’il crée.
Préférez les tokens à courte durée de validité aux clés à longue durée de validité
Une clé qui n’expire jamais reste valide chaque fois qu’elle apparaît, plusieurs mois plus tard, dans un journal ou une transcription. Lorsque le service propose un token de session, utilisez-le et définissez la durée de validité la plus courte compatible avec la tâche.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900Quinze minutes est la durée minimale acceptée par AWS STS (security token service). Elle suffit généralement pour une tâche d’agent. Pour GitHub, attribuez à l’utilisateur de l’agent son propre login gh avec un token à granularité fine limité au seul dépôt sur lequel il travaille. Ainsi, gh auth token exécuté dans cette session renvoie un résultat qui ne peut rien modifier ailleurs. Limitez d’abord par ressource, puis par durée.
Vérifiez, puis vérifiez régulièrement
Trois contrôles doivent être exécutés après toute modification de la configuration d’un agent. Exécutez-les avec l’utilisateur de l’agent, et non avec votre propre compte.
sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/userLa première commande ne doit rien afficher. La deuxième doit afficher ls: cannot open directory '/home/you/': Permission denied. La troisième indique quelle identité le chemin réseau de l’agent présente. C’est la question à laquelle le modèle de passerelle doit répondre : un 401 signifie que l’agent ne transporte aucun identifiant GitHub qui lui est propre, tandis qu’un 200 signifie qu’il en transporte un. Vous devez donc savoir de quel token il s’agit. Si vous exécutez des agents sans intervention, contrôler les coûts des agents IA sur un VPS présente les limites budgétaires associées à ces limites d’accès.
FAQ
Puis-je simplement faire confiance au modèle pour qu’il ne divulgue pas mes clés ?
Non, car le modèle n’est pas l’attaquant dans ce modèle de menace. L’agent lit le contenu des pages web, des dépôts et des gestionnaires d’incidents, et ce contenu peut contenir des instructions. Le modèle ne peut pas déterminer de manière fiable quelles instructions viennent de vous et lesquelles proviennent du contenu récupéré. Tout contrôle qui dépend d’un choix correct du modèle échoue dès qu’une instruction injectée est suffisamment convaincante. Le contrôle doit donc être assuré par le système d’exploitation ou le réseau.
Les variables d’environnement sont-elles vraiment si mauvaises pour les secrets des agents ?
Elles posent un problème précis : elles sont héritées. Chaque processus fils lancé par l’agent en reçoit une copie, notamment un script de build, un exécuteur de tests et tout hook d’installation de package. Les variables sont également lisibles via /proc/<pid>/environ par le même utilisateur. Tout ce que l’agent lance peut donc les lire sans que l’agent les transmette explicitement. Un fichier lu au moment de l’utilisation, avec LoadCredential= ou via une passerelle, limite l’exposition à ce moment précis.
Le fait de placer les secrets dans un coffre-fort résout-il le problème à lui seul ?
Seulement en partie. Un coffre-fort résout le problème du stockage. Il ne résout pas la dernière étape : un composant récupère le secret dans le coffre-fort et le transmet à l’agent sous forme de variable d’environnement, ce qui vous ramène au point de départ. Ce qui compte, c’est l’élément qui effectue la substitution. Si l’agent récupère le secret, il détient le secret. Si une passerelle ou le système init effectue la substitution en dehors du processus de l’agent, l’agent ne le détient jamais.
Comment savoir si un agent a déjà divulgué quelque chose ?
En général, vous ne pouvez pas le déterminer après coup. C’est précisément l’argument en faveur de la passerelle. Sans passerelle, vos éléments de preuve sont dispersés entre l’historique du shell, la transcription de l’agent et les logs des connexions sortantes, que vous ne conservez probablement pas. Avec une passerelle de credentials, chaque utilisation d’un credential correspond à une ligne contenant l’identité de l’agent et un horodatage. Si vous suspectez une fuite, faites d’abord une rotation de la clé et enquêtez ensuite. La rotation ne coûte presque rien, contrairement à la certitude.
Que dois-je faire au minimum aujourd’hui ?
Déplacez chaque fichier .env hors des répertoires dans lesquels vos agents travaillent et créez un utilisateur non privilégié par agent. Ces deux changements prennent environ dix minutes et ferment le vecteur le plus courant : un agent lit un fichier de credentials qui n’avait aucune raison de se trouver à côté du code. La passerelle et les tokens à courte durée de vie viennent ensuite, pas en premier. Ce même point de départ s’applique à tout runtime d’agent, notamment exécuter un agent autonome en toute sécurité sur un VPS.