# Sécuriser une configuration MCP lors des modifications d’un dépôt

Une configuration MCP de dépôt est un élément de déploiement exécutable qui se cache derrière l’apparence d’un petit fichier JSON. Elle peut sélectionner un binaire, télécharger un paquet, choisir l’endroit où le processus démarre, lui transmettre des données et lui donner accès au contenu du dépôt. La sécurité de la configuration MCP devient donc un sujet d’examen des pull requests avant même de devenir un sujet de sécurité des agents.

J’ai vu des équipes passer une heure à débattre de la possibilité pour un agent d’appeler un outil, puis fusionner un lanceur capable d’installer tout ce que le nom d’un paquet public désignera cet après-midi-là. La liste des outils est la partie visible. Le processus qui crée cette liste a déjà franchi une limite.

## La configuration est une instruction de démarrage de processus

Une modification de `.mcp.json` mérite la même attention qu’une modification d’un script de compilation, car un client doit transformer ses champs en processus local. Le schéma exact varie selon l’hôte, mais la structure courante est familière : une commande, un tableau d’arguments, des entrées d’environnement facultatives et parfois un répertoire de travail ou un réglage de transport.

Ce processus démarre avec les permissions du développeur ou de l’hôte de l’agent qui le lance. Il peut lire les fichiers accessibles à cet utilisateur, créer des fichiers dans les emplacements où il peut écrire, hériter de certaines valeurs d’environnement et effectuer des requêtes réseau, sauf si le système d’exploitation ou un autre contrôle les bloque. Le fait que son rôle final soit « serveur MCP » ne réduit pas les permissions ordinaires de ce processus.

Les réviseurs déduisent souvent la sécurité du nom du paquet ou du caractère limité des outils annoncés. Aucune de ces déductions n’est fiable. Un serveur nommé `issue-reader` peut n’exposer que des opérations de lecture après son démarrage, alors que son lanceur appelle un wrapper shell qui lit un jeton dans un répertoire personnel. Un serveur au nom plus large peut être un binaire versionné dans le dépôt qui fait exactement ce que le dépôt documente. Commencez par les faits, pas par les noms.

La ligne dangereuse est souvent visuellement banale :

```json
{
  "mcpServers": {
    "docs": {
      "command": "npx",
      "args": ["-y", "@example/docs-mcp"]
    }
  }
}
```

Cela ne signifie pas que `npx` ou un paquet de registre est automatiquement inacceptable. Cela signifie que la pull request délègue une partie de la chaîne d’approvisionnement exécutable à un moment ultérieur, sur chaque machine qui lance le serveur. Le réviseur doit savoir quelle version du paquet sera exécutée, d’où elle provient, ce qu’elle installe et ce qu’elle fait au premier démarrage.

Une configuration versionnée dans le dépôt crée aussi une pression sociale. Une fois le fichier présent dans le dépôt, un nouveau contributeur voit un serveur présenté comme faisant partie de la configuration du projet et suppose qu’il a déjà été examiné. C’est précisément pour cette raison que cette catégorie de fichiers a besoin d’un responsable et d’une habitude de revue.

## Les messages MCP et les lanceurs du dépôt sont deux couches différentes

La spécification du Model Context Protocol décrit les échanges entre un client et un serveur MCP, notamment l’initialisation, la découverte des outils, les appels d’outils et le comportement du transport. Elle ne crée pas un format `.mcp.json` universel et ne rend pas inoffensive une configuration client. Chaque client décide où il lit la configuration et comment il lance un serveur local via stdio.

Cette distinction semble théorique jusqu’à ce qu’une revue tourne mal. Le protocole peut limiter un serveur à un ensemble déclaré de méthodes une fois la connexion établie. La configuration de l’hôte décide quel programme aura la possibilité d’annoncer ces méthodes. Si vous examinez uniquement le comportement visible par le protocole, vous commencez votre revue après l’action du lanceur.

Gardez deux questions séparées :

- Quelles actions le serveur connecté peut-il demander ou effectuer avec ses outils ?
- Que fait le lanceur local avant que le client n’ait établi la connexion avec le serveur ?

La première question concerne les permissions de l’agent, le flux d’approbation et la conception des outils. La seconde concerne la confiance accordée au dépôt, l’exécution des processus, la provenance des dépendances et l’étendue du système de fichiers. Une bonne revue répond aux deux questions, mais ne laisse jamais la réponse à l’une remplacer celle de l’autre.

La documentation du protocole encourage les clients et les serveurs à négocier leurs capacités. C’est utile pour la compatibilité, mais cela n’établit pas la confiance envers une commande du dépôt. La négociation des capacités peut indiquer au client qu’un serveur prend en charge certains outils. Elle ne peut pas prouver que le chemin de commande, le contenu du paquet ou le hook de démarrage méritent l’accès du développeur.

Cela explique aussi pourquoi un serveur familier peut rester risqué dans une nouvelle configuration. Le code du serveur peut être inchangé, alors qu’un argument modifié le dirige vers un autre endpoint, une autre source d’identifiants ou un autre répertoire de projet. Le contexte de lancement fait partie du comportement.

## Lisez la commande lancée avant les outils annoncés

Examinez la résolution de la commande comme une chaîne, et non comme la première chaîne de caractères du JSON. Demandez quel programme l’hôte exécutera réellement après avoir résolu la commande avec l’environnement courant. `python`, `node`, `uvx`, `npx`, `bunx`, `sh` et un chemin relatif signifient tous qu’un autre résolveur choisira l’exécutable final.

Un chemin absolu direct est le plus facile à examiner, même s’il nécessite toujours une explication de sa provenance. Un chemin relatif au dépôt peut être acceptable si le dépôt contient le fichier et si la revue habituelle du code le couvre. Un simple nom de commande exige d’examiner le comportement du `PATH` du développeur. Une commande shell appelle le plus haut niveau de méfiance, car les guillemets, l’expansion, les pipes, les redirections et la substitution de commande dissimulent davantage d’exécution qu’un simple tableau d’arguments.

Les arguments méritent leur propre passage. Les réviseurs lisent souvent les deux premiers arguments, reconnaissent un nom de paquet et passent à la suite. Lisez chaque élément. Un argument peut sélectionner un fichier de configuration situé hors du dépôt, choisir un répertoire de sortie, activer un chargeur de plugins, pointer vers un registre différent ou transformer une commande inoffensive en évaluateur de scripts.

Observez la différence entre ces deux entrées :

```json
{
  "command": "node",
  "args": ["tools/mcp-server.js", "--root", "."]
}
```

```json
{
  "command": "node",
  "args": ["tools/mcp-server.js", "--config", "../../shared/runtime.json"]
}
```

La seconde entrée peut être légitime. Elle crée aussi une dépendance envers un fichier situé en dehors de la limite du dépôt, que la pull request ne montre peut-être pas. Si le serveur lit des instructions ou des identifiants dans ce fichier, les réviseurs ne peuvent pas évaluer la modification à partir du diff seul. Demandez à l’auteur d’ajouter la configuration référencée à la revue, de la remplacer par un chemin explicite dans le dépôt ou d’expliquer pourquoi ce fichier externe est nécessaire.

N’acceptez pas une réponse vague comme « le paquet s’en charge ». Le paquet peut le faire correctement aujourd’hui, mais la modification lui attribue tout de même une autorité. Une réponse utile nomme le programme, sa version, ses entrées et les fichiers attendus. Si l’auteur n’a pas exécuté la commande exacte depuis un dépôt fraîchement cloné, il décrit une intention plutôt qu’un comportement.

Adoptez une règle simple pour les wrappers shell : refusez-les, sauf si le dépôt a un besoin concret qu’un tableau d’arguments ne peut pas exprimer. Un wrapper peut sembler pratique parce qu’il définit une valeur d’environnement et démarre deux assistants. Il rend aussi la revue dépendante des règles d’analyse du shell et des commandes auxquelles le wrapper donne accès. Si ce wrapper est indispensable, placez la configuration dans un script contrôlé portant un nom clair, puis examinez ce script comme du code applicatif.

## Les lanceurs de paquets transforment le démarrage en événement de chaîne d’approvisionnement

Un lanceur de paquets peut télécharger et exécuter du code au premier démarrage du serveur. Un simple nom de paquet ne constitue donc pas une revue complète de la dépendance. C’est pourquoi les formes `npx -y package`, `uvx package` et leurs équivalents exigent davantage d’attention qu’une commande qui appelle un fichier versionné dans le dépôt.

L’argument habituel en faveur d’un lanceur sans version fixée est la commodité. Les contributeurs n’ont rien à installer manuellement et le projet semble rester à jour. En contrepartie, le dépôt ne précise plus le code exact qu’il demande à chaque contributeur d’exécuter. Une nouvelle version du paquet, une dépendance modifiée ou un changement de routage du registre peut modifier le comportement sans nouvelle pull request.

Au minimum, rendez la version explicite et rendez la source compréhensible :

```json
{
  "mcpServers": {
    "schema-checker": {
      "command": "npx",
      "args": ["-y", "@acme/schema-mcp@1.4.2"],
      "cwd": "${workspaceFolder}"
    }
  }
}
```

Figer la version ne certifie pas le paquet. Cela stabilise la cible de la revue. Un réviseur peut examiner cette version, la comparer à la précédente et exiger une pull request explicite pour toute mise à jour. Utilisez le fichier de verrouillage du gestionnaire de paquets, les données d’intégrité ou un artefact versionné dans le dépôt lorsque la chaîne d’outils environnante le permet. Une chaîne de version sans moyen de vérifier le contenu téléchargé laisse une faille, mais reste préférable à un nom sans limite.

Ne confondez pas le cache du gestionnaire de paquets avec une revue. Un cache change seulement l’endroit d’où proviennent les octets sur une machine. Il n’indique pas à un réviseur quels octets recevront les autres contributeurs, si des hooks d’installation seront exécutés ni si une machine vierge se comportera de la même façon.

Posez une question un peu gênante qui révèle de nombreuses modifications fragiles : que se passe-t-il si le paquet est absent ? Si la réponse est « il s’installe tout seul », la configuration possède un chemin d’accès au réseau et d’exécution de code. Si la réponse est « le lancement échoue et demande d’installer une dépendance documentée », l’équipe a choisi une configuration plus lente, mais plus visible. Aucune réponse n’est intrinsèquement correcte. Faire comme si la différence n’existait pas est une erreur.

## Les répertoires de travail et les entrées d’environnement définissent l’étendue réelle

Le champ `cwd` indique à un processus où commencer à chercher les fichiers relatifs, et ce choix contrôle souvent bien plus de choses qu’on ne l’imagine. De nombreux outils découvrent la configuration du projet, les environnements de langage, les fichiers d’exclusion, les identifiants et les plugins en remontant depuis le répertoire de travail. Un serveur qui démarre à la racine du dépôt voit un environnement différent de celui qui démarre dans un répertoire de fixtures dédié.

Définissez le répertoire de travail intentionnellement. Si un serveur a seulement besoin des spécifications d’API générées dans `tools/specs`, ne le démarrez pas depuis un répertoire parent qui contient des éléments de déploiement et des notes de développement privées. S’il doit examiner l’intégralité du dépôt, indiquez-le dans la pull request. Le but n’est pas de rendre chaque chemin minuscule. Il est de faire correspondre l’accès approuvé par le réviseur à celui que reçoit réellement le serveur.

Les entrées d’environnement ont une importance similaire. Certaines ne sont que des réglages opérationnels inoffensifs, comme la langue ou un port. D’autres sélectionnent un registre de paquets, étendent un chemin de recherche de modules, activent un mode de débogage qui écrit le contenu des requêtes ou transportent des identifiants. Une configuration qui utilise `${TOKEN}` masque la valeur du jeton dans le fichier, mais la transmet tout de même à un processus enfant.

Évitez les identifiants dans les configurations MCP de dépôt lorsque le serveur peut fonctionner via une limite d’action distincte. Si un serveur a réellement besoin d’un secret, documentez sa source, son objectif et la possibilité que le processus le transmette à des processus enfants. Ne placez pas un exemple de jeton de production dans un commentaire ou dans la transcription copiée d’un shell. Les développeurs copieront l’exemple le plus facile à faire fonctionner.

Les valeurs d’environnement héritées méritent aussi votre attention. Un processus peut en hériter bien davantage que ce que liste le JSON. L’hôte décide de ce qu’il transmet, mais l’auteur du dépôt peut choisir une commande qui lit des emplacements d’exécution standard. C’est une raison supplémentaire de privilégier des lanceurs directs et limités plutôt que des shells généraux et des lanceurs de paquets.

## Un diff de pull request peut révéler le graphe d’exécution

Vous pouvez examiner la plupart des modifications de configuration sans exécuter de code non fiable, en transformant le diff en processus et en entrées qu’il implique. Commencez par un diff ciblé plutôt que par la vue du fichier rendu, afin que les arguments supprimés et les chemins modifiés restent visibles :

```sh
git diff --check
git diff -- .mcp.json
```

La première commande signale les erreurs d’espacement lorsqu’elle en trouve. La seconde doit afficher chaque ligne ajoutée, supprimée ou modifiée dans le chemin de configuration. Si le dépôt stocke le fichier ailleurs ou le génère, adaptez le chemin et demandez aussi la modification du générateur. Une configuration générée sans son code source ne permet pas une revue complète.

Prenez cette petite modification :

```diff
 "mcpServers": {
   "release-notes": {
-    "command": "node",
-    "args": ["tools/release-notes-server.js"],
-    "cwd": "${workspaceFolder}"
+    "command": "npx",
+    "args": ["-y", "release-notes-mcp"],
+    "cwd": ".."
   }
 }
```

L’histoire visible dit que l’équipe a remplacé un assistant local par un serveur publié. Le graphe d’exécution révèle davantage. Le client résout `npx` avec le `PATH` du développeur. `npx` peut télécharger `release-notes-mcp` et ses dépendances. Le paquet démarre en dehors de la racine du dépôt, car `cwd` pointe maintenant vers le répertoire parent. Le serveur peut y découvrir une configuration, y écrire des fichiers de cache ou lire des projets voisins. Chaque flèche exige une réponse avant la fusion.

Une revue rigoureuse demande des preuves dans cet ordre :

1. Identifiez l’exécutable final et le paquet ou fichier du dépôt exact qui le fournit.
2. Dressez la liste de chaque chemin de fichier, URL, registre et valeur d’environnement susceptible de modifier le démarrage.
3. Indiquez le répertoire de travail du processus et les répertoires que le serveur lit ou modifie dans son fonctionnement normal.
4. Vérifiez ce qui se passe sur une machine vierge avant la création du moindre cache de paquet.
5. Comparez l’objectif annoncé de l’outil avec les permissions dont le lanceur a réellement besoin.

Cette séquence produit un élément concret, pas une formalité. Elle évite l’échec habituel où un réviseur approuve la description d’un outil alors qu’un installateur, un sélecteur d’environnement ou un répertoire parent s’est glissé dans une modification d’une seule ligne.

Si vous devez exécuter une configuration pour la valider, faites-le avec un compte jetable ou dans un environnement isolé, sans identifiants de production et avec un cache de paquets vide. Notez l’exécutable résolu, la version du paquet, les hôtes contactés et les fichiers écrits. Une capture d’écran montrant une découverte réussie des outils est une preuve faible, car elle omet la partie qui a créé le plus de risques.

## Les descriptions d’outils ne compensent pas un lanceur trop large

Un schéma d’outil limité n’efface pas ce qui s’est passé avant la négociation MCP. Les équipes traitent régulièrement la sortie de `tools/list` d’un serveur comme un inventaire des permissions, puis concluent qu’elle semble sûre parce qu’elle propose `search_docs` et `read_status`. Cet inventaire décrit l’interface après l’initialisation. Il en dit peu sur l’installation des paquets, la découverte de configuration, la télémétrie, les processus enfants ou les fichiers lus au démarrage.

Cette distinction fonctionne dans les deux sens. Un serveur doté d’un outil capable d’écrire peut être acceptable lorsque son lanceur est figé, local, documenté et limité au projet prévu. Un serveur en lecture seule peut être inacceptable lorsqu’une configuration de dépôt exécute silencieusement un paquet modifiable téléchargé sur le réseau, avec accès à l’espace de travail plus large du développeur.

Demandez à l’auteur de décrire le cycle de vie en termes simples : ce qui démarre, ce qui est lu avant la connexion, ce qui est téléchargé le cas échéant et ce que le serveur peut toucher une fois connecté. La réponse doit tenir dans la description d’une pull request. Si elle nécessite une longue enquête, la configuration est trop indirecte pour une revue courante.

Ne laissez pas « nous faisons confiance à ce fournisseur » clore la discussion. La confiance compte, mais elle ne fige pas une version, ne limite pas un répertoire de travail et ne montre pas si la commande utilise un registre différent. Ce sont des contrôles distincts, avec des modes de défaillance distincts.

## La propriété du dépôt a besoin d’un filet de sécurité à l’exécution

La revue du dépôt empêche des instructions de lancement dangereuses de devenir une configuration normale du projet. Les contrôles à l’exécution restent importants, car une dépendance approuvée, un prompt malveillant ou un utilisateur demandant trop de choses à un agent peut exploiter une configuration approuvée.

Gardez la limite claire. Le dépôt contrôle ce qu’il propose de lancer. La personne qui utilise la machine contrôle la possibilité pour une session d’agent donnée d’agir. Les actions sensibles ont besoin de traces permettant à l’opérateur de reconstituer ce que l’agent a fait et de révoquer la session si l’exécution tourne mal.

Sallyport garde les identifiants API et SSH hors du processus de l’agent, tandis que son autorisation de session et son journal d’activité offrent à l’opérateur un espace distinct pour approuver et examiner les actions. Cela ne rend pas inoffensive une commande de dépôt risquée, raison pour laquelle l’examen de la configuration passe toujours en premier.

Pour les équipes qui utilisent une autre limite, appliquez le même test. Un développeur peut-il voir quel processus d’agent a demandé l’accès ? Peut-il arrêter ce processus sans interrompre un travail sans rapport ? Peut-il distinguer une action tentée d’une action réussie ? Si la réponse est non, une mauvaise configuration a davantage de temps pour causer des dégâts après sa fusion.

## Traitez ces fichiers comme du code d’exécution géré

Un dépôt devrait attribuer des responsables de code, ou appliquer une règle de revue équivalente, à la configuration MCP, à ses scripts wrapper et à tous les manifestes de paquets qui déterminent ce que le lanceur installe. Le responsable n’a pas besoin d’être spécialiste de la sécurité. Il doit avoir assez de contexte pour demander pourquoi cette modification démarre ce programme avec cet accès.

Gardez les configurations petites. Un serveur par objectif est plus facile à examiner qu’un assistant universel qui atteint tous les systèmes utilisés par un développeur. Ajoutez à la configuration les besoins inhabituels, en prose ordinaire : commande attendue, politique de version du paquet, répertoire de travail, accès réseau requis et entrées prévues. Cette note transforme les connaissances tacites en éléments que le prochain réviseur peut remettre en question.

Le test pratique est simple. Donnez le diff à un développeur expérimenté qui ne connaît pas la fonctionnalité et demandez-lui d’expliquer ce qui sera exécuté sur une machine vierge, où cela s’exécutera et ce qui sera accessible. S’il ne peut pas répondre à partir de la pull request et du code référencé, la modification doit encore être améliorée avant d’intégrer le dépôt.
