Accueil / Articles

Aucun résultat concernant la quantité de code : audit de 300 paquets npm MCP

Un audit des fichiers compressés contenant 300 paquets de serveur MCP sur npm a révélé 217 dont le code en temps de exécution n’a jamais été lu par le scanneur. C’est pourquoi un résultat « propre » nécessite l’attachement d’un certificat de couverture.

Éditeur : REACTAPP.TOP8 min de lecture1871 mots

Sources: I Audited 300 MCP-Related npm Packages. My Scanner Couldn’t Inspect Runtime Code in 217. So I created Driftward.

Trois cents paquets npm portant le nom MCP ont été récupérés du registre et examinés sous forme de fichiers tarball publiés, sans jamais avoir été installés ou exécutés. Le résultat le plus frappant n’avait rien à voir avec la présence de logiciels malveillants. Il s’agissait d’une faille de couverture : pour 217 des 300 paquets (72,3 %), le fichier tarball contenait du code d’exécution, mais chaque fichier correspondant se trouvait dans un répertoire que le scanner évite délibérément, comme dist/, build/ ou les fichiers minifiés. L’outil pouvait lire un manifeste et un README, afficher un résultat rassurant, sans jamais examiner le code qui serait réellement exécuté.

On comptait 217 cas de lacunes de couverture, 80 paquets contenant du code d’exécution valide, et 3 cas où aucun code d’exécution n’a été trouvé du tout.

Cela ne signifie pas pour autant que 72,3 % des paquets MCP soient invisibles aux analyses statiques en général. Les bundles peuvent être analysés, les cartes de source permettent parfois de restaurer le code original, et des outils spécialisés peuvent déobfusquer ou interpréter le JavaScript généré. La conclusion est plus ciblée et plus utile :

Un résultat statique négatif n’a aucun sens s’il ne précise pas également quelle quantité de code exécutable a réellement été examinée.

Le contexte est important ici. Le résultat indique simplement qu’un scanner particulier n’a rien trouvé de pertinent à analyser dans 217 fichiers tar. Il ne prétend pas pour autant que ces paquets soient malveillants ou dangereux, ni que d’autres analyseurs ne puissent pas les examiner.

Pourquoi MCP augmente le niveau de risque

Un serveur MCP local n’est pas simplement une bibliothèque importée par une application. Grâce au mécanisme de transport stdio habituel, le client le lance en tant que processus enfant. La politique de sécurité du projet stipule que le serveur dispose des mêmes privilèges au niveau de l’environnement que le client, et que le mécanisme de transport stdio de l’SDK ne propose aucune sandbox. Si l’agent peut accéder à des identifiants, à une adresse réseau ou à un outil local, un serveur sans sandbox peut probablement y accéder également.

Les directives officielles de sécurité MCP recommandent de lancer les serveurs locaux avec des privilèges minimaux et de limiter l’accès au système de fichiers ainsi qu’au réseau. Il revient à l’hôte ou à l’opérateur d’assurer cette isolation. L’examen du code source reste utile, mais la question restante est de savoir ce qu’il peut réellement indiquer sur les éléments que les développeurs installent effectivement.

Comment l’échantillon a été créé

Il s’agissait d’un échantillon à des fins pratiques et non d’une étude exhaustive de l’écosystème.

Le collecteur a effectué 11 recherches dans le registre npm, en utilisant des expressions telles que mcp server, model context protocol, mcp connector et modelcontextprotocol. Chaque recherche a donné jusqu’à trois pages de 100 résultats ; les doublons ont été supprimés, seuls les noms contenant « mcp » ont été conservés, et le nombre de résultats a été limité à 300. Le classement des recherches détermine qui est pris en compte : un même éditeur peut apparaître plusieurs fois, et un nom contenant « mcp » ne garantit pas pour autant l’existence d’un serveur MCP.

Pour chaque package, la dernière version disponible au moment de la collecte a été identifiée, le fichier tarball du registre a été téléchargé directement, la valeur d’intégrité publiée par npm a été vérifiée, et ses composants ont été chargés en mémoire. Toutes les 300 vérifications d’intégrité ont réussi, sans erreur de téléchargement ou de traitement. Aucun package n’a été installé, déballé dans un environnement exécutable ou lancé.

Le scanner est conçu pour assurer une grande précision. Il recherche des API réseau, des hôtes URL littéraux, des appels de sous-processus, des mentions de chemins de identifiants ainsi qu’un ensemble restreint de phrases d’injection, tout en ignorant le code de test, les fichiers de configuration, les exemples, les dossiers de sortie compilée, les fichiers minifiés, les cartes sources et tout fichier dépassant 1 MB. Ces omissions réduisent le bruit, mais créent également des points aveugles quantifiables.

Résultats obtenus

La couverture doit figurer à côté des résultats, et non dans une note de bas de page. La première ligne des résultats représente la principale découverte, et chaque autre ligne doit être lue à son éclairage. Un nombre nul de références à des identifiants, par exemple, ne signifie pas que le corpus ne traite jamais d’identifiants ; cela indique simplement que le scanner n’en a trouvé aucun dans les parties de code qu’il a jugées pertinentes, même si 217 paquets ne lui ont fourni rien de pertinent à analyser.

Quatre chiffres se détachent :

  • Code en exécution mais zéro fichier en exécution qualifié : 217 paquets.
  • Sur ces 217, 107 contenaient une carte de sources et les 110 restants ne l’avaient pas.
  • API réseau ou signal literal-host dans le code qualifié : 50 paquets.
  • Correspondances de type injection de commande : 2, et l’examen manuel a confirmé que les deux étaient des faux positifs.

Deux fausses alertes

Des phrases de type injection ont été trouvées dans deux paquets, et une lecture manuelle a montré que les deux étaient des textes explicatifs. L’un, le serveur de documentation Mastra à la version 1.2.22, contenait des exemples d’évaluation avec des chaînes de caractères telles que IGNORE PREVIOUS INSTRUCTIONS. L’autre, le serveur MCP REMnux à la version 0.1.71, avertit que les échantillons de logiciels malveillants peuvent contenir des chaînes d’injection de commande et indique aux analystes comment les traiter.

Aucune preuve d’une tentative de manipulation d’un agent n’a non plus été trouvée. Il s’agit d’une faiblesse connue du balayage par mots-clés : la documentation de sécurité, les fichiers de configuration des tests et les instructions malveillantes peuvent contenir des chaînes identiques, et seul le contexte permet de déterminer laquelle est la bonne.

Un package de test malveillant qui a obtenu zéro point

Un package servait à contrôler le taux de couverture. malicious-mcp-server 1.5.0 se présente comme un serveur MCP délibérément malveillant destiné aux tests bout en bout. Son fichier tar contenait trois fichiers : package.json de 970 octets, README.md de 742 octets, et dist/index.js de 610 358 octets.

Le balayage en première passe a lu les deux petits fichiers de texte, a ignoré dist/index.js, n’a détecté aucun signal comportemental et a attribué au package un score de risque de 0. Le comptage des tokens bruts dans le bundle ignoré a ensuite révélé six occurrences de exec, deux d’eval, 52 occurrences de base64 et trois occurrences de raw.githubusercontent.com.

Ainsi, le verdict honnête n’était pas un certificat de santé sans défaut. Il s’avérait que la couverture en temps de exécution ne pouvait pas être établie conformément aux règles en vigueur. Ces chaînes de caractères ne prouvent ni l’exécution ni la malveillance en soi, et le manifest du paquet fournit le contexte manquant. Le problème est que l’ensemble portant tout le risque n’a jamais atteint l’étape qui aurait généré zéro.

La technique n’était pas en cause

Omettre les résultats générés relève d’une décision de conception, et non d’une règle de la discipline. Un autre analyseur pourrait parser le JavaScript intégré, examiner l’arbre syntaxique, déballer les modules, extraire les cartes de source, suivre les métadonnées des dépendances, comparer l’origine du code, ou exécuter des règles conçues pour du code minifié. Chacune de ces méthodes sacrifie un peu de vitesse et de précision pour obtenir une portée plus large.

L’erreur réside dans le fait de ne pas considérer les résultats comme une seule condition, car celle-ci peut correspondre à au moins trois situations différentes :

  1. Le code concerné a été lu et aucune règle n’a été déclenchée.
  2. Le code concerné a été lu, mais les règles n’ont pas pu reconnaître ce comportement.
  3. Le code concerné n’a jamais été lu.

Seul le premier cas constitue un résultat positif significatif, et même celui-ci ne prouve pas la sécurité.

Ce qu’un bon rapport doit révéler

Un analyseur ne devrait pas fusionner les cas où rien n’a été trouvé et ceux où aucune inspection n’a eu lieu en un seul résultat positif. Pour les artefacts de paquet, une attestation de couverture doit accompagner chaque résumé des résultats :

  • Le nombre de fichiers en temps de exécution existants, le nombre ayant été analysés, ainsi que le nombre d’octets couverts.
  • Tout ce qui a été exclu, classé par cause : chemins générés, limites de taille, formats non pris en charge, échecs de parsing.
  • La présence de cartes sources et la possibilité de récupérer des sources réelles à partir d’elles.
  • Le jeu de règles et la version de l’analyseur ayant produit le résultat.
  • Un état de résultat distinct pour l’absence de trouvailles dans le code inspecté, par rapport à une couverture en temps de exécution indisponible.
  • C’est un petit changement dans la présentation des résultats qui apporte une grande amélioration de la confiance, car les examinateurs peuvent déterminer si un résultat silencieux constitue une preuve ou n’est que le signe d’un manque de visibilité.

    Pourquoi le comportement en temps de exécution reste important

    L’examen statique ne représente qu’un instantané. Le comportement peut changer suite à une mise à jour, et une commande non enregistrée comme npx package-name peut faire référence à du code nouveau tandis que la configuration MCP locale reste inchangée. Les contrôles en temps de exécution complètent donc le travail statique au lieu de concurrencer celui-ci. Chaque serveur local doit fonctionner sous sa propre identité en suivant une politique de moindres privilèges. Ses destinations réelles et ses capacités doivent être enregistrées, ainsi qu’une ligne de base qui doit être approuvée explicitement. Les exécutions ultérieures sont ensuite comparées à l’historique propre de ce serveur, afin de détecter tout écart d’identité dû à des modifications de configuration, et de constater que même une mise à jour de paquet effectuée selon une définition inchangée reste visible en cas de changement des capacités en temps de exécution.

    Outil open source suivant ce modèle : il enveloppe chaque serveur MCP en tant que principal distinct, par exemple mcp:github, avec une politique propre, une ligne de base signée et un registre des écarts. Il s’agit d’un mécanisme de secours en temps de exécution pour l’examen des sources, et non d’un substitut. Le support sur les plateformes est inégal : sous macOS, Seatbelt relie les sockets sortants à un proxy de boucle locale, tandis que sous Linux, bubblewrap offre jusqu’à présent une confinement du système de fichiers et des processus ainsi que des variables d’environnement de proxy, sans équivalent en termes de liaison réseau stricte.

    De nouvelles recherches vont dans le même sens. TrustShiftProbe, un préprint d’août 2026, modélise des serveurs MCP qui fonctionnent normalement puis changent de comportement à un certain horizon de confiance, et il teste des défenses en temps de exécution basées sur des lignes de base comportementales. Un préprint n’est pas un consensus, mais son modèle de menace tenant compte du temps est le bon, car la confiance peut s’éroder après avoir effectué un examen.

    Limites de l’audit

    • La sélection a été effectuée à partir du classement des recherches et du filtrage par nom, de sorte que l’échantillon n’est ni aléatoire ni représentatif.
    • Toutes les données correspondent au 1er septembre 2026, et à la fois le contenu des npm et les résultats de recherche évoluent.
    • Ce qui a été mesuré, ce sont les fichiers tarball, et non le fait que chacun d’eux constitue un serveur MCP fonctionnel.
    • L’expression « scanner blind » s’applique aux règles d’exclusion de ce scanner, et non à l’analyse statique dans son ensemble.
    • Le comptage des cartes de source a été effectué, mais personne n’a vérifié si chacune d’elles contient du code utilisable.
    • Les adresses URL littérales et les noms d’API ne sont que des indices statiques et ne prouvent pas qu’un chemin de code est exécuté.
    • Les paquets n’ont jamais été exécutés, de sorte que les destinations réseau en temps de fonctionnement restent inconnues.
    • Ne rien trouver n’est pas un verdict de sécurité.

    Répétition de l’exécution

    Pour reconstituer exactement le même ensemble de fichiers tarball, utilisez le fichier des résultats figés comme entrée de verrouillage :

    # self-bootstrapping — no install needed
    git clone https://github.com/abdalhafeezbushara/driftward.git
    cd driftward
    python3 research/mcp-npm-audit-2026-09-01/audit.py \
      research/mcp-npm-audit-2026-09-01/results.json \
      /tmp/mcp-audit-rerun.json
    

    Pour obtenir un instantané frais de 300 paquets à partir des résultats actuels de recherche npm :

    N=300 ./detonate/fetch-corpus.sh /tmp/mcp-packages.txt
    python3 research/mcp-npm-audit-2026-09-01/audit.py \
      /tmp/mcp-packages.txt /tmp/mcp-audit-current.json
    

    Aucun de ces deux chemins ne installe de paquet ni n’exécute son code ; ils se contentent de télécharger et de lire des fichiers tarball.

    Une courte liste de contrôle pour les utilisateurs MCP

    • Fixez les versions. Une version précise accompagnée d’une valeur d’intégrité est préférable à une appel npx sans spécification de version.
    • Examinez l’artefact publié. Un répertoire propre ne vaut pas grand-chose si le fichier tarball installé contient un bundle généré que personne n’a ouvert.
    • Demandez des chiffres de couverture. Si un rapport indique l’absence de fichiers en temps de exécution concernés, interprétez cela comme un manque d’accessibilité et non comme un état propre.
    • Isolez chaque serveur individuellement, en lui attribuant une identité propre, un espace de fichiers défini ainsi que des hôtes autorisés, que ce soit en local ou via un pont.
    • Faites de la confiance une étape délibérée : enregistrez une exécution fiable, signez cette référence de base, et bloquez les processus CI ou exigez une révision dès que les capacités changent.

    L’abandon de l’analyse statique n’est pas la leçon à retenir ; ce qui compte, c’est d’indiquer son niveau de couverture. Pour les serveurs MCP et les outils similaires utilisés par les agents, la lecture du code source, l’analyse des artefacts générés, la vérification de l’origine du code, le fonctionnement en environnement isolé, l’enregistrement du comportement en temps réel et la détection des écarts couvrent chacun un aspect différent d’un même problème. La ligne la plus inquiétante d’un rapport de sécurité n’est pas nécessairement celle indiquant la détection de logiciels malveillants, mais plutôt l’absence totale de résultats, ce qui soulève la question suivante : zéro résultat pour quelle quantité de code ?