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

L'interrupteur était à l'intérieur du bac à sable

La CVE-2026-82533, publiée le 8 septembre, est un 9,4 dans DeepSeek Harness : l'agent de codage confiné pouvait éteindre son propre bac à sable avec une seule commande shell. Le harnais exécutait les commandes de l'agent dans un bac à sable système qui limitait les écritures de fichiers, et il exposait son plan de contrôle sous la forme d'une interface HTTP sur la boucle locale, que le bac à sable ne couvrait pas. Le contrôle qui gardait cette interface lisait l'en-tête Host fourni par l'appelant et ne regardait jamais d'où venait la connexion. Dans Bromure Agentic Coding, l'agent est déjà sans confinement à l'intérieur de la VM, et le plan de contrôle qu'il devrait appeler n'existe pas de son côté de la ligne.

Celle-ci n'a demandé aucun exploit. L'agent confiné a lancé une commande shell vers un port local, a demandé au bac à sable qui le tenait de s'éteindre lui-même, et le bac à sable a accepté.

DeepSeek Harness exécute les commandes d'un agent de codage à l'intérieur d'un bac à sable du système d'exploitation. Ce bac à sable est étroit et assumé : les écritures de fichiers restent dans l'espace de travail et les dossiers temporaires, et tout le reste de ce que fait l'agent est le problème de quelqu'un d'autre. La référence en ligne de commande du projet le dit elle-même. Avec les réglages par défaut, « les lectures et l'accès réseau ne sont pas confinés ».

Le harnais a aussi une interface web. Elle tourne sur la même machine, sur un port HTTP local, et le harnais en donne l'adresse au shell de l'agent pour que les outils puissent lui parler. Cette interface est le plan de contrôle : elle détient le mode d'une session, y compris le mode nommé danger-full-access, qui éteint le bac à sable et arrête les demandes d'approbation.

Ces deux choix de conception sont ordinaires. Mettez-les côte à côte et l'agent peut éteindre son propre bac à sable avec une seule commande, parce que le bac à sable n'a jamais couvert la boucle locale et que le plan de contrôle n'a jamais vérifié qui appelait.

Des chercheurs d'OX l'ont trouvée. VulnCheck l'a publiée le 8 septembre sous le nom de CVE-2026-82533, CVSS 9,4. Elle fonctionnait sur une installation par défaut, sans exposition réseau et sans identifiants, jusqu'à ce que DeepSeek publie un correctif le 27 août.

Le contrôle qui lisait l'enveloppe

L'interface n'avait aucune authentification. Ce qui en tenait lieu était une fonction nommée isTrustedApiRequest, et DevOps.com cite ce qu'elle faisait : elle « lisait l'en-tête de requête Host et accordait l'accès si la valeur était une autorité de boucle locale ou figurait dans une liste trustedHosts configurée ».

L'en-tête Host est une ligne de texte que le client écrit. Il dit quelle adresse le client croit contacter, et un appelant situé n'importe où sur le réseau peut y taper localhost. L'origine réelle de la connexion se trouve une couche plus bas, dans l'adresse du pair TCP, et le contrôle de DeepSeek n'a jamais lu cette valeur. La fiche CVE classe le bug sous CWE-345, Reliance on Untrusted Inputs in a Security Decision.

L'agent, assis dans un bac à sable qui autorisait les appels réseau, en a fait un vers l'interface et a réglé sa propre session sur danger-full-access avec l'approbation sur never. Comme The Hacker News le formule, le changement de mode « ne nécessitait pas d'approbation explicite parce qu'il modifiait des réglages de session plutôt que de demander de nouvelles permissions ».

Le système d'approbation a fonctionné comme prévu. L'agent n'a jamais demandé à faire quelque chose de dangereux ; il a demandé à changer un réglage, et ce réglage gouvernait justement le fait que les choses dangereuses donnent lieu à une question. Une barrière de permission inspecte l'opération qu'elle a devant elle. Cette requête était une écriture de configuration, et les écritures de configuration ne figuraient pas sur sa liste.

une machine, un processus, deux décisions qui ne se sont jamais croiséesbac à sable système : ce qu’il limiteles commandes shell de l’agent de codageécritures → workspace + tempimposé par le système, et il a tenuhors périmètre, par conceptionlectures : non confinéesréseau : non confiné, boucle inclusePOST http://127.0.0.1:…Host: localhostplan de contrôle : HTTP local, sans authisTrustedApiRequest(req)• lit l’en-tête Host de l’appelant• autorité loopback ou trustedHosts → ok• ne lit jamais l’adresse du pair TCPl’état de session obtenumode = danger-full-accessapproval = neverune écriture de réglage : rien ne demande
L'évasion, en entier. Le bac à sable système limite les écritures de fichiers à l'espace de travail et aux dossiers temporaires ; les lectures et les appels réseau, boucle locale comprise, sont hors de son périmètre par conception. Le plan de contrôle du harnais écoute sur un port HTTP local dont le shell de l'agent connaît déjà l'adresse. Son contrôle de confiance lit l'en-tête Host fourni par l'appelant et n'inspecte jamais l'adresse du pair TCP, si bien qu'une requête émise par le processus en bac à sable ressemble à celle du développeur. L'agent écrit alors le mode de sa session sur danger-full-access et son réglage d'approbation sur never. C'est une écriture de réglage plutôt qu'une demande de permission, donc rien ne s'affiche.

La même porte, dans l'autre sens

Une interface qui fait confiance à un en-tête fourni par l'appelant ne se soucie pas de la direction d'où arrive la connexion. Si le port était joignable depuis un réseau, le même appel fonctionnait depuis l'extérieur de la machine : un inconnu non authentifié pouvait prendre le contrôle de l'agent et exporter toutes les conversations stockées, sans clé d'API et sans dépenser un seul jeton en appel de modèle. C'est pourquoi le vecteur CVSS 4.0 s'ouvre sur AV:N, et pourquoi le score est 9,4 plutôt qu'une note locale et anodine.

La chronologie a un détail qui mérite d'être gardé. Deux développeurs ont publié la découverte sur le forum de discussion GitHub de DeepSeek les 13 et 14 août, de leur propre initiative, avant qu'OX ne la confirme le 24 août et avant qu'une CVE n'existe. Le dépôt comptait plus de 216 000 étoiles le 9 septembre et aucun fichier de politique de sécurité décrivant comment signaler une vulnérabilité en privé, si bien que le seul endroit où la mettre était un fil public. Le correctif, quand il est arrivé, a été l'authentification : l'outil imprime désormais un jeton à usage unique dans son adresse de démarrage, le navigateur échange ce jeton contre un cookie signé, et chaque appel à l'interface exige le cookie.

Si vous faites tourner ce harnais, vérifiez la version présente sur votre machine plutôt que celle que publie le projet. Des enveloppes de bureau tierces embarquent leur propre copie. 0.1.2-alpha.1 n'existait que sur GitHub le 27 août, 0.1.2-alpha.2 le 30 août a été la première version npm porteuse du correctif, et une enveloppe Windows est restée sur 0.1.1-rc.2 jusqu'à passer à une version corrigée le 6 septembre.

Votre harnais en a un, lui aussi

Vous pourriez classer ceci comme le bug d'un projet et passer à autre chose. La forme voyage, parce que les harnais d'agents font pousser des plans de contrôle locaux : un serveur d'état pour le tableau de bord, un pont vers l'IDE, un point d'entrée MCP, une page sur localhost qui affiche le diff. Chacun est un petit service HTTP sur votre machine, et le code que votre agent exécute peut les atteindre tous, parce que l'agent tourne sur cette machine et que localhost, c'est précisément ce que veut dire « cette machine ».

Dès qu'un plan de contrôle s'installe à côté de la chose qu'il contrôle, une seule question décide de l'issue : la partie confinée peut-elle adresser les réglages de son confinement ? DeepSeek a répondu oui, à travers un contrôle qui croyait l'appelant sur parole. Un contrôle plus solide répond quand même à une question qui n'aurait jamais dû pouvoir être posée. Une fonction d'authentification est du code, le code a des branches de repli, et cette année a été un long défilé de décisions de sécurité qui échouent dans la branche que personne n'avait exercée.

Où vit l'interrupteur dans un workspace Bromure

Bromure Agentic Coding part de l'autre bout. Un workspace Bromure laisse l'agent sans confinement.

À l'intérieur de l'invité, l'agent a déjà ce que DeepSeek Harness appellerait danger-full-access. C'est l'état de repos, par conception. Le manuel le dit lui-même : un agent victime d'injection de prompt ou se comportant mal « peut faire tout ce qu'il veut à l'intérieur de l'invité, mais il ne peut pas lire une vraie clé d'API, signer avec un vrai secret AWS, ni extraire une clé privée SSH — aucun de ces éléments n'existe de son côté de la ligne ». L'agent peut écrire n'importe où dans le système de fichiers, exécuter ce qu'il veut et ouvrir n'importe quel port : il ne lui reste aucun mode vers lequel s'élever. La frontière est un bord d'hyperviseur, avec l'agent de l'autre côté.

L'invité n'a donc aucune adresse pour le plan de contrôle. La politique du workspace (guardrails, identifiants, règles de pare-feu, serveurs MCP) vit dans un profile.json sous ~/Library/Application Support/BromureAC/profiles/ sur votre Mac, et vous la modifiez dans la fenêtre Edit workspace. L'invité n'a aucun chemin de fichier vers ce fichier et aucune route réseau vers le processus qui le lit.

Les deux côtés se parlent par des sockets virtio, des ponts point à point hôte-invité qui tournent en dehors du réseau de la VM. Une VM de workspace en a huit, chacun avec sa tâche : 8443 porte le HTTPS de l'invité vers le proxy de l'hôte, 8444 est le pont ssh-agent, 8445 alimente l'assistant d'identifiants AWS, 5800 sert le terminal et les chemins de fichiers, 5010 relaie les rappels OAuth. Chacun sert un protocole fixe et aucun n'accepte d'en-tête Host, si bien qu'un appelant n'a aucune liste trustedHosts dans laquelle s'inviter par la parole. Sur un vsock, l'identité est une topologie : le pont sur lequel les octets sont arrivés. Cela ne se tape pas dans une requête.

le harnais : le bac à sable et ses réglages, même machineagent en bac à sableécritures limitéesréseau non confinéconnaît l’adresse de l’interfaceHTTP en boucle localeen-tête fourni par l’appelantplan de contrôlemode de sessionréglage d’approbationà une requête de ce qu’il gouverneun workspace Bromure : tout dedans, la politique de l’autre côtéVM du workspacel’agent a un accès total iciécrire partout · tout exécuter · tout portaucun mode où s’éleverles plans de contrôle qu’il lance écoutentsur la boucle de cette VM, pas celle du Macaucun vrai secret de ce côtéhyperviseurvsock 8443HTTPS seulaucun contrôlehôte macOSproxy par workspace : la frontière du filguardrails · pare-feu de sortie · faux secrets:80 et :443 détournés par le switch virtuelrien que l’invité puisse désactiveroù la politique est stockée~/Library/Application Support/BromureAC/…les dialogues s’affichent ici, en AppKit
Deux emplacements du même interrupteur. En haut : le bac à sable du harnais et le plan de contrôle qui le gouverne tournent sur une seule machine, si bien que l'agent atteint les réglages par la boucle locale et que la seule chose qui les sépare est un contrôle sur un en-tête que l'agent écrit. En bas : un workspace Bromure accorde tout à l'agent à l'intérieur de l'invité et place la politique de l'autre côté de l'hyperviseur. Le switch virtuel détourne le HTTPS sortant de l'invité vers le proxy de l'hôte, sans variable d'environnement à supprimer ; les guardrails, le pare-feu de sortie et la substitution d'identifiants s'exécutent tous là ; et les dialogues d'approbation s'affichent sur macOS. L'invité n'a d'adresse pour aucun de ces éléments.

Des approbations qu'on ne peut pas régler sur never

La seconde moitié de danger-full-access éteignait les invites, et cette moitié mérite sa propre réponse. Un agent qui fait taire ses propres approbations, c'est la partie qui m'empêcherait de dormir.

Dans un workspace Bromure, les invites vivent sur l'hôte. Le proxy les lève après que la requête a déjà quitté la VM, si bien que la session à l'intérieur de l'invité ne porte aucun réglage capable d'en supprimer une. Demander avant usage met en pause la première utilisation d'un identifiant dans une session et propose des autorisations limitées dans le temps : cinq minutes, une heure, le reste de la session, ou Ne pas autoriser. Une politique d'écriture réglée sur Demander avant écriture arrête chaque mutation par un dialogue intitulé Allow write on "<scope>" from workspace "<name>"? qui montre l'opération littérale, l'instruction SQL elle-même ou METHOD /path pour un appel REST. Autoriser une fois ne crée aucune autorisation, si bien que l'écriture suivante redemande. Les autorisations vivent en mémoire et disparaissent à la fermeture de la fenêtre de session.

La page des guardrails énonce la propriété en une proposition : une politique d'écriture est « imposée dans le proxy, si bien qu'un agent compromis dans la VM ne peut pas la contourner ». Le pare-feu de sortie en dessous fonctionne de la même façon : une table de règles ordonnée avec un réglage Unmatched traffic que vous pouvez mettre sur Deny, appliquée deux fois — une fois au switch virtuel par IP de destination et nom d'hôte espionné dans le DNS, une fois au proxy par nom de serveur TLS et, pour les règles web, par méthode HTTP. Les modifications de règles atteignent les sessions en cours sans redémarrage.

Une exécution sans surveillance échoue du bon côté. Un workspace piloté sans interface par SSH ou par une automatisation reçoit les mêmes quatre choix sous forme d'invite texte dans son tmux, et l'absence de réponse vaut refus. Le pire résultat, là, est un appel bloqué et une ligne rouge dans la Security Timeline. Dans cette CVE, l'agent a réglé les approbations sur never et les invites ont disparu.

Si vous faites tourner un harnais d'agent sur votre Mac

Demandez quelles sont ses interfaces locales et qui peut les appeler. Dans un workspace Bromure, bromure-cli vm ports <workspace> imprime les sockets en écoute vivants de l'invité, avec port, protocole, adresse et processus, et les liaisons limitées à la boucle locale signalées. La carte Listening Ports du tableau de bord de la VM montre la même chose. Cet inventaire est la question sur laquelle cette CVE reposait, et une seule commande y répond.

Si l'interface regarde aussi vers l'extérieur

La moitié distante de la CVE-2026-82533 exigeait que le port soit joignable. Les VM de workspace tournent sur un réseau NAT privé : elles sont joignables depuis votre Mac, mais elles ne sont « pas exposées sur votre LAN physique, et les connexions entrantes venues d'ailleurs sont impossibles à moins que vous ne publiiez explicitement un service ». Un port de contrôle d'agent non authentifié qu'un réseau de café ne peut pas router vous laisse un correctif à appliquer à votre propre rythme.

Un confinement qu'on peut révoquer est une préférence

DeepSeek a livré un contrôle faible et l'a corrigé trois jours après la confirmation d'OX. La partie durable, c'est ce que ce contrôle gardait. Une frontière tracée autour d'un processus par le propre environnement d'exécution de ce processus est une frontière avec laquelle le processus peut négocier : elle a une adresse, une API, et un objet de réglages avec un champ dedans. Quelque part derrière ce champ tourne un chemin de code qui décide si cet appelant a le droit de l'écrire, et un chemin de code de ce genre est à un mauvais réglage par défaut de n'être plus qu'un décor.

Tracez la frontière sous le processus et il n'y a plus de contrepartie avec qui négocier. L'agent d'un workspace Bromure détient root, détient tout le système de fichiers et peut lancer le service qu'il veut sur le port qu'il veut. Ses clés d'API sont des leurres, sa seule route vers le réseau est un socket virtio vers un proxy sur votre Mac, et le fichier qui contient ses permissions se trouve à un endroit qu'il ne peut pas lire. Donnez tout à l'agent de son côté de la ligne et vous cessez d'avoir à défendre la ligne.

Les agents de codage vont continuer à faire pousser des plans de contrôle locaux, parce qu'un plan de contrôle est la façon dont on construit un tableau de bord, un pont vers l'IDE ou un visualiseur de diff. Chaque projet doit ensuite répondre à la question de savoir si le code que son agent exécute se trouve du même côté de la ligne que les réglages qui gouvernent cet agent. Répondez mal et l'authentification devient la seule chose qui tienne encore, ce qui explique qu'un 9,4 se ramène à une seule commande shell. Installez Bromure Agentic Coding et mettez l'interrupteur quelque part où votre agent ne peut pas l'atteindre.