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

L'isolement a exécuté la commande

Le 10 août 2026, Manifold Security a publié une faille dans l'agent CLI de Cursor : l'option qui démarre l'agent dans un worktree git isolé lisait un fichier JSON du dépôt que vous veniez de cloner et en passait le contenu directement à un shell — avant l'invite Workspace Trust, et hors du bac à sable même quand vous l'aviez activé. Aucun modèle, aucune injection de prompt, aucune persuasion. Bromure Agentic Coding livre la même primitive de worktree sans aucun endroit où un dépôt puisse placer une commande, dans une VM qui existait avant l'arrivée du dépôt.

Vous avez tapé l'option qui veut dire « isole ceci ». C'est cette option qui a exécuté la commande shell du dépôt, en votre nom, sur votre machine, avant que quoi que ce soit vous demande si vous faisiez confiance à ce dépôt.

Le 10 août 2026, Francisco Rosales, de Manifold Security, a publié une découverte dans l'agent de code en ligne de commande de Cursor, que The Hacker News a reprise trois jours plus tard dans son récapitulatif ThreatsDay. La preuve de concept ouvre une calculatrice. C'est la version polie d'une liste que Manifold détaille : lire ~/.ssh, prendre les identifiants cloud dans l'environnement, ouvrir un reverse shell, écrire une persistance.

Lisez-la pour la livraison plutôt que pour la charge utile. Personne n'a rien injecté, personne n'a persuadé un modèle, aucune phrase habile ne se cachait dans un README. Un fichier JSON versionné dans un dépôt a nommé une commande, et la partie de l'agent dont le métier même est l'isolement l'a exécutée.

Une option, un fichier JSON, et le mauvais ordre

cursor-agent est la version terminal de l'agent de code de Cursor. Lâcher un agent sur votre arbre de travail vous rend nerveux, alors la CLI propose un confinement, documenté dans sa propre référence des paramètres :

-w, --worktree [name]    Start in an isolated git worktree at
     ~/.cursor/worktrees/<reponame>/<name>

Un worktree tout neuf est un checkout propre : il n'a donc aucun de vos artefacts de build. Pour le rendre utilisable, la CLI exécute une étape d'installation lorsqu'elle en crée un, par défaut. Cette étape lit .cursor/worktrees.json dans le dépôt qu'elle vient de récupérer et passe la valeur setup-worktree directement à sh -c :

{
  "setup-worktree": "<any shell command>"
}

La description que fait Manifold de ce qui se tenait entre cette valeur et le shell tient en trois mots : « Aucun parsing, aucune liste blanche, aucune invite. »

.cursor/worktrees.json est un fichier versionné ordinaire. Il arrive avec un simple git clone, comme un README ou un fichier de lock. Sur les builds antérieurs à 2026.07.23-e383d2b, la création du worktree et son installation se déroulaient pendant la résolution de l'espace de travail — et cette résolution se terminait avant le chemin de code qui affiche la boîte de dialogue Workspace Trust. La commande passait donc en premier. Dans l'enregistrement joint au rapport de Manifold, le terminal affiche encore Running worktree setup commands…, la calculatrice s'ouvre, et c'est seulement ensuite qu'apparaît l'invite vous demandant si vous faites confiance à ce répertoire.

Cursor est explicite sur la raison d'être de cette invite : rien de ce que contrôle le dépôt ne doit s'exécuter avant que vous l'acceptiez. Quand quelque chose le fait, l'industrie a un nom pour cette classe de bug — l'exécution avant confiance — et Cursor a déjà livré un correctif du même genre, dans le même répertoire. En 2025, un .cursor/mcp.json fourni par le dépôt démarrait automatiquement le serveur qu'il configurait dès l'ouverture du projet. C'est devenu CVE-2025-64109, classée High à 8,8 en CVSS. Un problème de traitement trop permissif de .cursor/cli.json dans la même CLI est devenu CVE-2025-61592, également High, également 8,8. Le chemin worktree -w n'existait pas encore. Il est arrivé cinq mois après ce correctif, avec la même primitive et sans aucune barrière.

Manifold a signalé la faille le 20 juillet. Cursor a livré la réorganisation le 23 juillet : l'invite de confiance s'affiche désormais en premier, et la commande d'installation attend que l'espace de travail soit approuvé. Le 29 juillet, Cursor a clos le rapport en Informative, au motif que l'exploitation exige que l'utilisateur clone un dépôt contrôlé par l'attaquant. La réponse de Manifold est celle qu'il faut retenir. Cloner un dépôt, c'est la raison d'être du produit, et c'était aussi la condition préalable de CVE-2025-64109, le bug que Cursor a noté 8,8 et corrigé. Le clonage décrit la livraison. Le défaut est ailleurs.

Avant 2026.07.23-e383d2b — cursor-agent -w demogit clone.cursor/worktrees.jsonarrive, versionnérésolution de l'espaceworktree créé, l'étaped'installation lit le fichiersh -c "<leur commande>"votre compte, votre env,sandbox : insecure_noneInviteWorkspace Trust« confiance ? »L'invite s'affichait après que la commande du dépôt avait déjà tourné.Après le correctif de juillet — réordonnéInviteWorkspace Trusten premier, désormaisvous validez [a] Trustpour un dépôt clonéjustement pour le liresh -c "<leur commande>"toujours votre compte, votre env,toujours insecure_none — --sandbox enabled n'y va pasUn autre ordre pour les trois mêmes choses, toutes décidées par le processus qu'elles devaient contraindre.
Trois contrôles, tous à l'intérieur du processus qu'ils devaient contraindre. Sur les builds antérieurs à 2026.07.23-e383d2b, la commande d'installation du worktree s'exécutait pendant la résolution de l'espace de travail — avant l'invite de confiance — et sous une politique de bac à sable figée sur insecure_none, que --sandbox enabled ne change pas. Le correctif de juillet a déplacé la commande derrière la barrière de confiance. C'est toujours le même shell, avec la même portée dans le même compte utilisateur.

Le réglage que vous aviez activé n'atteignait pas ce chemin

Il y a un second volet, et il a survécu au correctif.

L'étape d'installation s'exécute sous une politique de bac à sable insecure_none codée en dur. C'est le nom que Cursor donne lui-même à cette valeur : dans sandbox.json, type accepte workspace_readwrite (la valeur par défaut), workspace_readonly ou insecure_none, qui désactive complètement le bac à sable. Le chemin d'installation du worktree est figé sur la dernière. Passer --sandbox enabled n'y change rien. Manifold, à propos des builds actuels : mettre à jour « ferme la fenêtre d'exécution avant confiance, pas la brèche du bac à sable ».

Mettez les deux volets côte à côte et vous obtenez quelque chose qui survivra à ce produit-là. La barrière de confiance était une étape dans une séquence, elle pouvait donc atterrir au mauvais endroit. Le bac à sable était un réglage, un chemin de code pouvait donc s'en exempter. Le worktree portait le mot isolement, les utilisateurs y ont donc lu du confinement. C'est le code de Cursor qui a décidé des trois, à des moments qu'il choisissait lui-même, à l'intérieur du processus qu'ils étaient censés contraindre. C'est la même faillite qui a eu raison du drapeau de sécurité d'une bibliothèque il y a deux semaines, et celle qui se cache derrière sept évasions de bac à sable où rien n'a échappé à un bac à sable.

Un isolement qui partage votre compte utilisateur

-w isole l'arbre de travail. Il donne à l'agent son propre checkout pour qu'il ne piétine pas votre travail non commité, ce qui est un vrai problème et un vrai remède. Personne ne l'a conçu pour isoler la machine. Le worktree vit dans ~/.cursor/worktrees/, sous votre compte, avec votre environnement, votre ~/.ssh, votre profil de shell, vos identifiants cloud et votre trousseau à un appel système de distance. La liste dressée par Manifold de ce que la commande d'installation aurait pu faire se lit comme l'inventaire de ce répertoire personnel.

Donc : un dépôt que vous n'aviez pas encore lu a choisi une commande, et la fonctionnalité que vous aviez invoquée pour votre sécurité est celle qui l'a exécutée. Un fichier JSON et un shell, le modèle restant sur le banc de touche.

L'essentiel de la conversation sur la sécurité des agents a dérivé vers le modèle. Est-il crédule, peut-on le persuader, a-t-il lu quelque chose qu'il n'aurait pas dû. Ces questions comptent et nous y consacrons beaucoup d'articles. Pendant ce temps, la façon la plus sûre d'exécuter du code sur le portable d'un développeur en 2026 reste de le mettre dans un fichier qu'un outil lit au démarrage, et d'attendre que quelqu'un clone.

Un worktree sans aucun endroit où mettre une commande

Bromure Agentic Coding livre la même primitive, parce que la primitive est bonne. Appuyez sur ⇧⌘G dans un onglet de dépôt, tapez un nom de tâche, choisissez un agent, et Bromure coupe une branche wt/<slug> depuis le commit courant, la checkout sous ~/.bromure/worktrees/<repo>/<slug>, ouvre un onglet dedans et lance l'agent sur votre prompt. Plusieurs tâches, plusieurs branches, plusieurs agents, un seul dépôt — le motif de la flotte.

L'étape d'installation de Bromure a le même travail que celle de Cursor : un checkout propre n'a pas les fichiers gitignorés dont un agent a besoin, il faut donc bien que quelque chose les apporte. La différence tient à ce que le dépôt a le droit d'en dire. Le dépôt peut livrer un fichier .worktreeinclude, et chaque ligne non commentée de ce fichier est un chemin relatif à la racine du worktree principal. Bromure copie chacun d'eux avec cp -a, et seulement si la source existe et si la destination n'existe pas. Aucun champ de ce fichier ne contient de commande : il n'y a donc pas de sh -c à ordonner correctement et pas de question d'ordonnancement à rater. Le pire qu'un .worktreeinclude hostile puisse demander, c'est qu'un fichier soit copié dans le checkout. Voilà tout le vocabulaire.

La plus grande différence, c'est l'endroit où tout cela se passe.

Worktree dans votre comptesetup-worktree → sh -cchoisie par le dépôt, exécutée en votre nomce qui est à un répertoire de là~/.ssh/id_ed25519AWS_SECRET_ACCESS_KEY, ANTHROPIC_API_KEY~/.git-credentials, ~/.kube/config~/.zshrc — persistancesortant : une connexion ordinaire depuisun outil de développement ordinaireArbre de travail isolé, machine partagée.Worktree dans un profil Bromure.worktreeinclude → cp -ades chemins seulement — aucun champ de commandeVM Linux jetable · créée avant le clonepas de clé privée ici — ssh-agent hôte via vsockidentifiants d'env leurrés (brm_…, faux kubeconfig)la persistance expire avec la tâchesortant : scanné au proxy de l'hôte —un leurre hors de sa portée → appel avorté,451 au guest, VM en pause, alerte rougeDeux axes : le worktree isole l'arbre, l'hyperviseur isole la machine.
Le même dépôt, la même étape d'installation, deux machines différentes. À gauche, le worktree est un répertoire dans votre compte : ce qui s'y exécute atteint vos clés, vos jetons et votre profil de shell. À droite, le worktree vit dans une VM jetable créée avant que le dépôt ne soit cloné — les clés privées n'y sont jamais entrées, les identifiants à portée sont des leurres, et la requête sortante qui en emporte un est avortée au proxy de l'hôte avant que la destination n'en voie un seul octet.

Exécutez la commande. Voyez ce qu'elle trouve.

Prenez la liste de Manifold et faites-la traverser un profil, en accordant tout à l'attaquant — avant confiance, sans bac à sable, root dans le guest si vous voulez.

Lire ~/.ssh. Rien à y lire. Les clés privées d'un profil restent sur l'hôte. La VM reçoit un SSH_AUTH_SOCK pointant vers /tmp/bromure-agent.sock, relayé par vsock vers un agent par profil qui tourne sur votre Mac, et Bromure laisse volontairement votre agent launchd macOS non raccordé. Le protocole de l'agent a des requêtes pour « liste mes clés publiques » et « signe ce défi ». Il n'a aucune requête qui signifie « donne-moi la clé privée ». Activez Require approval to use et chaque signature devient une boîte de dialogue sur l'hôte assortie d'une autorisation à durée limitée — cinq minutes, une heure, le reste de la session.

Prendre les identifiants cloud dans l'environnement. Ils sont là, ils ont l'air justes, et ce sont des faux. Tout ce qui figure dans le panneau Credentials d'un profil est injecté sous forme d'espace réservé puis échangé contre la vraie valeur sur le fil par le proxy de l'hôte : brm_… pour les clés d'API génériques, un ~/.kube/config synthétique avec des certificats client jetables, un faux blob base64 dans ~/.docker/config.json, ~/.git-credentials, du matériel AWS re-signé côté hôte et qui répond InvalidSignatureException à quiconque tente de contourner le proxy. Le vol réussit et ne rapporte rien qui vaille la peine.

Ouvrir un reverse shell. L'attaquant doit maintenant faire passer quelque chose sur un fil, et le fil appartient à l'hôte. Chaque requête sortante de la VM est confrontée aux leurres frappés pour le profil par un automate d'Aho-Corasick — en-têtes et corps, la requête entière, et pas seulement les parties qui ressemblent à des identifiants. Un leurre en route vers un hôte extérieur à la portée pour laquelle il a été frappé, c'est la signature d'une machine qui exfiltre quelque chose qu'elle ne devrait même pas connaître. Le proxy avorte l'appel en amont avant que la destination n'en voie un seul octet, renvoie un 451 au guest, met la VM en pause, teinte l'image figée en rouge et lève une alerte nommant l'identifiant, l'hôte pour lequel il avait été frappé et l'hôte vers lequel il est parti à la place. À vous de choisir : arrêter, conserver pour investigation — image disque, répertoire personnel, dossiers partagés, empaquetés et marqués de sorte que le profil ne redémarrera plus sans effacement — ou continuer.

Écrire une persistance. La durée de vie de la machine, c'est une tâche. Erase home réinitialise /home/ubuntu, et Reset to base reclone le disque système du workspace.

Pendant ce temps, le travail que vous vouliez avance. L'agent relit le dépôt, lance les tests, ouvre la pull request. La commande du dépôt s'est exécutée, dans une pièce où s'exécuter était tout ce à quoi elle avait droit.

La partie que le patch ne couvre pas

Un dernier détail du compte rendu de Manifold, et c'est celui qui a la plus longue durée de conservation. Cursor n'a publié aucun avis de sécurité pour le chemin worktree et l'a omis du changelog de juillet : le correctif est donc arrivé à l'intérieur d'un build de routine. Quiconque était sur une version affectée n'avait aucun moyen d'apprendre que mettre à jour fermait un chemin d'exécution avant confiance. Vous n'avez aujourd'hui aucun moyen de savoir quel outil de votre portable en a encore un d'ouvert, parce que ce chemin, c'est toujours un fichier qu'un outil lit au démarrage et que personne n'a encore audité.

Cursor a fermé celui-ci en trois jours, ce qui est rapide. Cursor avait aussi fermé la version mcp.json en 2025, et une nouvelle fonctionnalité est arrivée cinq mois plus tard avec la même primitive. Voilà à quoi ressemble le fait d'ajouter des fonctionnalités à une séquence de démarrage : chaque capacité ajoute une étape, et chaque étape est une chose de plus à ordonner correctement un jeudi.

Un hyperviseur n'est pas une étape de cette séquence. Il ne lit pas .cursor/worktrees.json, ni .worktreeinclude, ni quoi que ce soit d'autre dans votre dépôt. Il était là avant le clone, il n'a pas de champ de politique qu'un chemin de code puisse figer sur insecure_none, et sa garantie — cette machine est jetable, ces identifiants sont faux, ce fil est surveillé — est la même le jour où un contournement est publié que le jour où il est corrigé.

Continuez à cloner des dépôts que vous n'avez pas lus. Relire du code inconnu, c'est le métier, et le confier à un agent, c'est tout l'intérêt d'en avoir un. Cessez simplement de laisser cette relecture se dérouler dans le même compte que vos clés SSH. Installez Bromure Agentic Coding, donnez à chaque tâche sa branche, son checkout et sa machine jetable, et la prochaine fois qu'un chemin de démarrage s'avérera exécuter tout ce qu'un fichier JSON raconte — et ça arrivera — il s'exécutera dans une pièce bâtie pour lui.