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.
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 fuite | Ce que l'agent détient dans une machine Bromure |
|---|---|
| Jetons GitHub et GitLab | ghp_ + 36 caractères, glpat- + 20, dans GH_TOKEN, GITLAB_TOKEN et ~/.git-credentials |
| Jetons npm | une valeur brm_… dans NPM_TOKEN, issue d'une entrée Other API key limitée à registry.npmjs.org |
| Clés AWS | le 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ées | une valeur brm-db-… dans la variable de votre choix, pour ClickHouse, la Mongo Data API et Elasticsearch |
| Identifiants SSH | aucun 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.
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.jsonet.claude/settings.jsondans votre organisation GitHub, historique compris. - Faites tourner chaque secret que ces fichiers ont jamais contenu, puis nettoyez.
- Lancez
git rm --cached .claude/settings.local.jsonet ajoutez le fichier au.gitignoredu projet et aux modèles de votre organisation. - Traitez
.claude/,.cursor/,.gemini/et.specstory/comme des fichiers.envdans 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.