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

Le hook n'est jamais arrivé jusqu'au modèle

Le 31 août, Token Security a détaillé sur The Hacker News les nouveaux points d'accès de l'API Compliance d'Anthropic pour les sessions locales de Claude Code. Ce sont de vraies avancées, et l'article est précis sur l'endroit où le transcript s'arrête : les hooks qui se déclenchent avant l'exécution d'un outil, les fichiers posés sur le disque, les processus lancés hors session, tout ce qui passe par Bedrock ou Vertex. Le ver npm de la semaine dernière a vécu toute sa vie dans cet interstice. Bromure Agentic Coding pose sa sonde sur le fil entre la VM et le reste du monde : la trace couvre ce qui s'est passé, pas ce qu'on a dit au modèle.

Un transcript ne peut contenir que ce qu'on a dit au modèle. La moitié intéressante d'une session d'agent est celle que personne n'énonce à voix haute.

Pendant l'essentiel de cette année, un responsable d'ingénierie qui voulait savoir ce que Claude Code avait fabriqué sur un portable avait deux options : lire le scrollback du développeur, ou acheter un wrapper tiers. Les points d'accès de conformité d'Anthropic couvraient claude.ai et Claude Desktop, et Claude Code à peine.

Cela a changé le 11 août. Anthropic a livré trois points d'accès de l'API Compliance pour les sessions locales : l'un liste les sessions, l'autre récupère les métadonnées d'une session, et le troisième tire le transcript complet depuis GET /v1/compliance/apps/sessions/local/{session_id}/messages. Le 31 août, Token Security a publié un tour d'horizon de ce qu'ils offrent sur The Hacker News.

Token Security a sponsorisé cet article, et il reste l'un des comptes rendus les plus soigneux que j'aie lus sur l'endroit où s'arrête l'horizon d'un journal.

Trois types de blocs, et ce qu'ils couvrent

Le transcript arrive sous forme de trois types de blocs. Dans les mots de l'article : « Tout ce qui est communiqué au modèle est journalisé sous trois types de blocs : text, tool_use et tool_result. À eux trois, ils couvrent les prompts de l'utilisateur, les commandes bash, les lectures et les écritures, et même les commandes MCP. »

C'est beaucoup : chaque commande shell que le modèle a choisie, chaque fichier qu'il a lu, chaque outil MCP qu'il a appelé. Si votre question est « qu'est-ce que cet agent a décidé de faire mardi », le transcript y répond, et il y répond depuis les serveurs d'Anthropic plutôt que depuis un fichier posé sur la machine que vous enquêtez.

La proposition qui fait tout le travail est la première : tout ce qui est communiqué au modèle.

L'horizon

Quatre choses se tiennent hors de cette frontière, et l'article les nomme toutes les quatre.

Les hooks. « Les hooks sont le cas le plus net : ils s'exécutent localement, entre la décision du modèle et l'exécution effective de l'outil, et peuvent empêcher un outil de s'exécuter ou un prompt d'être envoyé. » Un hook est du code posé sur le disque du développeur, qui se déclenche dans l'intervalle entre le choix d'une action par le modèle et sa réalisation. Le modèle n'en entend jamais parler, donc le transcript ne le porte jamais.

Le disque. « Ni l'un ni l'autre ne voit ce qui repose sur le disque : fichiers de configuration, skills et plugins installés avec leurs fichiers .md (sauf s'ils ont été utilisés dans une session), ou processus lancés hors session. » Une skill que vous installez sans jamais l'invoquer ne laisse aucune trace, et pas davantage un processus d'arrière-plan que l'agent a démarré il y a une heure avant de l'oublier.

Les autres fournisseurs de modèles. « Si vous faites tourner Claude Code sur un modèle qui n'est pas celui d'Anthropic, vous n'obtenez aucune couverture de l'API Compliance, car elle ne journalise que les interactions avec les modèles d'Anthropic. Les sessions tournant sur Bedrock, Foundry ou Google Cloud ne seront pas couvertes. » La couverture s'arrête aux modèles d'Anthropic, ce qui laisse dehors Codex, Gemini CLI, Grok, et ce que votre équipe installera au trimestre prochain.

Les décisions de permission. L'approbation par un développeur d'une commande dangereuse, ou un drapeau de contournement qui a fait passer tout un prompt, atterrit dans OpenTelemetry et non dans le transcript de conformité.

Tout cela est un comportement correct. Un journal ancré au modèle enregistre ce que le modèle a vu. La question pour votre prochaine revue de sécurité est de savoir si les choses que vous avez le plus besoin de voir tombent du côté du modèle de cette ligne.

la frontière de l'hyperviseur — proxy hôte et commutateurchaque octet que la VM envoie ou reçoit, de tout processus, sous tout agentce qui a atteint le modèletext · tool_use · tool_resultprompts de l'utilisateurcommandes bash choisieslectures et écritures demandéesappels d'outils MCPentre les deux lignesles hooks, déclenchés avantque l'outil ne tournescripts d'install et node-gyp,enfants d'une seule commandearchives arrivant denpm, PyPI, Cargo, Mavenles requêtes sortantespropres à un serveur MCPprocessus démarrés horsde toute sessionsessions sur Bedrock,Foundry ou VertexLes deux traces sont exactes. Elles répondent à des questions différentes, car elles sont ancrées à des frontières différentes.
Deux frontières autour d'une même session d'agent. L'intérieure est le modèle : tout ce qui lui est communiqué figure dans le transcript de l'API Compliance. L'extérieure est l'hyperviseur : tout ce que la VM envoie ou reçoit traverse le proxy et le commutateur virtuel de Bromure. La région intéressante est l'espace entre les deux, là où vivent les hooks, les processus enfants lancés à l'installation, les archives de paquets, le trafic propre aux serveurs MCP et les backends non-Anthropic.

Le ver de la semaine dernière vivait dans cet interstice

Il y a quatre jours, un générateur de code npm dépassant les 150 000 téléchargements hebdomadaires s'est mis à livrer un voleur d'identifiants. Nous avons décrit samedi la chaîne de publication : un inconnu a commenté une pull request, le workflow de publication du projet a lu le commentaire sans vérifier qui l'avait écrit, et dix versions signées sont parties.

Relisez ce malware avec le transcript de conformité dans l'autre main. Tracez où chaque étape apparaîtrait.

L'agent lance npm install. C'est un bloc tool_use, et le transcript le possède. Tout ce qui suit est un processus enfant.

La première vague ne déclarait aucun script d'installation. Sa charge se logeait dans binding.gyp, la description de build que lit node-gyp, à l'intérieur d'un champ conditions que node-gyp évalue avec Python. Le modèle n'a jamais eu son tour dessus et aucun appel d'outil ne le nomme : une expression Python dans un fichier de build, tournant comme petit-enfant de l'unique commande que le transcript a enregistrée comme parfaitement ordinaire.

Le balayage qui a suivi a parcouru plus de 150 motifs glob dans le répertoire personnel, à la recherche de clés SSH, de fichiers .env, d'identifiants cloud et de jetons de registre. Personne n'a posé la moindre question au modèle là-dessus.

Puis la persistance. Dans chaque dépôt qu'il pouvait atteindre, le ver écrivait un .claude/settings.json portant un hook SessionStart qui exécute setup.mjs chaque fois qu'un développeur ouvre le projet dans Claude Code. Un hook, sur le disque, se déclenchant avant que quiconque ne consulte le modèle : deux des quatre angles morts dans un seul fichier. L'attaquant qui a choisi cet emplacement n'avait presque certainement jamais lu le changelog de conformité d'un éditeur.

Toute la fenêtre hostile a duré trois heures et onze minutes, et pas une seule de ses parties n'a jamais atteint un modèle.

Où Bromure pose la sonde

Bromure Agentic Coding fait tourner chaque espace de travail comme sa propre machine virtuelle sur Apple Silicon, et tout ce qui quitte cette VM traverse un proxy et un commutateur virtuel qui vivent du côté Mac de l'hyperviseur. Le commutateur détourne les flux des ports 80 et 443 de la VM vers le proxy sans aucune variable d'environnement à positionner et sans rien que l'invité puisse désactiver : la trace ne dépend donc pas de la coopération de l'agent, ni de ce que l'agent sache que le proxy est là.

Au niveau de trace Activité seule et au-dessus, chaque requête reçoit un enregistrement de métadonnées : horodatage, hôte, port, méthode, chemin, code de statut, latence, octets de requête mesurés avant tout échange d'identifiants, octets de réponse, quels identifiants le proxy a substitués à la sortie, et un avertissement si la requête portait un jeton bearer que Bromure n'a pas émis. Cet enregistrement existe que la requête vienne de l'agent, d'un script postinstall, d'un serveur MCP qui fait sa propre cuisine, ou d'un processus que quelqu'un a démarré il y a trois heures avant de l'oublier.

La fenêtre Chronologie de sécurité (Window → Security Timeline…) se tient à côté et répond à une autre question. L'Inspecteur de traces vous dit ce que l'agent a envoyé ; la chronologie vous dit ce que les moteurs de Bromure ont décidé. Une seule table chronologique, la plus récente en tête, en couleurs : vert pour autorisé, rouge pour bloqué, bleu pour informatif — Courtage d'identifiants, Pare-feu, Chaîne d'approvisionnement, Garde-fous, Injection de prompt, Identifiant utilisé, TLS amont. Elle tient 5 000 lignes en mémoire. La copie durable, ce sont les traces de session chiffrées sous ~/Library/Application Support/BromureAC/traces/, scellées en AES-GCM avec la même clé de trousseau que les secrets de votre espace de travail.

La même installation, dans un espace de travail Bromure, se lit comme ceci.

Chronologie de sécuritémoteursa décision20:31:02Chaîne d'appro.npm [email protected] — bloquéversion vieille de 9 minutes, délai exigé 2 jours20:31:02Chaîne d'appro.npm [email protected] — réécritscripts retirés, hash de métadonnées mis à jour20:31:07Pare-feuobjects.githubusercontent.com:443 tcp — bloquéaucune règle, trafic non apparié en Refus20:31:09Courtage d'ident.brm_a1b2… → paste.example — bloquétentative d'exfiltration, VM suspendue20:31:09Garde-fousgit-receive-pack github.com — bloquéGitHub est en Lecture seule pour cet espace20:33:41Injection de promptrules FLAG source=/repo/AGENTS.md
Ce qu'une Chronologie de sécurité Bromure enregistre pendant l'installation que le transcript a vue comme un unique bloc tool_use. Chaque ligne provient d'un moteur côté hôte qui surveille le fil, donc rien de tout cela ne dépend du passage de la charge par le modèle, et rien ne peut être modifié par du code à l'intérieur de la VM.

Pas une seule de ces lignes n'exige que la charge soit passée par un modèle, et aucune ne peut être modifiée par du code à l'intérieur de la VM, parce que les moteurs qui les écrivent tournent sur le Mac et que l'invité n'a aucune route vers eux.

Les mêmes lignes, quel que soit l'agent que vous faites tourner

Une équipe se standardise sur Claude Code. Puis quelqu'un amène Codex pour un projet, l'équipe plateforme route un espace de travail via Bedrock pour la résidence des données, et un chercheur se met à faire tourner Grok. D'après le compte rendu de l'article lui-même, trois de ces quatre-là ne produisent rien dans l'API Compliance.

Un proxy qui lit les noms de serveur TLS voit les quatre de la même manière, et les mêmes événements llm.request, tool.use, command.run, file.read et file.write sortent de chacun d'eux, extraits du trafic plutôt qu'octroyés par un éditeur. Sur un Mac enrôlé auprès d'un espace de travail bromure.io, ils sont diffusés vers l'organisation en TLS mutuel, aux côtés de egress.firewall pour chaque verdict du pare-feu, credential.exfiltration quand un identifiant leurre part vers un hôte pour lequel il n'a jamais été émis, et supply_chain.fetch pour chaque paquet que l'espace de travail a tiré. Ce dernier se déclenche même avec toutes les couches d'application de la chaîne d'approvisionnement désactivées, parce que Bromure sépare l'observation du blocage. Le flux ne porte aucun prompt brut. Il répond à ce que l'agent a fait, pas à ce que le développeur a demandé.

Claude Code · Anthropicdans l'API ComplianceClaude Code · Bedrocknon couvertCodex · OpenAInon couvertGrok · xAInon couvertproxy hôte + switchlit les noms de serveur TLS,pas les ID de compteun seul flux d'événementsllm.request · tool.usecommand.run · file.readegress.firewallsupply_chain.fetchcredential.exfiltration
Quatre espaces de travail sur quatre backends différents. Une trace ancrée aux modèles d'Anthropic couvre le premier. Une trace ancrée au fil les couvre tous les quatre, parce qu'elle est extraite du trafic plutôt qu'octroyée par l'éditeur qui se trouve servir les jetons.

La phrase sur laquelle l'article se termine

La phrase la plus tranchante du texte laisse la couverture derrière elle : « à eux seuls, les journaux d'activité ne peuvent pas vous dire si l'accès d'un agent est légitime. »

Cela vaut pour n'importe quel journal, et c'est pourquoi les moteurs de Bromure écrivent eux-mêmes la chronologie au lieu de confier la tâche à un observateur séparé. Chaque ligne rouge de cette fenêtre enregistre une décision qui a déjà eu lieu. Une ligne chaîne d'approvisionnement signifie un paquet que l'agent n'a jamais reçu. Une ligne pare-feu signifie une connexion qui ne s'est jamais ouverte. La ligne Garde-fous est un push que l'hôte a refusé avec un 403, que l'agent a lu comme un échec d'API ordinaire, et la ligne de courtage d'identifiants est un leurre qui n'est jamais devenu un vrai secret, parce que le vrai n'a jamais été dans la VM.

Il vous reste à décider de la politique. Mais vous répondez à « cet accès était-il légitime » en configurant l'espace de travail, pas des semaines plus tard en lisant des lignes.

Où poser votre sonde

Si vous faites tourner des agents de code à la moindre échelle, activez l'API Compliance. Il y a quatre semaines, cette surface n'avait presque aucune visibilité, et une semaine de transcripts vous apprendra sur votre propre équipe des choses que vous ignoriez.

Puis posez la deuxième question : que faites-vous d'une machine dont le transcript a l'air propre ? Dans un espace de travail Bromure, la réponse est déjà à l'écran. Ouvrez Window → Trace Inspector pour les hôtes, Window → Security Timeline pour les verdicts, ou lancez bromure-cli trace hostnames et lisez chaque domaine que cet espace de travail a contacté depuis son démarrage.

L'agent n'a pas besoin d'en souffler mot pour que ce soit là. Installez Bromure Agentic Coding, ouvrez la chronologie, et regardez une installation.