← Tous les articles

La boucle locale n’est pas une frontière de confiance : CVE-2026-2611

From the guide: Claude Code Comprehensive Guide

Le 19 mai 2026, la National Vulnerability Database a publié CVE-2026-2611. Dans MLflow 3.9.0, la fonctionnalité Assistant validait mal l’origine des requêtes sur ses points de terminaison locaux /ajax-api. Une page malveillante ouverte dans n’importe quel onglet pouvait envoyer des requêtes cross-origin au serveur local de l’Assistant, modifier sa configuration pour activer le mode accès complet, puis exécuter des commandes arbitraires via l’intégration Claude Code CLI fournie avec l’outil. Huntr a attribué à la vulnérabilité un score CVSS 3.0 de 9,6 (CRITICAL), avec le vecteur AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H : accessible par le réseau, faible complexité, aucun privilège requis, périmètre modifié, impact total sur la confidentialité, l’intégrité et la disponibilité.1

La cause profonde n’a rien de nouveau et le correctif n’a rien d’exotique, mais le modèle de menace a changé. Tout service local qui se lie à la boucle locale et expose à un onglet de navigateur des points de terminaison capables de modifier l’état devient une surface de frontière de confiance. Quand ce service pilote un agent IA capable de lire des fichiers, d’exécuter des commandes shell et de pousser du code, « boucle locale uniquement » cesse d’être un contrôle de sécurité. CVE-2026-2611 est un cas d’école pour toute une famille de vulnérabilités qui réapparaîtra à mesure que davantage d’outils de développement intégreront des agents LLM dans des interfaces web locales.

TL;DR

CVE-2026-2611 documente un bug d’énumération CORS incomplète dans MLflow 3.9.0. La fonctionnalité Assistant exposait des points de terminaison /ajax-api/ que le contrôle is_api_endpoint() de MLflow ne couvrait pas, si bien que le middleware de blocage CORS les ignorait. Par-dessus cela, l’ancien CORSMiddleware acceptait allow_origins=["*"]. Une requête cross-origin venue de evil.com pouvait donc appeler /ajax-api/3.0/mlflow/assistant/config, faire basculer l’Assistant en mode accès complet, puis envoyer des commandes shell via l’intégration Claude Code CLI fournie avec l’outil.1 Le correctif amont (PR #20832, « Block CORS for ajax paths ») dans MLflow 3.10.0 ajoute /ajax-api/ à l’énumération des chemins protégés et resserre allow_origins sur la liste d’autorisation configurée, plus une regex localhost.2 Les défenses ci-dessous visent toute la catégorie des agents IA locaux, pas seulement MLflow.

Points clés

Rôle Action
Ingénieurs AI/ML qui exécutent MLflow Passez à MLflow 3.10.0 ; auditez toute autre interface locale d’agent IA qui écoute sur la boucle locale
Équipes sécurité Ajoutez « service d’agent LLM local sur boucle locale » à vos modèles de menace ; dans cette catégorie, une seule faille CORS équivaut à une RCE
Auteurs d’outils qui construisent des interfaces locales d’agents Énumérez une seule fois tous les préfixes de chemins API ; interdisez allow_origins=["*"], même sur des serveurs liés à localhost ; conditionnez les points de terminaison destructeurs à un geste utilisateur récent, pas seulement à l’origine
Responsables d’ingénierie Isolez les agents de codage IA de l’équipe et traitez leur capacité à exécuter des commandes comme le véritable rayon d’impact

À quoi ressemble la chaîne d’attaque

La chaîne tient en cinq étapes. Chacune utilise des capacités autorisées ; c’est leur composition qui échoue.

Étape Action Ce que contrôle l’attaquant
1 L’utilisateur lance mlflow ui localement ; l’Assistant écoute sur la boucle locale (127.0.0.1 par défaut) Rien pour l’instant (service local)
2 L’utilisateur ouvre evil.com dans un onglet pendant que MLflow tourne Le contenu de la page
3 evil.com envoie fetch('http://127.0.0.1:5000/ajax-api/3.0/mlflow/assistant/config', { method: 'PUT', credentials: 'include', body: ... }) La requête cross-origin
4 MLflow accepte la requête parce que /ajax-api/ ne figurait pas dans l’énumération des chemins bloqués par CORS ; la configuration de l’Assistant bascule en accès complet La configuration d’exécution de l’Assistant
5 evil.com publie des charges utiles de commande via le point de terminaison de chat de l’Assistant ; MLflow invoque le Claude Code CLI local avec les contrôles d’autorisation contournés ; les commandes s’exécutent avec les droits de l’utilisateur Exécution de code arbitraire

L’attaquant n’a rien à installer sur la machine de la victime. Il lui suffit que la victime charge un seul onglet contenant la charge utile pendant que mlflow ui est lancé. L’attaque fonctionne depuis n’importe quelle origine, parce que l’ancien CORSMiddleware utilisait allow_origins=["*"] et que le middleware de blocage CORS au niveau des chemins ne couvrait pas le préfixe /ajax-api/.2

L’intégration Claude Code est l’amplificateur de privilèges. Le ClaudeCodeProvider de MLflow localise le CLI claude local via shutil.which("claude") et indique aux utilisateurs de l’installer avec npm install -g @anthropic-ai/claude-code lorsqu’il est absent. Quand l’option de configuration permissions.full_access de l’Assistant est activée, le fournisseur étend l’invocation du CLI avec --permission-mode bypassPermissions, le mode Claude Code qui supprime les demandes interactives d’autorisation et laisse les appels d’outils s’exécuter sans confirmation utilisateur.3 Les capacités autorisées de l’agent (shell, écriture de fichiers, HTTP) deviennent un shell distant avec une UX en langage naturel, exactement ce que cherche un attaquant.


La cause profonde est une énumération incomplète des chemins

Le commit de correction rend le bug évident. security_utils.py dans MLflow contenait une constante :

API_PATH_PREFIX = "/api/"

et un contrôle :

def is_api_endpoint(path: str) -> bool:
    return path.startswith(API_PATH_PREFIX) and path not in TEST_ENDPOINTS

Le middleware de blocage CORS appelait is_api_endpoint() pour décider s’il devait appliquer la validation d’origine. Tout chemin qui ne commençait pas par /api/ passait comme chemin non-API. L’Assistant a introduit /ajax-api/ pour ses points de terminaison destinés au navigateur. Personne n’a mis à jour is_api_endpoint() pour inclure ce nouveau préfixe.

Le correctif est un changement de comportement d’une ligne dans security_utils.py :2

API_PATH_PREFIX = "/api/"
AJAX_API_PATH_PREFIX = "/ajax-api/"

def is_api_endpoint(path: str) -> bool:
    return (
        path.startswith(API_PATH_PREFIX) or path.startswith(AJAX_API_PATH_PREFIX)
    ) and path not in TEST_ENDPOINTS

Il s’accompagne d’un correctif parallèle dans fastapi_security.py, qui remplace allow_origins=["*"] sur l’ancien CORSMiddleware par la liste d’autorisation configurée, plus une regex localhost. Au total, le changement ajoute 5 lignes et en supprime 2 dans security_utils.py, puis ajoute 5 lignes et en supprime 1 dans fastapi_security.py.2

Voilà toute la vulnérabilité. CVE-2026-2611 existe parce qu’un nouveau préfixe de point de terminaison a été livré sans que la fonction unique qui énumère les chemins protégés par CORS soit mise à jour. La surface d’attaque était une constante de configuration oubliée.

Pourquoi ce motif va se répéter

Le bug est plus ancien que MLflow et plus ancien que les agents IA. Les frameworks web produisent des erreurs d’énumération de chemins depuis que l’énumération de chemins existe. Ce qui a changé, ce sont les conséquences. Une interface web locale qui oubliait d’énumérer un point de terminaison pouvait autrefois divulguer quelques données de configuration ou provoquer un déni de service. Une interface web locale qui oublie d’énumérer un point de terminaison tout en exécutant un agent de codage IA avec accès shell expose toute la machine de développement.

Les interfaces locales d’agents IA forment une catégorie en pleine croissance. Tout outil qui enveloppe un agent LLM dans une interface de navigateur servie depuis 127.0.0.1 entre dans le périmètre : outils de suivi d’expériences avec panneaux de chat (le modèle MLflow), plugins d’IDE pilotant un agent via une API localhost, serveurs en mode développement avec lesquels une extension communique en HTTP, tableaux de bord web pour moteurs de modèles locaux. Chacun se lie à la boucle locale. Chacun expose à un onglet de navigateur des points de terminaison qui modifient l’état. Chacun exécute un agent autorisé à lancer un shell, écrire des fichiers et pousser vers git. Chacun n’est qu’à un bug d’énumération de chemins de CVE-2026-2611.

L’hypothèse commune derrière cette catégorie est la suivante : « la politique same-origin du navigateur protégera le serveur sur boucle locale, puisque les requêtes légitimes viennent aussi de 127.0.0.1 ». Elle s’effondre dès qu’une barrière cross-origin (CORS, validation de l’en-tête Host, validation de l’en-tête Origin, en-têtes Fetch Metadata) manque un préfixe de point de terminaison. Le navigateur ne protège pas le serveur quand le serveur accepte explicitement la requête.

L’attaque par sortie silencieuse que j’ai analysée en mars casse la même hypothèse de confiance dans l’autre sens. La sortie silencieuse dissimule l’exfiltration derrière les outils autorisés de l’agent. La mauvaise classification de la boucle locale dissimule l’entrée derrière les appels réseau autorisés du navigateur. Dans les deux cas, le fond est le même : les capacités autorisées d’un agent LLM deviennent une primitive d’attaque dès qu’un seul contrôle de frontière cède.

À quoi ressemble vraiment cette catégorie

Cette famille de vulnérabilités comporte quatre ingrédients. Un service devient exploitable lorsque les quatre sont réunis :

  1. Serveur web local lié à la boucle locale ou à 0.0.0.0. La variante limitée à la boucle locale est la plus fréquente. Ce sont les contrôles CORS, Host et Origin du navigateur qui protègent un service lié à localhost contre un onglet cross-origin, pas la liaison réseau.
  2. Points de terminaison de modification accessibles sans geste utilisateur. Changements de configuration, soumissions de commandes, écritures de fichiers. Si un fetch() depuis une origine arbitraire peut modifier l’état, le service est joignable.
  3. Capacité détenue par l’utilisateur local mais interdite à une page web. Lire le dossier personnel, exécuter des commandes shell, appeler des API cloud avec des identifiants stockés, modifier des fichiers source.
  4. Un contrôle de frontière manquant. Préfixe de chemin oublié, allow_origins générique, validation absente de l’en-tête Host, validation absente de l’en-tête Origin, absence d’application de Sec-Fetch-Site.

CVE-2026-2611 coche les quatre cases. Le correctif ferme le quatrième ingrédient pour le préfixe /ajax-api/ dans MLflow précisément. Les trois premiers restent présents dans tous les autres outils locaux d’agents IA. Le prochain CVE de cette catégorie aura le même visage, avec un autre nom de package.


Défenses contre la catégorie des agents IA locaux sur boucle locale

Les recommandations CORS classiques (définir allow_origins sur une liste précise, n’activer les identifiants que si nécessaire) sont indispensables, mais insuffisantes. Cette catégorie exige des contrôles plus profonds.

Énumérez les préfixes de chemins à un seul endroit, puis écrivez un test qui échoue dès qu’un nouveau préfixe apparaît sans protection. Le bug de MLflow est arrivé parce que l’équipe a mis à jour is_api_endpoint() lors de l’arrivée de /api/, puis l’a oublié lorsque /ajax-api/ est arrivé. Un test qui parcourt chaque route enregistrée sur l’app FastAPI, demande à is_api_endpoint() si elle est protégée, et échoue sur tout préfixe de chemin non reconnu aurait attrapé le bug au moment de la PR. Le contrôle coûte environ 20 lignes de test.

Validez les en-têtes Origin et Host, pas seulement CORS. CORS protège les navigateurs vis-à-vis de la réponse du serveur. Il ne protège pas le serveur contre l’acceptation de la requête. Les services locaux d’agents IA devraient rejeter les requêtes dont l’en-tête Origin n’appartient pas à la liste d’autorisation explicite et dont l’en-tête Host n’est pas 127.0.0.1 ou localhost (ce dernier point limite les attaques de DNS rebinding). Les deux contrôles doivent échouer fermement.

Conditionnez les points de terminaison destructeurs à Sec-Fetch-Site: same-origin. Les navigateurs envoient Sec-Fetch-Site: cross-site sur tout fetch() cross-origin, y compris de evil.com vers 127.0.0.1. La spécification Fetch Metadata Request Headers définit les valeurs de cet en-tête, et Chrome, Firefox, Safari et Edge l’envoient tous.4 Un middleware qui rejette les requêtes vers les points de terminaison de modification sauf si Sec-Fetch-Site: same-origin ajoute une couche qui ne dépend pas de la mémorisation parfaite de chaque préfixe de chemin.

Isolez la capacité d’exécution de l’agent IA. Claude Code, Cursor, Aider et Continue s’exécutent tous par défaut avec les pleins privilèges de l’utilisateur, parce que c’est l’UX la plus simple. Un bac à sable par espace de travail (bwrap avec des règles de montage explicites sous Linux ; macOS App Sandbox pour les outils distribués en bundles .app ; conteneur par espace de travail pour le multiplateforme) réduit le rayon d’impact sans sacrifier le flux principal. La mitigation ajoute de la friction. Cette friction est le but.

Traitez les commits de l’agent comme non fiables tant qu’ils n’ont pas été relus. Protection de branche, revues obligatoires, commits signés. Le modèle « agent IA avec accès commit » s’effondre dès qu’un attaquant compromet l’environnement local de l’agent. La nécessité de désaccord en revue (traitée dans La revue de code IA a besoin de désaccord) s’applique ici aussi. Si l’agent IA et le relecteur humain sont toujours d’accord, le relecteur humain ne sert que de décor.


À quoi ressemblera probablement le prochain CVE de cette catégorie

La vraie question n’est pas de savoir si une prochaine vulnérabilité en forme de CVE-2026-2611 apparaîtra. La vraie question est de savoir quel package la livrera en premier. Les candidats traversent tout l’écosystème de l’outillage IA local : outils de suivi d’expériences qui intègrent un agent à leur interface, plugins d’IDE qui parlent à un backend local d’agent en HTTP, serveurs en mode développement qu’un outil ouvre pendant une session, API localhost utilisées par des extensions pour piloter des interfaces d’agents. Aucun n’a encore publié de CVE de cette forme. Plusieurs livrent pourtant des chemins de code qui se lient à la boucle locale et acceptent des requêtes cross-origin en laissant le navigateur servir de frontière de confiance.

La catégorie dépasse CVE-2026-2611, et le modèle de menace a changé. En 2026, une machine de développement héberge un agent IA capable de lancer un shell, d’écrire des fichiers, de pousser des commits et d’appeler des API cloud. Les interfaces web locales qui pilotent ces agents héritent des privilèges de l’agent à travers la frontière de confiance que leur serveur HTTP applique. Une mauvaise configuration CORS sur un framework web de 2020 divulguait quelques données de configuration. Une mauvaise configuration CORS sur une interface locale d’agent IA en 2026 expose toute la machine de développement, plus la capacité de déploiement en production.

La solution n’est pas un patch. C’est une famille de contrôles : énumération explicite des chemins avec tests, validation d’en-têtes en défense en profondeur, isolation des capacités, et discipline de revue sur les commits produits par des agents. Les outils qui livrent l’un sans les autres livrent le successeur de CVE-2026-2611 avec un préfixe différent.


FAQ

Qu’est-ce que CVE-2026-2611 ?

CVE-2026-2611 est l’identifiant de la National Vulnerability Database pour une faille de validation d’origine incorrecte dans la fonctionnalité Assistant de MLflow 3.9.0. Cette faille permet à n’importe quelle page web d’envoyer des requêtes cross-origin au point de terminaison local /ajax-api de l’Assistant, de modifier sa configuration pour activer le mode accès complet, puis d’exécuter des commandes arbitraires via l’intégration Claude Code CLI fournie avec l’outil. Score de base CVSS 3.0 : 9,6 (CRITICAL). Corrigé dans MLflow 3.10.0 via la PR #20832, « Block CORS for ajax paths ».12

Quelle était la cause profonde ?

Le contrôle is_api_endpoint() de MLflow n’énumérait que le préfixe de chemin /api/. Quand la fonctionnalité Assistant a introduit des points de terminaison /ajax-api/, personne n’a mis l’énumération à jour. Le middleware de blocage CORS a alors laissé passer les requêtes cross-origin vers /ajax-api/ sans aucun contrôle d’origine. Une seconde faiblesse a aggravé le bug : l’ancien CORSMiddleware autorisait allow_origins=["*"], si bien que même les chemins non bloqués renvoyaient des en-têtes CORS permissifs à n’importe quelle origine. Le correctif ajoute AJAX_API_PATH_PREFIX = "/ajax-api/" au contrôle de chemin et remplace allow_origins=["*"] par la liste d’autorisation configurée, plus une regex localhost.2

Comment savoir si mon installation MLflow est affectée ?

Exécutez pip show mlflow | grep Version. Selon la plage CPE du NVD, les versions MLflow >=3.9.0,<3.10.0 sont affectées. MLflow 3.10.0 (publiée le 20 février 2026) inclut le correctif. Mettez à jour avec pip install --upgrade mlflow, puis redémarrez tous les processus mlflow ui en cours.

L’attaque fonctionne-t-elle sans Claude Code CLI installé ?

L’étape de modification de configuration fonctionne dans tous les cas. L’étape d’exécution de code arbitraire exige que le Claude Code CLI soit présent dans le PATH de la victime, car l’Assistant de MLflow l’invoque via shutil.which("claude"). Sans le CLI, le pire scénario devient une divulgation d’informations et une altération arbitraire de configuration, plutôt qu’une RCE. Pour les utilisateurs qui ont configuré le backend Claude Code (le parcours documenté de configuration de l’Assistant), l’exposition pratique est bien une RCE.

Comment protéger d’autres services locaux d’agents IA ?

Auditez les quatre ingrédients : liaison à la boucle locale ou à une adresse wildcard, points de terminaison de modification, capacités au niveau de l’agent, et tout contrôle de frontière susceptible de comporter des trous. Ajoutez des listes d’autorisation Origin explicites, une validation de l’en-tête Host contre 127.0.0.1/localhost (pour contrer le DNS rebinding), et des barrières Sec-Fetch-Site: same-origin sur les points de terminaison destructeurs. Isolez la capacité d’exécution de l’agent avec bwrap, App Sandbox ou un conteneur par espace de travail. Traitez les commits git de l’agent comme non fiables tant qu’ils n’ont pas été relus.



  1. National Vulnerability Database. « CVE-2026-2611 : validation d’origine incorrecte dans MLflow Assistant. » Publié le 19 mai 2026. Score de base CVSS 3.0 de 9,6 (CRITICAL), vecteur CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H, émis par Huntr. Correctif livré dans MLflow 3.10.0. https://nvd.nist.gov/vuln/detail/CVE-2026-2611. Rapport original : https://huntr.com/bounties/8462addd-b464-4a84-b6a2-5529604e6e5a

  2. MLflow. « Block CORS for ajax paths (#20832). » Fusionné le 16 février 2026 par Tomu Hirata. Fichiers modifiés : mlflow/server/fastapi_security.py (+5/-1), mlflow/server/security_utils.py (+5/-2), plus des mises à jour de tests. Le diff remplace le contrôle à préfixe unique de is_api_endpoint() par un contrôle à deux préfixes et resserre le allow_origins de l’ancien CORSMiddleware, de ["*"] vers la liste d’autorisation configurée plus une regex localhost. https://github.com/mlflow/mlflow/commit/8f9c8a53af90842944101eb8b7d60706822c81bc

  3. Source de MLflow 3.10.0. mlflow/assistant/providers/claude_code.py : recherche via shutil.which("claude") (lignes 268, 280, 355) ; conseil d’installation "Install it with: npm install -g @anthropic-ai/claude-code" (ligne 286) ; escalade du mode d’autorisation cmd.extend(["--permission-mode", "bypassPermissions"]) conditionnée à config.permissions.full_access (lignes 379–381). mlflow/assistant/config.py : valeur par défaut PermissionsConfig.full_access: bool = False (ligne 15). mlflow/server/assistant/api.py : préfixe de route /ajax-api/3.0/mlflow/assistant (ligne 63) ; lecteur GET /config (ligne 261) ; mutateur PUT /config (ligne 276), le point de terminaison PUT étant celui que l’attaque cross-origin cible pour activer full_access. Vérifié à partir du sdist MLflow 3.10.0 sur PyPI (mlflow-3.10.0.tar.gz). 

  4. W3C. « Fetch Metadata Request Headers. » Définit Sec-Fetch-Site et ses valeurs cross-site, same-origin, same-site, none. Implémenté par Chrome 76+, Firefox 90+, Safari 16.4+ et Edge 79+. https://www.w3.org/TR/fetch-metadata/ 

Articles connexes

Quand le mainteneur est l’attaquant : jqwik 1.10.0

jqwik 1.10.0 émet une chaîne d’injection de prompt destructrice dans la sortie Maven. Des séquences ANSI la masquent aux…

17 min de lecture

La sécurité des configurations d’agents IA relève de la sécurité de la chaîne d’approvisionnement

La sécurité des configurations d’agents IA doit faire partie de l’examen de la chaîne d’approvisionnement : points d’acc…

17 min de lecture

La boucle Ralph : comment je fais tourner des agents IA autonomes pendant la nuit

J'ai construit un système d'agents autonomes avec des hooks d'arrêt, des budgets de spawn et une mémoire par système de …

10 min de lecture