Le mot de passe est dans le transcript : où atterrissent les secrets confiés à un agent de sécurité

eHVB-IT · · sécurité de l'IA · agents LLM · gestion des secrets · test d'intrusion · SOC

Un identifiant de test passé à un agent LLM est recopié dans le transcript de session et dans chaque délégation. Le parcours observé lors d'un test d'intrusion, le nettoyage qui visait le mauvais endroit, et les contrôles qui tiennent.

Read in English

Résumé

Un test d'intrusion authentifié commence par un identifiant : un compte de test créé pour la mission, avec un mot de passe que le client confie au testeur. Quand ce testeur est un agent LLM, la question de savoir où va ce mot de passe change de nature. Lors d'une mission authentifiée menée en octobre 2026, le dossier de la mission était propre : le mot de passe n'y avait jamais été écrit, et le jeton de session de test avait été supprimé. Le mot de passe se trouvait pourtant en clair sur le disque, deux fois, à un endroit que personne n'avait contrôlé : le transcript de la session. Cette note décrit le parcours qu'il a suivi, explique pourquoi ce parcours découle du fonctionnement même d'un agent, et présente les contrôles que nous appliquons désormais.

Le parcours du secret

La mission a été lancée depuis une commande qui délègue le travail offensif à un sous-agent spécialisé. Le mot de passe du compte de test a été passé en argument de cette commande. Nous l'avons ensuite retrouvé à deux endroits.

  1. Le transcript de la session. Notre outillage enregistre chaque session sous la forme d'un fichier JSONL, sous ~/.claude/projects/<projet>/<session>.jsonl. Ce fichier contient chaque message, donc l'argument de la commande, en clair.
  2. Le prompt du sous-agent. Pour déléguer, la session principale rédige un brief que le sous-agent reçoit comme instruction. Le mot de passe y a été recopié, puisque le sous-agent en avait besoin pour se connecter. Ce brief est lui aussi enregistré.

L'utilisateur a tapé le secret une fois. L'outillage l'a écrit plusieurs fois, dans des fichiers qui ne font pas partie de la mission et qui survivent à sa clôture.

Pourquoi ce n'est pas un bug ponctuel

Un agent travaille sur son contexte, et ce contexte est du texte. Tout ce qui y entre (un argument, un brief, la sortie d'une commande) est conservé pour que l'agent puisse s'y référer, et enregistré pour que la session puisse être reprise. Déléguer à un sous-agent, c'est sérialiser une partie de ce contexte dans un nouveau message. Chaque étape de délégation est donc une copie de plus.

Le même mécanisme vaut pour ce qui sort des outils. Si l'agent affiche une réponse HTTP qui contient un cookie de session, ou le contenu d'un fichier de configuration, ce texte entre dans le contexte et donc dans le transcript. Nous ne l'avons pas mesuré sur cette mission ; c'est une conséquence directe du fonctionnement décrit ci-dessus, et nous la traitons comme telle.

La règle qui en découle : la surface d'exposition d'un secret confié à un agent, c'est l'ensemble des endroits où l'outillage l'a copié, et non l'endroit où on l'a tapé.

Le nettoyage qui visait le mauvais endroit

En fin de mission, le sous-agent a vérifié lui-même qu'il n'avait laissé aucune trace : une recherche du mot de passe dans tout le dossier de la mission n'a renvoyé aucune occurrence, et le fichier contenant le jeton de session de test a été supprimé avec rm -P.

Les deux contrôles étaient sincères, et aucun ne portait sur le bon objet. La recherche couvrait le dossier de la mission, alors que le secret se trouvait dans le transcript, hors de ce dossier. Quant à rm -P, censé écraser le fichier avant de le supprimer, la page de manuel de macOS indique que l'option n'a plus aucun effet : elle n'est conservée que pour compatibilité. Le fichier a été supprimé normalement, sans écrasement.

Sur un disque SSD, l'écrasement d'un fichier ne garantit de toute façon pas l'effacement des données. Le contrôle qui compte pour un jeton de session se situe côté serveur : le jeton est invalidé, ou il expire. Supprimer sa copie locale ne change rien pour qui l'aurait déjà lue.

Le second angle mort : ce que l'agent ne voit pas

La même mission a mis en évidence une limite d'un autre ordre. Le sous-agent offensif travaillait avec des requêtes HTTP et sans navigateur. Il pouvait analyser les fichiers JavaScript servis par l'application, mais pas ce que l'application range dans le navigateur une fois exécutée : localStorage, sessionStorage, IndexedDB, variables globales, messages de la console. Un jeton laissé dans le stockage du navigateur lui aurait échappé.

Nous avons comblé cet angle mort par une passe distincte, menée depuis la session principale avec un navigateur piloté : connexion par le formulaire, lecture du stockage, lecture de la console. Le résultat était propre. Ce qui compte ici est que le rapport du sous-agent ne mentionnait pas cette limite d'emblée. Un rapport qui ne dit pas ce que l'outil ne pouvait pas voir laisse croire que la question a été couverte.

Le premier rapport de la mission, non authentifié, portait une mention honnête : « pas un test d'intrusion complet ». C'est le format que nous attendons de toute déclaration de couverture : dire ce qui a été testé, et nommer ce qui ne l'a pas été.

Les contrôles

Ne jamais faire passer la valeur par le contexte. Le mot de passe ne doit apparaître ni dans un argument de commande, ni dans un brief de délégation. On transmet une référence : un chemin vers un fichier lisible uniquement pendant la session, ou une variable d'environnement. La référence ne protège que si l'agent l'utilise sans jamais en afficher le contenu. Une commande comme curl -u "test:$(cat /chemin/secret)" laisse la référence dans le transcript ; un cat /chemin/secret seul y écrit la valeur.

Un compte jetable, révoqué à la clôture. Le compte de test est créé pour la mission, avec les seuls droits qu'elle exige, et désactivé ou renouvelé à la fin. Si une copie du secret a survécu quelque part, elle ne donne plus accès à rien.

Chercher le secret dans les transcripts après la mission. La recherche doit être lancée depuis votre propre terminal, pas par l'agent : demander à l'agent de chercher le secret, c'est le lui donner, et donc l'écrire une fois de plus. Dans un shell bash, sans que le secret apparaisse dans l'historique ni dans la ligne de commande :

read -rs SECRET
[ -n "$SECRET" ] && grep -rlF -f <(printf '%s\n' "$SECRET") ~/.claude/projects/
unset SECRET

La commande liste les fichiers qui contiennent le secret. Si elle en trouve, renouvelez d'abord le secret, puis supprimez ou expurgez les fichiers concernés. L'ordre compte : un fichier supprimé a pu être sauvegardé ou synchronisé avant.

Révoquer côté serveur, pas seulement en local. Les jetons de session émis pendant les tests sont invalidés par une déconnexion ou une révocation, et la clôture le vérifie.

Une déclaration de couverture qui nomme les angles morts. Chaque rapport dit avec quels outils il a été produit et ce que ces outils ne pouvaient pas observer. Une passe navigateur est prévue dès qu'une application stocke des données côté client.

Limites

  • Nous décrivons notre outillage. Nous n'avons pas vérifié comment les autres environnements d'agents enregistrent leurs sessions ; le mécanisme général s'applique dès qu'un agent conserve son contexte, mais les emplacements diffèrent.
  • Le transcript est stocké en JSON. Un secret qui contient un guillemet, une barre oblique inversée ou certains caractères non ASCII y est échappé, et la recherche littérale ne le trouve pas. Cherchez aussi la forme échappée.
  • La recherche ne couvre que les fichiers locaux. Elle ne dit rien d'une sauvegarde déjà faite, d'un dossier synchronisé, ni de ce que le fournisseur du modèle conserve selon ses propres conditions.
  • La passe navigateur couvre le stockage et la console de la session observée. Elle ne prouve pas l'absence de fuite sur des parcours qu'elle n'a pas suivis.

Check-list

  1. Ne passez jamais un secret en argument de commande ni dans un brief de délégation ; transmettez une référence.
  2. Vérifiez que l'agent utilise la référence sans en afficher le contenu.
  3. Utilisez un compte de test dédié, aux droits minimaux, révoqué à la clôture.
  4. Cherchez le secret dans les transcripts depuis votre propre terminal, jamais par l'agent.
  5. Si vous le trouvez, renouvelez d'abord, nettoyez ensuite.
  6. Invalidez les jetons de session côté serveur ; ne comptez pas sur la suppression d'un fichier.
  7. Exigez de chaque rapport qu'il nomme ce que ses outils ne pouvaient pas voir.

eHVB-IT est un cabinet français de cybersécurité (tri d'alertes SOC, sécurité applicative, ingénierie de détection). Contact : contact@ehvb-it.fr

← Toutes les notes de recherche