← Retour à tous les articles
Publié le · par Renaud Deraison

La règle d'autorisation a gardé le mot de passe

Le 6 novembre, Apiiro a montré que le bouton « Yes, and don't ask again » de Claude Code enregistre la commande approuvée, secrets compris, dans .claude/settings.local.json, et que 1 122 dépôts GitHub publics avaient commité ce fichier avec des mots de passe, jetons et clés d'apparence valide. Deux scanners de secrets répandus en ont manqué 215. Dans Bromure Agentic Coding, chaque secret que l'agent peut taper est un substitut : une règle enregistrée contient une valeur qui ne marche nulle part, et chaque push attend votre réponse sur le Mac.

Vous cliquez sur « Yes, and don't ask again » pour ne plus revoir une demande. Claude Code enregistre la commande entière pour la reconnaître la prochaine fois, mot de passe compris, dans un fichier de votre projet.

Le 6 novembre, Apiiro a publié une étude sur les agents de code qui laissent fuiter des secrets via « don't ask again ». The Hacker News l'a reprise le lendemain matin.

Le mécanisme est banal. Quand Claude Code demande à lancer une commande shell et que vous choisissez Yes, and don't ask again, il écrit une règle d'autorisation dans .claude/settings.local.json, dans le projet. Parfois la règle est un motif comme npm test *. Parfois c'est la commande exacte, et quand cette commande portait un mot de passe après PGPASSWORD=, sshpass -p ou mysql -p, le mot de passe se retrouve dans un fichier JSON à côté de votre code.

Apiiro a cherché ce fichier sur GitHub public et a trouvé :

  • 104 185 copies dans 103 617 dépôts ;
  • 1 122 copies contenant au moins un secret, 1 379 secrets distincts en tout ;
  • 379 mots de passe de bases de données, 43 identifiants SSH de serveurs publics, 42 jetons GitHub, GitLab et npm, et 23 clés AWS ;
  • des secrets appartenant à 63 organisations, dont une clé cloud de niveau administrateur chez un éditeur de bases de données et le mot de passe propriétaire d'une base de dossiers médicaux.

Chacun de ces fichiers était encore sur la branche par défaut quand Apiiro a vérifié. Dans la moitié des dépôts, il y était depuis au moins 217 jours. Des commits plus anciens contenaient 734 secrets de plus dans 509 dépôts, et 211 forks de 74 dépôts en portaient des copies. En comptant le .claude/settings.json partagé, l'historique et les journaux de conversation d'agents sauvegardés, Apiiro arrive à 2 539 secrets dans 1 994 dépôts.

Un clic, quatre étapes jusqu'au secret publicDemande d'approbationPGPASSWORD=s3cr3t psql-h db.example.com …"Yes, and don't ask again"Règle enregistrée.claude/settings.local.jsonla commande entière, en clairCommité539 / 1 105dépôts tracés : un commitco-écrit par Claude l'a modifiéPublic sur GitHub1 122copies avec secrets ;la moitié depuis 217 j ou +Supprimer le fichier ne supprime pas le secret734 secrets de plus dans d'anciens commits de 509 dépôts ; 211 forks de 74 dépôts portent les mêmes valeurs
Comment un mot de passe passe d'une demande d'approbation à un dépôt public. La règle d'autorisation stocke la commande exacte. L'agent commite le dossier du projet, le fichier part avec lui, et chaque fork et chaque ancien commit en garde une copie une fois la branche par défaut nettoyée.

Pourquoi rien ne l'a arrêté

Claude Code essaie bien de garder le fichier hors de git. La première fois qu'il écrit settings.local.json, il l'ajoute à vos exclusions git globales. Cette règle vit sur un seul ordinateur. Le portable d'un collègue, un runner de CI ou un clone tout neuf ne l'ont pas, et elle ne fait rien pour un fichier déjà commité. Claude Code n'ajoute jamais le fichier au .gitignore du projet lui-même. Sur les 16 agents de code comparés par Apiiro, Claude Code est le seul dont il est confirmé qu'il enregistre par défaut la commande approuvée complète dans un fichier du projet.

Les scanners de secrets n'ont pas fait mieux. Apiiro a passé gitleaks et TruffleHog sur chaque copie. Gitleaks a signalé 564 des 1 122 fichiers, TruffleHog 643, et 215 ont échappé aux deux. La plupart de ces 215 étaient des mots de passe tapés dans des commandes. Les scanners cherchent des formats de clés connus comme AKIA… ou ghp_…, et sshpass -p hunter2 ne correspond à aucun d'eux.

Des utilisateurs ont signalé le problème il y a plus d'un an. Un ticket d'août 2025, anthropics/claude-code#5544, indiquait que Claude Code stockait mot pour mot dans ce fichier des commandes approuvées contenant des clés d'API. Un bot l'a fermé comme « not planned » après 60 jours sans activité. Quatre signalements apparentés ont connu le même bot, le dernier la veille de l'article de The Hacker News.

Le secret était dans la commande parce qu'il était entre les mains de l'agent

Chacun de ces 1 379 secrets était une valeur que l'agent pouvait taper. Un développeur l'avait collée dans la conversation, ou laissée dans un fichier .env, ou l'agent l'avait lue dans ~/.aws/credentials ou ~/.pgpass et insérée en clair dans la commande qu'il voulait lancer. La règle d'autorisation a noté ce que l'agent avait déjà.

Le conseil d'Apiiro pour l'avenir va dans le même sens : garder les secrets hors des commandes, et référencer une variable issue d'un gestionnaire de secrets pour que la règle enregistrée ne contienne que $DB_URL. Cela aide si chaque développeur et chaque agent s'y tient à chaque fois. Un agent qui cherche à faire marcher une commande en échec développera la variable. Bromure prend l'autre chemin et garde la vraie valeur hors de l'environnement de l'agent, tout simplement.

Le même clic, dans Bromure Agentic Coding

Bromure Agentic Coding fait tourner Claude Code, Codex, Grok, Kimi et omp dans des VM Ubuntu sur votre Mac. L'agent reçoit ses identifiants à travers ce que nous appelons la frontière du fil : dans la VM, chaque secret est un substitut, et la vraie valeur est chiffrée sur le Mac, avec sa clé maîtresse dans le trousseau macOS. Un proxy sur le Mac remplace le substitut par la vraie valeur au passage sur le réseau, et uniquement sur une requête vers l'hôte auquel cet identifiant appartient.

Donc quand l'agent demande à lancer une commande contenant un jeton et que vous choisissez Yes, and don't ask again, Claude Code enregistre la commande exactement comme avant. Le jeton qu'elle contient est le substitut.

Chaque secret que l'agent peut taper est un substitut

Les substituts gardent la forme qu'attendent les outils, rien ne change donc dans le projet :

Ce qu'Apiiro a trouvé en fuiteCe que l'agent détient dans une machine Bromure
Jetons GitHub et GitLabghp_ + 36 caractères, glpat- + 20, dans GH_TOKEN, GITLAB_TOKEN et ~/.git-credentials
Jetons npmune valeur brm_… dans NPM_TOKEN, issue d'une entrée Other API key limitée à registry.npmjs.org
Clés AWSle vrai identifiant de clé d'accès avec une fausse clé secrète ; le Mac re-signe chaque requête avec la vraie
Mots de passe de bases de donnéesune valeur brm-db-… dans la variable de votre choix, pour ClickHouse, la Mongo Data API et Elasticsearch
Identifiants SSHaucun mot de passe : des signatures faites par des clés qui restent sur le Mac

Prenons une commande qu'un agent pourrait lancer contre un point d'accès ClickHouse :

curl -u default:brm-db-7Qk2…Xw9 \
  'https://analytics.example.com:8443/?query=SELECT+count()+FROM+events'

Approuvez-la pour toujours et la règle d'autorisation contient brm-db-7Qk2…Xw9. Le proxy remplace le substitut sur les requêtes vers analytics.example.com, dans l'en-tête Basic-auth et dans le corps de la requête, car les secrets de bases de données voyagent souvent dans du SQL et du JSON. Partout ailleurs, la chaîne ne sert à rien.

Pour AWS, le secret n'atteint jamais la VM. Le ~/.aws/config de l'agent pointe vers un utilitaire qui fournit le vrai identifiant de clé d'accès avec une fausse clé secrète de 40 caractères. L'AWS CLI, boto3 et terraform signent avec la fausse, et le Mac retire cette signature et re-signe avec le vrai secret avant que la requête ne parte. La vraie clé secrète n'existe jamais dans la VM, donc aucune commande écrite par l'agent ne peut la contenir.

SSH couvre les 43 identifiants de serveurs du décompte d'Apiiro. Le SSH_AUTH_SOCK d'une machine Bromure est une passerelle vers le Mac : la VM peut demander des signatures, et les octets de la clé privée ne traversent jamais. Les clés protégées par une YubiKey, Secretive ou 1Password peuvent être proposées une par une, avec la demande de toucher sur votre Mac. L'agent n'a jamais besoin de sshpass -p, il n'y a donc aucun mot de passe qu'une règle d'autorisation puisse enregistrer.

VM de la machineCommande approuvéeGH_TOKEN=ghp_Xk3…a7Q gh api …"Yes, and don't ask again"settings.local.jsoncontient le substitut, qui nemarche que via le proxy du Macgit pushle commit inclut le fichierVotre MacLe proxy remplace sur le filsubstitut → vrai jeton, seulementvers github.comVraies valeurs, sous trousseaujetons, secrets AWS, clés SSH :jamais dans la VM, sous aucune formePrompt before writele push attend votre réponseInternetapi.github.comreçoit le vrai jeton, lacommande marche comme d'habitudeQuelqu'un copie le fichierd'un dépôt, d'un fork ou d'unvieux commit et essaie le jeton :401 Bad credentials
Le même clic dans une machine Bromure. La règle d'autorisation stocke toujours la commande, mais le jeton qu'elle contient est un substitut. Le push qui le publierait attend une boîte de dialogue sur le Mac, et un substitut copié depuis GitHub échoue partout, car seul le proxy de votre Mac peut le transformer en vraie valeur.

Un substitut sur GitHub n'ouvre rien

Imaginons que le fichier atteigne bel et bien un dépôt public, un fork et une dizaine d'anciens commits. Celui qui le récupère obtient ghp_ suivi de 36 caractères que GitHub n'a jamais émis. Envoyé depuis son portable, il échoue à l'authentification. Seul le proxy de votre Mac sait à quel vrai jeton il correspond, et ce proxy ne sert que la VM de votre machine.

Si un substitut part un jour vers le mauvais hôte depuis l'intérieur de la machine, le proxy le traite comme une tentative d'exfiltration. Il bloque la requête avec un HTTP 451, met la VM en pause et ouvre une alerte intitulée This environment may have been compromised qui nomme l'identifiant, l'hôte auquel il appartient et l'hôte vers lequel il a été envoyé. Chaque substitut est ainsi un fil-piège autant qu'un espace réservé.

La rotation se fait en un seul endroit

La première consigne d'Apiiro est de faire tourner chaque mot de passe, jeton et clé que le fichier a jamais contenu, avant de nettoyer quoi que ce soit. Avec de vrais secrets éparpillés entre fichiers .env, historique du shell et configuration de l'agent, c'est une chasse au trésor. Dans Bromure, les vraies valeurs sont à un seul endroit : le panneau Credentials de la machine, sur le Mac. Collez-y le nouveau jeton et le substitut dans la VM reste le même, si bien que les règles d'autorisation, les scripts et les configurations de la machine continuent de fonctionner.

Une Other API key peut aussi être une référence 1Password comme op://Engineering/npm/token. Bromure la résout sur le Mac et la résout de nouveau toutes les 2 minutes : une clé changée dans 1Password prend effet en quelques minutes, sans redémarrage et sans rien à modifier dans la VM.

Déplacer les secrets dans ce panneau prend une étape. Import env file… lit un .env ou un ~/.bashrc existant, affiche les variables avec leurs valeurs masquées et les transforme en identifiants. Le dossier du projet n'a plus besoin du fichier .env dans lequel l'agent lisait les mots de passe.

Le push vous attend

Dans 539 des dépôts tracés par Apiiro, un commit créditant Claude comme co-auteur a modifié le fichier. Les agents commitent et poussent quand ils terminent une tâche.

Sur les nouvelles machines Bromure, chaque service de forge démarre en Prompt before write dans le panneau Guardrails. Les lectures passent. Un git push en HTTPS attend une boîte de dialogue sur le Mac qui montre l'opération, et rien ne part avant votre réponse. La politique s'applique dans le proxy, donc rien dans la VM ne peut la désactiver.

Un relecteur qui lit la ligne

Les 215 secrets qu'aucun scanner n'a repérés étaient des mots de passe dans des commandes. Un modèle qui lit PGPASSWORD=… psql -h db.example.com dans un fichier JSON commité sait ce qu'il a sous les yeux.

Une code review Bromure surveille un dépôt GitHub avec un agent sur l'une de vos machines, sur Every new commit, Every new pull request ou un calendrier hebdomadaire, et Bromure dédoublonne ses résultats d'un scan à l'autre. Pour l'étape d'Apiiro « cherchez dans votre organisation, historique compris », Run Baseline Now lance une revue ponctuelle de tout le dépôt. GitHub est interrogé depuis le Mac, et l'agent de revue travaille avec les mêmes substituts que n'importe quel autre.

La transcription contient aussi le substitut

Le décompte d'Apiiro incluait des journaux de conversation d'agents sauvegardés. Les fichiers de session de Claude Code dans la VM enregistrent ce que l'agent a vu, et dans une machine Bromure c'est le substitut. Les traces de Bromure sur le Mac conservent les remplacements de jetons sous forme de courts aperçus comme sk-a…f9q3, et les octets du vrai secret n'entrent jamais dans un enregistrement de trace. Quand un secret d'apparence réelle apparaît malgré tout dans le trafic, parce que quelqu'un l'a collé à la main dans la VM, la trace le signale comme fuite potentielle, et bromure-cli trace leaks les liste tous. Activez Only allow credentials Bromure injects dans Guardrails, et une requête portant un identifiant que Bromure n'y a pas mis est bloquée et déclenche l'alerte de compromission.

Des substituts dans chaque commande

Jetons, secrets de bases de données et clés AWS sont des substituts dans la VM. Une règle d'autorisation enregistre une valeur qui ne marche qu'à travers votre Mac.

Pas de mot de passe pour SSH

La VM demande des signatures au Mac. La clé privée ne traverse jamais, et sshpass -p n'est jamais nécessaire.

Les push attendent sur le Mac

Prompt before write est le réglage par défaut des nouvelles machines. Un push attend votre réponse, et c'est le proxy qui l'impose.

Une seule rotation

Les vraies valeurs vivent dans un seul panneau, ou dans 1Password. Changez-les là et chaque substitut continue de fonctionner.

Que faire cette semaine

D'après la liste d'Apiiro, pour toute équipe qui utilise des agents de code :

  • Cherchez .claude/settings.local.json et .claude/settings.json dans votre organisation GitHub, historique compris.
  • Faites tourner chaque secret que ces fichiers ont jamais contenu, puis nettoyez.
  • Lancez git rm --cached .claude/settings.local.json et ajoutez le fichier au .gitignore du projet et aux modèles de votre organisation.
  • Traitez .claude/, .cursor/, .gemini/ et .specstory/ comme des fichiers .env dans les scans pre-commit et de CI, et vérifiez que le scanner repère les mots de passe dans les commandes.

Ensuite, mettez les vrais secrets hors de portée de l'agent. Dans une machine Bromure, le prochain « don't ask again » enregistre un substitut, que le développeur se soit souvenu ou non des règles d'Apiiro ce jour-là.

Pour le schéma plus large des agents qui recopient des secrets dans l'historique et les journaux, voir Le jeton est resté dans la transcription. Pour le fonctionnement détaillé du remplacement, lisez le chapitre Credentials du manuel.