Charges cachées dans les métadonnées d'outils MCP : l'écart de fidélité de la vue d'approbation
Une étude de juillet 2026 montre que des caractères Unicode TAG invisibles peuvent glisser des instructions dans les métadonnées d'outils MCP — présentes dans le contexte du modèle, absentes de la fenêtre d'approbation vue par l'utilisateur.
De quoi s’agit-il ?
Le 7 juillet 2026, Mohammadreza Rashidi a publié sur arXiv Unicode TAG-Block Concealment of Tool-Metadata Payloads in the Model Context Protocol. L’article isole une faiblesse structurelle dans la manière dont les agents de code bâtis sur le Model Context Protocol (MCP) recueillent le consentement d’un utilisateur pour un serveur d’outils. Quand un agent se connecte à un serveur, il effectue une poignée de main tools/list qui renvoie, pour chaque outil, un nom, une description en langage naturel et un schéma d’entrée JSON. Le client affiche ces métadonnées une seule fois, dans une fenêtre d’approbation, puis les injecte telles quelles dans le contexte du modèle à chaque tour suivant.
Le constat est le suivant : rien dans le protocole n’impose que la vue approuvée par l’humain et les octets délivrés au modèle soient identiques. L’auteur nomme cet écart la fidélité de la vue d’approbation, et montre qu’il s’agit d’une propriété du mécanisme, pas d’un bug isolé : le chemin de code qui rend l’affichage d’approbation et celui qui livre les données au modèle sont indépendants, et tous deux sont alimentés par les mêmes octets contrôlés par le serveur, donc non fiables.
Comment ça marche
Le cœur de la technique est l’encodage de dissimulation. Le bloc TAG d’Unicode (points de code U+E0000–U+E007F) n’a aucun glyphe assigné dans les moteurs de rendu de terminaux, de messageries ou d’IDE courants. Un texte écrit dans cette plage est donc absent de ce que voit un relecteur humain, tout en survivant octet pour octet jusqu’au tokeniseur du modèle. Un attaquant qui contrôle un serveur d’outils peut placer des instructions dans la plage invisible, à l’intérieur de la description ou du schéma d’un outil ; l’utilisateur lit une description propre et anodine et clique sur « autoriser », tandis que le modèle reçoit l’intégralité de la charge cachée à chaque tour.
L’article construit une preuve de concept au niveau du protocole, qui parle du vrai MCP en JSON-RPC sur stdio face à un client et un serveur réels, et évalue huit techniques concrètes sur cinq surfaces de métadonnées distinctes. Les résultats rapportés sont précis : les huit techniques délivrent une charge contrôlée par l’attaquant dans le contexte du modèle ; quatre sur huit passent un filtre par correspondance de chaînes représentatif ; et — exactement comme le prédit l’analyse du mécanisme — seul l’encodage TAG est à la fois invisible dans la vue d’approbation humaine et livré tel quel au modèle, ce qui en fait la seule technique du lot à déjouer les deux couches de défense d’un coup. Les autres techniques couvrent des failles voisines : un serveur qui conserve le nom d’un outil mais en change silencieusement la définition plus tard (un « rug-pull » de type time-of-check to time-of-use), un serveur tiers qui usurpe le nom d’un outil de confiance de l’hôte, et des valeurs default ou enum dangereuses d’un schéma qu’un agent hérite sans confirmation. Fait notable, le protocole n’a forcé de nouvelle approbation pour aucune des huit, y compris sous le rug-pull. Nous décrivons le mécanisme au niveau conceptuel ; le préprint contient les charges et le banc de mesure.
Pourquoi c’est important
MCP est devenu la façon dominante dont les agents de code découvrent et invoquent des outils externes, et ces mêmes métadonnées d’outils sont souvent la seule chose que voit un utilisateur avant d’accorder l’accès à un serveur. La fenêtre d’approbation est donc la véritable frontière de confiance — et une frontière qui affiche une chaîne différente de celle que consomme le modèle n’en est pas une. Un attaquant capable d’amener une victime à se connecter à un serveur malveillant ou compromis, ou d’altérer les métadonnées en transit, obtient un canal d’instructions persistant qu’un relecteur humain attentif ne peut pas voir et qu’un filtre d’entrée naïf ne peut pas attraper.
Deux détails font de cela plus qu’un incident isolé. D’abord, le contournement du filtre et celui de la relecture humaine sont indépendants : la correspondance de chaînes inspecte la même surface affichée que l’humain, donc une charge rendue vide à l’écran est aussi vide pour le filtre. Ensuite, pour vérifier si le résultat tenait à une seule base de code, l’auteur a réimplémenté tout le catalogue de techniques face à trois bibliothèques de serveurs MCP Python développées indépendamment, et a trouvé un accord total sur les trente-deux cellules de résultats inter-bibliothèques — preuve que l’écart découle de la conception du protocole plutôt que d’une implémentation particulière. Un contrôle sur vingt-cinq descriptions d’outils bénignes n’en a signalé aucune : le filtre de référence ne rejette donc pas tout par défaut.
Défenses
L’article présente les correctifs comme quatre changements structurels indépendants, chacun fermant une technique précise plutôt qu’un rustinage cosmétique.
- Rendre le consentement fidèle aux octets. Une vue d’approbation doit afficher les octets exacts que recevra le modèle, ou bien les normaliser ou les rejeter explicitement. Faites passer chaque chaîne affichée dans la fenêtre par la même étape de décodage que le tokeniseur, puis affichez un marqueur visible pour tout point de code hors couverture de la police — comme les navigateurs affichent un glyphe « .notdef » (tofu) pour rendre visible une entrée inhabituelle plutôt que silencieusement absente — ou refusez d’afficher des métadonnées contenant de tels points de code tant qu’elles ne sont pas normalisées.
- Redemander le consentement en cas de mutation. Épinglez un hachage des métadonnées approuvées et redemandez à l’utilisateur quand un serveur renvoie ensuite une définition différente sous le même nom d’outil. Traitez l’identité comme nom de l’outil plus définition, comme les gestionnaires de paquets épinglent les hachages de dépendances et comme les plateformes mobiles redemandent l’autorisation lorsque les permissions déclarées d’une application changent à la mise à jour.
- Cadrer les noms d’outils par provenance. N’autorisez pas un serveur tiers à usurper le nom d’un outil de confiance fourni par l’hôte. Faites reposer l’identité d’un outil sur son origine (quel serveur a enregistré ce nom) plutôt que sur un espace de noms plat et global, afin qu’un serveur malveillant ne puisse pas enregistrer
read_fileet capter un appel destiné à l’implémentation de l’hôte. - Les valeurs par défaut d’un schéma ne sont pas un consentement. Tout champ dont le rôle est d’orienter un appelant autonome — une description, un
default, unenum— est un canal d’instructions atteignable par l’attaquant et doit être présenté dans la vue d’approbation avec la même fidélité, jamais hérité en silence.
L’auteur note aussi que ces correctifs côté rendu sont complémentaires des défenses côté rédaction, comme la génération de descriptions de confiance ou les schémas de signature/versionnage : un outil légitimement signé et légitimement rédigé peut tout de même porter une charge TAG qu’un moteur de rendu non fidèle aux octets n’affichera pas. Un client qui ne déploie qu’une seule classe de défense n’est pas protégé contre la surface que l’autre couvre.
Statut
| Élément | Valeur |
|---|---|
| Type | Recherche en sécurité — écart de fidélité entre vue d’approbation et métadonnées d’outils MCP |
| Modèle de menace | Serveur d’outils MCP malveillant ou compromis (ou métadonnées altérées en transit) qu’un utilisateur est amené à approuver |
| Effet | Des instructions cachées atteignent le modèle telles quelles tout en étant absentes de la vue d’approbation ; filtres de chaînes et relecture humaine contournés |
| Portée | 8 techniques sur 5 surfaces de métadonnées ; nouvelle approbation forcée pour 0/8 ; confirmé sur 3 bibliothèques de serveurs MCP Python indépendantes |
| Correctif structurel | Vues d’approbation fidèles aux octets ; re-consentement en cas de mutation ; noms cadrés par provenance ; valeurs par défaut présentées comme un consentement |
| Publication | 7 juillet 2026 (arXiv:2607.05744v1), CC BY 4.0 |
| Statut | Préprint académique ; ni vulnérabilité produit ni CVE |
Dates clés : 7 juillet 2026 — dépôt du préprint (arXiv:2607.05744). Le travail prolonge la documentation antérieure sur la dissimulation via caractères Unicode Tag dans les entrées de LLM (Embrace The Red, 2024) et la situe spécifiquement dans le pipeline d’approbation d’outils MCP. Résultats et méthode sont ceux énoncés dans le préprint.