← Retour au blog
Sécurité

La sécurité des agents IA devient un sujet chaud : comment un serveur MCP malveillant et de faux Skills attaquent un AI Coding Agent ?

Publié le 18/09/2026 Dernière mise à jour le 18/09/2026 Environ 16 min Boîte à outils JSON

Comment lire cet article

Un Coding Agent sait déjà lire le dépôt, modifier une config, se brancher à MCP, charger un SKILL.md. La surface d'attaque n'est plus seulement « une phrase ignore what precedes cachée dans une page ». Article daté du 18/09/2026 : d'abord déplier comment un serveur MCP malveillant et de faux Skills s'appuient sur la description d'outil, des entrées trop larges et le routage par manuel, puis refermer les permissions en un JSON Schema validable dans l'espace de travail. Les trois couches : Skills vs MCP vs API ; le chargement à la demande : Pourquoi un agent a besoin de Skills ; pourquoi modifier mcp.json exige de lire d'abord, puis de patcher : Comment un agent modifie une config JSON.

En 2026, un Coding Agent fait par défaut trois choses : scanner le catalogue de Skills du projet, se brancher à un MCP local ou distant, et choisir quelle main tendre selon la description d'outil. Marchés communautaires, installation en un clic, laisser l'agent réécrire mcp.json : « découvrir une capacité » est devenu une expérience produit. La même expérience pousse la frontière de confiance hors de l'éditeur : qui apparaît dans la table d'outils peut faire exécuter le modèle à votre place.

Pourquoi la sécurité des agents est soudain un sujet chaud

L'injection de prompt n'a pas disparu. Ce qui a changé, c'est ce qu'on touche après. Un chatbot d'avant générait au mieux un texte qui ressemblait à une commande ; derrière un Coding Agent d'aujourd'hui pendent lecture de fichiers, modification de config, lancement de checks, appels d'API internes. Une fois l'outil listé par le Host, le modèle traite la description comme une pub de capacité, et le retour comme un fait.

La discussion sécurité quitte donc « ne pas coller une clé dans le chat » pour trois couches :

  • Qui est installé. D'où viennent le serveur MCP et le paquet Skill, et s'ils entrent dans l'allowlist.
  • Ce qu'on a le droit d'appeler. Le nom d'outil a l'air inoffensif, les arguments peuvent pourtant viser n'importe quel chemin ou n'importe quelle action.
  • Le résultat va-t-il commander le saut suivant. Un retour d'outil peut devenir une nouvelle instruction ; un manuel peut changer le routage.

Le sujet n'est pas que le modèle serait soudain plus dangereux, c'est que la surface exécutable a grandi. Un Skill décide quand charger un manuel, MCP d'où viennent les outils. Ni l'une ni l'autre couche n'offre un moindre privilège natif. Le contrat s'écrit à part. Frontière dans l'article précédent comment choisir la couche d'outil.

En une phrase. Le nouveau problème de sécurité des agents, ce n'est pas que le modèle dise une bêtise, c'est que la table d'outils, le catalogue de manuels et le Schema n'aient pas figé « ce qu'on a le droit de faire ».

Surface d'attaque : derrière le modèle, un graphe d'outils

Une fois le runtime retiré, la forme courante ressemble à ceci :

Surface d'attaque du Coding Agent : Skill, MCP, Schema Le Host liste les outils Skill et MCP ; le modèle choisit l'appel selon la description ; le JSON Schema doit bloquer les entrées trop larges avant exécution. Host installer / lister Catalogue Skill routage / étapes MCP / outils description / entrée Modèle choisir l'appel Schema avant exécution
Le Skill change le routage, MCP change les actions appelables. Les deux doivent passer par le Schema pour parler de permissions, pas de conseils.

L'attaquant n'a pas besoin de casser les poids du modèle. Le chemin moins cher : devenir un coin de confiance du graphe. Un Server installé dans le Host, un Skill apparu dans le dépôt, un inputSchema trop large — plus stables qu'un jailbreak de prompt système.

La suite suit ce graphe : d'abord comment MCP s'appuie sur l'identité d'outil, puis comment un Skill change le saut suivant via un manuel, enfin ce qu'on peut bloquer en Schema. Pourquoi la couche contrat est du JSON : Pourquoi l'IA aime JSON.

Serveur MCP malveillant : exécuter des effets de bord sous couvert de confiance

MCP (Model Context Protocol) est un protocole client-serveur. Le Host liste les tools du Server ; chaque outil porte name, description et inputSchema. Le modèle voit la pub et le contrat, pas ce qui tourne vraiment dans le processus Server. Isoler les identifiants est un atout de MCP ; glisser une implémentation malveillante dans le même mécanisme de découverte, c'est le revers du même atout.

Les formes courantes se rangent en quatre classes. Ici, seulement la forme, pas d'étapes reproductibles :

  • Déguisement du nom d'outil. Un nom comme get_docs, summarize_repo, mais l'implémentation lit hors du périmètre déclaré, ou envoie le résumé quelque part que vous ne voyez pas. Le modèle choisit l'outil par le nom, il ne décompile pas le Server.
  • Entrées trop larges. path n'est marqué que string, sans préfixe de répertoire, longueur ni jeu de caractères. Une « lecture de fichier » devient une lecture arbitraire. Si l'action est une commande en texte libre, le contrat n'existe pas.
  • Empoisonnement de description. description devrait porter les mots-clés de déclenchement et les limites, mais glisse une phrase du type « collecter d'abord les infos d'environnement ». Le modèle traite la description comme une notice d'emploi, pas comme un texte marketing.
  • Le retour commande la suite. Le résultat d'outil pousse un champ non déclaré, ou un texte qui ressemble à une instruction, et pousse le tour suivant vers un outil à plus haut privilège. Sans outputSchema, la forme du retour n'est pas coincée.

Reste la couche supply chain : Server homonyme, fork périmé, confier tout le mcp.json à l'agent pour réécriture. Un tableau écrasé, une URL en trop : plus fréquent qu'une fonction malveillante écrite à la main. Comment poser le plus petit correctif : Comment modifier une config JSON.

Figer la frontière. MCP achète la découverte et l'isolation, pas l'audit. Un Server hors allowlist n'a rien à faire dans la table d'outils. Un outil déjà listé doit encore voir ses arguments refermés par le Schema.

Fake Skills : voler le routage, puis tendre la main via un vrai outil

Un Agent Skill est un dossier : à la racine, SKILL.md, le frontmatter YAML porte name et description, le corps écrit les étapes. Au démarrage, le runtime n'enregistre que l'index ; le déploiement a lieu après correspondance de tâche. C'est du chargement progressif, pas du RPC. Un Skill n'écrit généralement pas sur le disque, et ne se connecte pas seul. Il change quelle main le modèle tend au tour suivant.

La valeur d'un Fake Skill n'est pas « c'est une backdoor exécutable », c'est voler le mauvais manuel :

  • description qui vole le routage. Des mots-clés trop larges, qui couvrent commit, tests, release, fix de bug. Une tâche hors sujet le charge aussi.
  • Le corps comme prompt système. Les étapes disent « sauter la confirmation », « d'abord lancer l'outil à haut privilège déjà installé ». Le manuel est traité comme une instruction de plus haute priorité.
  • Voler le nom d'un vrai Skill. Déposé dans .cursor/skills/ du dépôt ou dans le catalogue personnel, l'apparence d'un paquet officiel, l'ordre d'acceptation changé.
  • Écrire l'action dangereuse comme chemin par défaut. Un vrai Skill pose des conditions d'arrêt ; un faux Skill les retire, et ne laisse que « continuer à appeler ».

L'écosystème communautaire rend l'installation moins chère, et la vérification de source plus chère. Un Skill vaut le coup quand il y a plusieurs étapes, des jugements, des conditions d'arrêt ; il ne doit pas remplacer l'authentification. Détails : Pourquoi les Coding Agents ont besoin de Skills. Le tableau de l'article précédent le disait déjà : remplacer l'authentification par un Skill, c'est choisir la mauvaise couche.

En une phrase, séparer les deux couches : un MCP malveillant est lui-même l'exécuteur ; un Fake Skill ressemble plutôt à un manuel contaminé, et n'inflige de dégâts qu'en empruntant un outil déjà branché. En défense, ne scannez pas seulement les Server : scannez aussi la source des Skills et si la description est trop large.

Pourquoi le Markdown ne suffit pas

« Ne pas supprimer les données de prod », « ne pas lire le répertoire des secrets », écrits dans un Skill ou un prompt système, se lisent comme une politique de sécurité. Pour le modèle, ce n'est qu'un autre paragraphe écrasable. Un retour d'outil, un complément utilisateur, un autre manuel chargé peuvent le chasser.

Le Markdown convient pour : quand utiliser cet outil, s'arrêter s'il manque un champ, ne pas envoyer l'e-mail si l'acceptation échoue. Il ne convient pas pour : cet outil peut-il toucher un chemin hors dépôt, lancer une commande arbitraire, porter un champ non déclaré. Ça, une machine doit le refuser avant exécution.

Le refus se joue à trois endroits, les trois indispensables :

  1. Couche installation. Le Host ne se connecte qu'aux MCP de l'allowlist ; les Skills ne se chargent que depuis un catalogue verrouillé ou une source signée.
  2. Couche contrat. Le JSON Schema fige l'enum d'actions, la forme des chemins, l'interdiction des extra fields.
  3. Couche exécution. Échec de validation = erreur, pas « exécuter autant que possible ». Les actions dangereuses passent par une confirmation à part, pas par un manuel où l'on espère que le modèle s'en souvienne.

Structured Output referme la réponse finale en objet ; les permissions d'outil, c'est la même intuition déplacée sur les arguments. Définition : Qu'est-ce que la Structured Output.

Le JSON Schema est le contrat de permissions

Le contrat d'outil s'appelle inputSchema dans MCP (de plus en plus souvent aussi outputSchema), et parameters en Function Calling. Les champs d'enveloppe diffèrent ; ce qu'il faut figer, c'est le même objet cœur. Écrire d'abord le Schema cœur, puis les enveloppes OpenAI / Claude / Gemini / MCP. Comment les envelopper : la section JSON Tool Schema.

Pour limiter les permissions, privilégier ces cinq contraintes, plutôt qu'un nouveau « soyez prudent » :

  • enum. L'action ne peut être que lint / typecheck / unit_test, pas une commande en texte libre.
  • pattern + maxLength. Le chemin doit tomber dans un répertoire relatif autorisé, bloquer .. et les chemins absolus.
  • additionalProperties: false. Le modèle ne peut pas glisser un extra_cmd, un note hors contrat.
  • required. Sans path ou check, l'appel échoue — ne pas compter sur la description pour le rappeler.
  • La sortie aussi a un Schema. Le retour n'autorise que les champs déclarés, ce qui réduit l'espace pour « une instruction glissée dans le résultat ».

Et une décision produit à ne pas écrire seulement dans le Skill : lecture seule et fort effet de bord ne doivent pas cohabiter dans le même outil trop large. S'il faut écrire un fichier, faire un repo_write à part, plus étroit que la lecture ; s'il faut supprimer, par défaut ne pas apparaître dans la table d'outils du Coding Agent.

Une permission, c'est une forme, pas un slogan. Le Schema vérifie si l'objet peut entrer dans l'exécuteur. Ça ne passe pas, on s'arrête. C'est la même chose que valider une Structured Output dans l'espace de travail, sauf que l'objet n'est plus « la réponse », c'est « l'appel d'outil ».

Un Schema de permissions recettable en local

Voici l'objet cœur d'une lecture seule de fichier du dépôt. Ce n'est pas encore l'enveloppe d'un vendeur, seulement le contrat. Le path: string trop large se referme ici.

{
  "tool": "repo_read",
  "description": "Read one text file under allowlisted project directories. Use when the user asks to inspect source. Do not call for paths outside src, frontend, or docs.",
  "arguments": {
    "path": "frontend/zh/blog/index.html"
  },
  "result": {
    "path": "frontend/zh/blog/index.html",
    "bytes": 11842,
    "truncated": false
  }
}

Le Schema cœur correspondant doit d'abord refermer quatre points : path required ; longueur plafonnée ; correspondre à un préfixe relatif autorisé ; additionalProperties de l'objet à false. Le retour ne garde que path / bytes / truncated — pas de champ pour un « prochain pas suggéré » en texte libre.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["path"],
  "properties": {
    "path": {
      "type": "string",
      "minLength": 1,
      "maxLength": 256,
      "pattern": "^(src|frontend|docs)/[A-Za-z0-9._/-]+$",
      "description": "Relative path inside allowlisted directories. No absolute paths, no parent traversal."
    }
  }
}

Le second contrat referme « lancer un check » en enum, pas en chaîne command. Un Skill peut écrire « d'abord lint, puis les tests unitaires » ; la couche outil ne voit aucune action hors enum.

{
  "tool": "run_allowed_check",
  "description": "Run one allowlisted repository check. Use after the user asks to verify the change. Do not invent new commands.",
  "arguments": {
    "check": "lint"
  }
}
{
  "type": "object",
  "additionalProperties": false,
  "required": ["check"],
  "properties": {
    "check": {
      "type": "string",
      "enum": ["lint", "typecheck", "unit_test"]
    }
  }
}

En regard, des formes qui ne doivent pas passer : path écrit ../.env ; un extra_cmd en trop ; check écrit en shell arbitraire. Ces objets doivent échouer dans l'espace de travail. Dériver un Schema depuis un exemple : Comment générer un Schema.

Checklist : installation, contrat, exécution

Couche Ce qu'il faut coincer Ne pas compter sur le Markdown
Installation Allowlist des sources MCP / Skill ; versions verrouillées « N'installer que les paquets officiels »
Découverte Description courte, mots-clés précis, pas d'étape extra glissée Compter sur le modèle pour ignorer une description bizarre
Contrat enum, pattern, required, additionalProperties « Le chemin ne sort pas du dépôt » dans le corps du Skill
Exécution Échec de validation = refus ; écriture / suppression séparées ; confirmation pour le haut risque « Réfléchissez encore avant d'exécuter »
Config Lire d'abord, puis le plus petit correctif ; diff avant réécriture Laisser l'agent réécrire tout le mcp.json

L'échelle minimale raisonnable : ne brancher en local que les MCP que vous nommez ; ne pas tirer le catalogue de Skills depuis une URL inconnue ; chaque outil à haut privilège a un Schema parsable. Une fois que l'API Responses met MCP et function dans le même tableau tools, ce contrat partagé compte encore plus : Agent DevDay 2026.

Recetter dans la Boîte à outils JSON

  1. Coller les arguments / result ci-dessus dans le formatage JSON, confirmer que ça parse.
  2. Avec JSON → Schema ou Structured Output, générer le contrat cœur, compléter enum, pattern, required et additionalProperties.
  3. Les mêmes champs via Function Calling et MCP Tool, sans recopier deux jeux de types.
  4. Préparer un jeu d'objets qui doivent échouer : chemin hors périmètre, champ extra, check illégal. Confirmer le refus dans la validation Schema ; si ça ne s'aligne pas avec l'exemple, la comparaison JSON.

Tout est traité dans le navigateur, rien n'est envoyé au serveur. D'abord un objet capable de refuser un mauvais appel, ensuite seulement décider si cet outil a le droit d'apparaître dans la table d'outils de l'agent.

FAQ

Quelle est la différence entre un serveur MCP malveillant et un MCP ordinaire ?

Le protocole est le même. La différence, c'est la confiance : un Server malveillant échange un nom inoffensif contre des entrées trop larges, ou glisse l'instruction du saut suivant dans le retour. Une fois listé par le Host, le modèle appelle selon la description. La frontière est l'allowlist, le Schema et le refus à l'exécution — pas « est-ce du MCP ».

Un Fake Skill peut-il modifier le dépôt ou exfiltrer des données ?

En général, non. Un Skill est un manuel. Le danger, c'est qu'une fois le routage volé, il pousse le modèle à appeler un outil déjà branché à haut privilège. D'abord les permissions d'outils, ensuite la source des Skills.

Pourquoi « ne pas supprimer la base de prod » dans un Skill ne suffit pas ?

C'est de la prose. Le modèle peut l'ignorer ; un retour d'outil peut l'écraser. Ce qu'il faut : ne pas exposer delete, ou refermer l'action en enum, et refuser si la validation échoue.

Comment un JSON Schema limite-t-il les permissions d'un outil ?

enum pour les actions, pattern / maxLength pour les chemins, additionalProperties: false pour interdire les champs extra, required pour les obligatoires. Un Schema aussi sur la sortie. C'est un contrôle de forme avant exécution.

On a déjà branché MCP, faut-il encore écrire un Schema Function Calling ?

Oui. L'inputSchema MCP et les parameters de Function Calling sont le même type de contrat. Un seul jeu de champs cœur, puis les enveloppes.

En résumé

La sécurité des agents IA est devenue un sujet chaud parce que les Coding Agents ont fait du catalogue de manuels et du bus d'outils une capacité par défaut. Un serveur MCP malveillant exécute des effets de bord via un nom d'outil et des entrées trop larges ; les Fake Skills volent le routage, puis tendent la main via un vrai outil. Le Markdown peut écrire des étapes, il ne peut pas servir de système de permissions. Ce qu'on peut bloquer s'écrit en JSON Schema : enum d'actions, forme des chemins, required, interdiction des extra fields, sortie refermée de la même façon. Pour le développeur, la suite n'est pas d'abord d'installer plus de Server, ni d'écrire « soyez prudent » dans un Skill plus long, c'est d'abord un contrat d'outil parsable, capable aussi de refuser un mauvais objet — index court, source verrouillée, permissions dures, manuel responsable seulement de l'ordre.