Le registre faisait passer des petits papiers
Check Point a publié le 8 septembre une recherche montrant que deux conteneurs d'exécution de code de ChatGPT, tournant sous deux comptes différents et sans aucune route réseau entre eux, pouvaient se transmettre des données via l'instance JFrog Artifactory interne qui leur servait les paquets. Rien n'a cédé sur la porte d'entrée. L'identifiant de lecture que portaient ces conteneurs pouvait aussi écrire : un champ de métadonnées sur un paquet stocké a donc fait office de boîte aux lettres. Bromure Agentic Coding ne donne à l'invité aucun compte sur le miroir, et vous laisse écrire read-only dans une règle que le fil applique.
L'isolation a tenu, les connecteurs se sont comportés comme documenté, et le service au milieu avait été installé pour la sécurité au départ. Le courrier de la victime a tout de même atteint le compte d'un inconnu, par un champ de métadonnées sur un paquet stocké.
En juin, des chercheurs de Check Point ont écrit une propriété sur un fichier
dans un dépôt de paquets. La propriété s'appelait chatgpt_test_ts et sa valeur
était un horodatage. Ils l'ont écrite depuis l'intérieur du bac à sable qui
exécute du code pour une conversation ChatGPT. Puis ils ont ouvert une
conversation sous un autre compte, dans son propre conteneur, et ont relu la même
propriété. La valeur est arrivée intacte.
Cet horodatage, c'est toute la découverte. Check Point l'a publiée le 8 septembre sous un titre qui vaut mieux que n'importe quel résumé : The Shared Clipboard Inside the Sandbox. OpenAI avait déjà retiré le service à ce moment-là. Les chercheurs ont signalé la faille fin juin et se sont vu répondre que l'instance Artifactory interne avait été démantelée : il n'y a donc aucun correctif à appliquer et rien à faire pour un utilisateur. La forme, elle, demeure.
Le service au milieu
Une conversation ChatGPT qui exécute du code obtient son propre conteneur, isolé
des conteneurs des autres conversations. Cette partie a fonctionné. Un conteneur
qui exécute Python doit aussi pouvoir faire pip install, et une équipe
plate-forme prudente ne laissera pas un bac à sable joindre pypi.org par
lui-même. Le bac à sable reçoit donc un intermédiaire contrôlé : une instance
interne de JFrog Artifactory qui se place devant
les registres publics et sert vers l'intérieur des paquets validés.
C'est une bonne conception. Une banque place la même chose devant npm, et un acteur de santé régulé la place devant PyPI. Le miroir existe pour réduire l'exposition du bac à sable, et il le fait.
Il donne aussi à chaque conteneur un compte sur le miroir.
L'API REST d'Artifactory comprend un groupe Item Management, joignable à
/api/storage/{repoKey}/{itemPath}, qui permet à un client d'attacher à un
élément stocké des propriétés nommées, de type chaîne et arbitraires, puis de les
relire : Set Item Properties et Get Storage Item Information. Écrire une
propriété exige la permission Annotate. La lire, non. Check Point a constaté que
l'identifiant délivré aux conteneurs du bac à sable, prévu comme lecteur, portait
les deux. Comme
Cybersecurity News l'a résumé,
« les identifiants de lecture remis aux conteneurs avaient assez de permissions
pour à la fois écrire et lire ces propriétés », et « ces propriétés n'étaient pas
isolées par compte ».
Les valeurs de propriété acceptent du texte, ou du Base64 pour tout ce qui est binaire, et une charge trop grosse pour une seule propriété se découpe sur plusieurs et se réassemble de l'autre côté. Deux conteneurs appartenant à des clients sans lien entre eux, sans aucune route réseau les reliant, disposent désormais d'un canal bidirectionnel dont le plafond de débit ne mérite pas d'être mentionné.
Deux conversations dans le même tour
Les instructions de l'attaquant atteignent la victime par des voies ordinaires : un prompt collé, un lien de conversation partagé, ou un GPT personnalisé dont l'utilisateur ne voit pas les instructions de configuration. La victime envoie ensuite un message normal, et c'est là tout le déclencheur.
En mode Thinking, le modèle a traité deux flux de travail indépendants en un seul tour. Dans la démonstration de Check Point, la victime posait une question de cuisine et recevait une réponse correcte, tandis qu'à côté de cette réponse le modèle lisait le compte Gmail connecté de la victime et écrivait ce qu'il y trouvait dans une propriété Artifactory, à charge pour le conteneur de l'attaquant de venir la récolter. Pedro Drimel Neto, qui dirige l'équipe d'analyse de malwares de Check Point, a déclaré à The Register : « Une instruction fabriquée pouvait amener ChatGPT à traiter un second flux de tâches à côté de la conversation visible. » Gmail était la démonstration. Google Drive, Microsoft Teams et GitHub se trouvaient à la même portée.
La victime a vu un seul signal, une petite étiquette « Talked to Gmail », et elle est apparue après coup. Les connecteurs sont par défaut réglés sur Important actions, qui approuve sans demander les lectures à faible risque ; le plus strict Always ask aurait produit une invite. Ce réglage classe une lecture de courrier comme à faible risque sans se demander qui l'a demandée.
La description que Check Point donne du modèle : il « peut donc agir comme un initié contraint : non pas malveillant, mais persuadé d'agir pour le compte de l'attaquant ».
La partie qui se généralise
Retirez ChatGPT et l'histoire parle d'un agent de code, parce que la structure est identique.
Un bac à sable d'agent doit installer des paquets. Le laisser joindre les
registres publics sans intermédiaire est précisément ce que vous vouliez éviter,
alors vous placez un miroir devant : Artifactory, Nexus, un proxy npm privé, ce
sur quoi votre organisation s'est standardisée. Le bac à sable s'authentifie
désormais auprès de ce miroir, il détient donc un identifiant, et quelqu'un a
décidé ce que cet identifiant pouvait faire bien avant que cet agent existe. Les
registres de paquets acceptent des envois, des tags, des annotations et des règles
de rétention, ce qui fait d'Annotate une petite permission posée sur une large
surface.
Posez donc deux questions à votre propre installation, plutôt que de demander si le bac à sable est isolé. Nommez ce sur quoi il détient un compte. Puis nommez quels verbes fonctionnent sur ce compte.
Dans un espace de travail Bromure
Bromure Agentic Coding exécute chaque agent de code dans une VM Linux virtualisée par le matériel sur votre Mac, et garde les vrais secrets du côté hôte de cette ligne. Faites passer les trois ingrédients de Check Point par un espace de travail.
Un service partagé. Il n'y en a aucun. Chaque espace de travail correspond à une VM, avec son propre disque système, sa propre image de home, sa propre IP et son propre écouteur proxy MITM sur l'hôte. Deux espaces de travail ne partagent aucune VM, et la description qu'en donne le manuel pour une paire d'entre eux est qu'ils « ne partagent jamais un octet ». Aucun miroir à l'échelle du parc ne se trouve sous les espaces de travail avec un plan de métadonnées dans lequel les deux peuvent écrire. Ce qui se trouve sous un espace de travail, c'est un processus sur votre propre Mac, un par espace de travail.
Un identifiant sur le miroir. L'agent n'en détient aucun. Les installations de
paquets dans un espace de travail Bromure sortent par un socket virtio vers le
proxy de l'hôte, et c'est le proxy qui parle au registre. Bromure garde les clés
d'API des services de réputation côté hôte et ne les exporte dans aucune VM. Le
chemin Depi est le cas le plus net : le proxy attache votre clé de registre comme
en-tête Authorization: Bearer sur l'hôte et supprime au passage tout en-tête
Authorization envoyé par l'invité. Aucun identifiant de lecture n'existe dans
l'invité, donc aucun ne peut porter un verbe de trop.
Un verbe d'écriture qui a survécu à sa raison d'être. Ici, c'est vous qui
écrivez la contrainte. Le panneau Guardrails d'un espace de travail porte un
pare-feu de sortie ordonné, et les règles pour le protocole web acceptent une
colonne Methods. Sous allow, les méthodes forment une liste d'autorisation,
et read-only est un raccourci pour GET, HEAD et OPTIONS. Le manuel dit pourquoi
cette colonne existe : la dimension des méthodes « est ce qui permet à un espace
de travail de lire une API qu'il n'a pas le droit d'écrire », sur le fil, quel
que soit l'outil qui émet la requête à l'intérieur de la VM. Deux couches
l'appliquent. Le commutateur réseau virtuel apparie chaque flux par IP de
destination et par nom d'hôte capté dans le DNS, et le proxy apparie de nouveau
par nom de serveur TLS et par méthode HTTP individuelle, si bien qu'un agent
compromis n'a de route pour contourner ni l'un ni l'autre. Les modifications
atteignent les sessions en cours sans redémarrage, et chaque verdict se dépose
comme une ligne Firewall dans la Security Timeline.
Le second flux, et le chemin du retour
Le second flux de tâches de l'attaquant est arrivé comme du contenu que le modèle
a lu. Dans un espace de travail Bromure, tout ce qu'un agent ingère et renvoie en
flux au modèle (contenus de fichiers, pages web, sorties de commandes, tout ce que
retourne un outil) voyage sous forme de spans tool_result à travers ce même
proxy hôte. Le détecteur de code source note ces spans avec un classifieur
PromptGuard local avant que le modèle n'agisse dessus, puis journalise la
détection, se met en pause pour votre décision, ou bloque la requête. Le détecteur
est livré désactivé et télécharge un modèle de 298 Mo la première fois que vous
l'activez : activez-le donc avant la session où vous en aurez besoin.
La lecture de connecteur non approuvée a ici son inverse. Les jetons bearer MCP
d'un espace de travail Bromure apparaissent dans la VM comme des marque-places
brm-mcp_. Le vrai jeton reste hors de l'invité, et le proxy le substitue sur le
fil en direction du seul hôte pour lequel ce jeton a été émis. Activez Ask
before use pour un identifiant et sa première utilisation dans une session se
met en pause pour un dialogue côté hôte offrant une autorisation de 5 minutes,
d'une heure, ou du reste de la session. C'est l'invite que « Important actions » a
refusé d'afficher, par identifiant, sur la machine devant laquelle vous êtes
assis.
Si quelque chose dans la VM emporte un marque-place là où il n'a rien à faire, le
proxy refuse la requête sans transmettre un octet, met la VM en pause, et lève une
alerte nommant l'aperçu du jeton, l'identifiant, l'hôte pour lequel il a été émis
et l'hôte vers lequel il se dirigeait. Sous ce fil-piège, le traçage réglé sur
Activity only écrit une ligne de métadonnées par requête : hôte, méthode,
chemin, statut, octets, quels identifiants le proxy a échangés, et un avertissement
de fuite pour tout jeton de type bearer que Bromure n'a pas émis. Lancez
bromure-cli trace hostnames après une session et chaque hôte distinct contacté
par l'agent s'affiche avec un compteur.
Si vous exploitez un miroir pour vos agents
Vérifiez l'identité avec laquelle vos bacs à sable s'authentifient, avant le
chemin réseau entre eux. Sur Artifactory, cherchez Annotate sur un jeton que
vous pensez être un lecteur, et une cible de permission plus large que les
dépôts depuis lesquels le bac à sable installe. L'API de propriétés est un
plan de métadonnées inscriptible ; les règles de rétention, les tags et les
informations de build en sont d'autres. Un lecteur qui peut annoter peut
poster.
La règle qui répond en une ligne
Dans le panneau Guardrails de l'espace de travail, ajoutez une règle de
sortie pour le registre avec le protocole web et read-only dans la colonne
Methods, puis réglez Unmatched traffic sur Deny et listez ce dont le
travail a besoin. Le dépliant au format pf en bas du panneau imprime ce que
vous avez construit : allow web registry.npmjs.org read-only,
allow web api.github.com, default deny. Cela s'applique aux sessions en
cours sans redémarrage.
Un service partagé fait des locataires des voisins
L'instinct, après une histoire comme celle-ci, est d'aller vérifier les murs. Les murs allaient bien. Deux conteneurs sous deux comptes n'avaient aucune route l'un vers l'autre, et Check Point n'en a trouvé aucune. Ils ont trouvé un tiers auquel les deux conteneurs avaient le droit de parler, et l'ont utilisé comme on utilise des disques partagés et des enregistrements DNS TXT depuis trente ans.
Check Point énonce la leçon dans l'article : « un service interne partagé, pensé purement comme de l'infrastructure, peut devenir une couche de communication involontaire entre des environnements censés rester isolés ». Une infrastructure que vous installez pour la sécurité reste une infrastructure, et ses locataires sont à un saut l'un de l'autre. Réduisez l'ensemble des choses sur lesquelles un bac à sable détient un compte, et déplacez la décision sur ce que ces comptes peuvent faire vers chaque requête, hors d'un ticket de provisionnement que quelqu'un a fermé il y a deux ans.
Un bac à sable d'agent qui vaut la peine d'être exécuté aura un chemin contrôlé vers les paquets dont il a besoin, parce que l'alternative est pire. Traitez ce chemin comme une relation à laquelle sont attachées des permissions, et traitez ces permissions comme votre périmètre. Décidez-les à un endroit où vous pouvez encore les voir. Installez Bromure Agentic Coding et ne donnez à votre agent rien avec quoi se connecter.