Des articles approfondis sur les technologies qui façonnent l'avenir.

Backdoor Cursor Marketplace : le piège des forks VS Code

La backdoor de Claude Code sur le Marketplace Cursor montre que les forks de VS Code n'héritent pas du modèle de confiance de Microsoft. Que faire ?

Un ordinateur portable affichant une grille de tuiles d'extensions, l'une fissurée avec une lumière rouge et une ombre qui atteint le clavier
La grille d'extensions ressemble à s'y méprendre, que la tuile en dessous soit saine ou porteuse d'une charge malveillante.

Un développeur ouvre Cursor un mardi matin, voit une mise à jour de l'extension Claude Code et la laisse s'installer. Rien ne semble différent. L'extension se charge, l'assistant IA fonctionne, et à midi la machine contient une clé Ed25519 de l'attaquant dans ~/.ssh/authorized_keys, un tunnel SSH inverse qui envoie des signaux vers un serveur sur Internet, et un LaunchAgent déguisé en processus système Apple qui maintient le tout après chaque redémarrage. C'est la backdoor du Marketplace Cursor révélée par Phorion, et c'est la démonstration la plus nette de ce que nous, dans la sécurité, répétons depuis des années : les forks de VS Code héritent du code de Microsoft, mais pas de son modèle de confiance.

J'ai passé une bonne partie de ma carrière à faire de la réponse à incident sur des postes de développeurs, et cet incident me touche à cause de la banalité du mécanisme de livraison. Pas de phishing. Pas de zero-day. Juste un bundle d'extension falsifié, les versions 2.1.312 et 2.1.313, publié sur une marketplace que les développeurs font confiance par défaut parce qu'elle ressemble à ce qu'ils connaissent déjà. Les canaux officiels d'Anthropic étaient propres : quelqu'un a utilisé un accès au pipeline de publication de Cursor pour diffuser une copie modifiée d'une extension qu'Anthropic n'avait jamais compilée pour ce registre. Le rayon d'impact : tous les utilisateurs de Cursor ayant effectué une mise à jour automatique pendant une fenêtre de onze heures.

Ce que la backdoor faisait réellement

L'implant était une expression de fonction immédiatement invoquée (IIFE) ajoutée en préfixe du fichier extension.js minifié. Tout ce qui suivait le préfixe injecté ressemblait à l'extension normale : mêmes exports activate et deactivate, même code d'interface, mêmes chaînes. C'est là que votre stratégie de détection « comparer le bundle avec la version amont » montre ses limites : la partie malveillante consiste en quelques centaines d'octets de JavaScript supplémentaires posés sur un fichier légitime de plusieurs mégaoctets. Elle s'exécute dès que Node charge le module, avant que la fonction activate de l'extension ne soit appelée. L'utilisateur n'a rien à cliquer, rien à invoquer avec Claude, aucune interaction avec l'extension.

La charge utile est un travail en quatre étapes, et honnêtement c'est du travail compétent :

  • Dépôt de clé. Ajoute une clé publique Ed25519 contrôlée par l'attaquant, commentée svc-telemetry, dans ~/.ssh/authorized_keys avec les bonnes permissions 0700/0600.
  • Activation de SSH. Tente de s'assurer que sshd tourne : systemctl sous Linux, Remote Login sous macOS, OpenSSH Server via PowerShell sous Windows.
  • Tunnel inverse. Ouvre une connexion SSH sortante vers un serveur contrôlé par l'opérateur avec -R *:0:localhost:22, pour que l'attaquant puisse revenir via le tunnel sur le port 22 de la victime et se connecter avec la clé déposée. Pas besoin de protocole C2 maison : SSH est le C2.
  • Persistance et reconnaissance. Un LaunchAgent utilisateur nommé com.apple.sysmond.plist sous macOS, une entrée cron @reboot sous Linux, un repli dans le dossier Démarrage sous Windows. Puis une balise ponctuelle avec le nom d'hôte, le nom d'utilisateur, l'e-mail git, l'IP publique et jusqu'à quarante noms de variables d'environnement correspondant à /KEY|TOKEN|SECRET|PASS|AWS|GCP/i.

Ce dernier point est malin, et il mérite qu'on s'y arrête. La balise n'exfiltre pas votre clé secrète AWS, elle exfiltre le fait que vous en avez une. C'est un inventaire de cibles. L'opérateur obtient une liste des machines compromises où travaillent des ingénieurs cloud avec des identifiants actifs dans leur environnement, et ce sont précisément ceux qu'il rappelle en premier via le tunnel.

Le bug macOS qui a involontairement aidé les victimes

Voici le détail que je trouve franchement drôle en tant que répondant à incident : le chemin d'activation sous macOS du malware est cassé par une erreur de script classique. Le code exécute systemsetup -getremotelogin en tant qu'utilisateur sans privilèges, et ne tente d'activer Remote Login que si la sortie correspond à /Off/i. Or sous macOS, cette commande refuse de tourner sans droits administrateur. Elle affiche « You need administrator access to run this tool... exiting! » sur la sortie standard, puis quitte avec le statut 0.

// what the malware effectively does
var rl = ex("systemsetup -getremotelogin 2>/dev/null", 4000);
if (/Off/i.test(rl)) {
det("sudo -n systemsetup -setremotelogin on >/dev/null 2>&1");
}

La chaîne d'erreur ne contient pas « Off », donc l'expression régulière échoue, donc l'étape d'activation ne s'exécute jamais. Le script traite « je n'ai pas la permission de vérifier si SSH est désactivé » comme « SSH est déjà activé ». Un code de sortie 0 émis par une commande en échec est l'un des pièges les plus anciens du scripting shell, et cette fois il a mordu l'auteur du malware plutôt qu'un administrateur système. Sur un Mac où Remote Login n'est pas déjà activé, le tunnel inverse se connecte sans problème, mais tout ce qui est redirigé vers la machine tombe sur un port 22 fermé. Les clés et la persistance sont quand même déposées, la machine reste compromise, mais le canal d'accès interactif est mort-né.

Ne vous rassurez pas pour autant. Si l'utilisateur avait déjà Remote Login activé, ou s'il l'active un jour, la clé déposée attend dans authorized_keys. Le chemin Windows a un bug similaire (il envoie une commande PowerShell via un utilitaire qui code en dur /bin/sh), et la logique cron sous Linux peut effacer la crontab existante d'un utilisateur lors des exécutions répétées. Les malwares écrits par des humains ont des bugs humains. Celui du mois prochain n'en aura pas forcément.

Pourquoi les marketplaces dérivées ne peuvent pas hériter du modèle de confiance de Microsoft

C'est le point structurel, celui que je veux que les responsables techniques méditent. Quand vous installez une extension depuis la Visual Studio Marketplace officielle, il existe un vrai pipeline derrière : comptes éditeurs avec vérification de domaine, analyse à la publication, procédures de retrait, et une entreprise avec un service juridique au bout. Il est loin d'être parfait, des extensions malveillantes s'y sont glissées, mais c'est un système.

Les forks de VS Code comme Cursor ne peuvent pas utiliser cette marketplace pour des raisons de licence. Ils pointent donc vers des registres alternatifs, Open VSX ou leur propre infrastructure. L'interface de l'éditeur est identique au octet près. La page de l'extension affiche le même nom, la même icône, un éditeur plausible. Mais chaque contrôle qui rendait la marketplace d'origine fiable est soit réimplémenté, soit absent. Qui a vérifié l'éditeur ? Qui a analysé le bundle ? L'artefact a-t-il été compilé depuis le dépôt source annoncé, et pouvez-vous le vérifier ? Dans cet incident, les versions piégées n'existaient apparemment jamais sur le registre de Microsoft, elles ne vivaient que chez le fork. La confiance était purement visuelle.

Nous avons déjà vu le même film dans l'écosystème des paquets. La compromission de LiteLLM sur PyPI a fonctionné parce que le point faible est l'accès à la publication, pas le code. Les extensions sont pires que les paquets sur un point important : il n'existe pas de culture du lockfile, pas de reproductibilité type npm ci, pas d'épinglage par hash. L'éditeur se met à jour silencieusement, et comme l'a découvert un commentateur de Hacker News, le délai de mise à jour automatique de VS Code ne s'applique même pas aux « éditeurs de confiance », une liste que vous ne pouvez pas configurer. Votre ensemble d'extensions est un arbre de dépendances vivant, non épinglé, qui s'exécute avec tous vos privilèges utilisateur et se met à jour pendant que vous dormez.

Deux vitrines identiques, dont l'une révèle une façade creuse soutenue par l'arrière
Une marketplace dérivée paraît identique vue de la vitrine ; l'infrastructure de confiance qui la soutient n'est peut-être qu'une façade.

Les outils de codage IA sont une élévation de privilèges que vous avez choisie

Mettez de côté le drame de la marketplace et regardez quelle était réellement la cible. Une extension de codage IA n'est pas un thème de couleurs. Elle s'exécute avec les permissions de l'IDE, c'est-à-dire les vôtres, et son travail consiste à lire votre arborescence de sources, détenir vos identifiants d'API et, de plus en plus, exécuter des commandes shell pour vous. Quand nous avons écrit sur la gestion des permissions dans les CLI de codage agentique, le thème récurrent était que l'agent utile et l'agent dangereux se distinguent par la politique, pas par la capacité. Une extension capable d'utiliser child_process n'est qu'une publication malveillante de distance de l'exécution de n'importe quoi.

Un commentateur l'a formulé sans détour : tous ceux qui utilisent ces outils exécutent en pratique un curl-pipe-bash quotidien, alors que nous passons une décennie à dire aux juniors de ne jamais faire ça. C'est exagéré, mais à peine. La différence entre curl | bash et une extension agentique à mise à jour automatique, c'est que l'extension a une interface plus jolie. J'ai déjà soutenu que les outils CLI ne devraient pas installer de démons en arrière-plan par défaut ; cet incident montre ce qui arrive quand le démon est installé par quelqu'un d'autre, sans que vous ayez jamais consenti. Un processus ssh détaché qui survit à la fermeture de l'IDE, c'est exactement le comportement que je signale en revue de code, ici livré par un attaquant via un canal de mise à jour de confiance.

Comment vérifier votre parc dès maintenant

Si quelqu'un dans votre organisation utilise Cursor, traitez cela comme un incident en cours, pas comme une actualité. Les indicateurs les plus parlants, directement tirés du rapport d'analyse forensique :

# macOS / Linux: the planted key is the cleanest signal
grep -s svc-telemetry ~/.ssh/authorized_keys && echo "IMPACTED" || echo "key not found"
# Check persistence artifacts
ls ~/Library/LaunchAgents/com.apple.sysmond.plist 2>/dev/null   # macOS
ls ~/.config/.smt ~/.config/.smtk 2>/dev/null                   # macOS + Linux
crontab -l 2>/dev/null | grep -F .smt                           # Linux @reboot entry
# Windows (PowerShell): check both authorized_keys locations
Select-String svc-telemetry "$HOME\.ssh\authorized_keys",
"C:\ProgramData\ssh\administrators_authorized_keys" -ErrorAction SilentlyContinue
# Live tunnel: look for detached ssh with remote forwarding
ps aux | grep -E 'ssh.*-R' | grep -v grep

Quelques notes de chasse tirées de l'expérience. D'abord, vérifiez l'empreinte de la clé, pas seulement le commentaire : l'implant dédoublonne sur la chaîne de commentaire svc-telemetry, et une tentative de nettoyage pourrait faire de même. Ensuite, le fichier de clé du tunnel se trouve dans le répertoire temporaire, avec un nom du type .stk_ suivi de dix caractères hexadécimaux dérivés d'un MD5 du nom d'hôte et du chemin du home ; il est déterministe par machine, vous pouvez donc calculer le nom exact du fichier et le rechercher. Troisièmement, la télémétrie des processus EDR ne montrera pas la tentative d'activation sous macOS pour les utilisateurs standard, car à cause du bug elle ne s'exécute jamais. L'absence de systemsetup -setremotelogin dans vos journaux ne signifie pas l'absence de compromission. La clé et le LaunchAgent sont la vérité terrain.

Si vous trouvez la clé, la machine passe en traitement complet de réponse à incident : révoquez chaque identifiant dont le nom de variable d'environnement a pu être envoyé par balise (c'est-à-dire la plupart, l'expression régulière attrape AWS, GCP et tout ce qui contient KEY ou TOKEN), coupez les accès de la machine et réinstallez-la entièrement. Ne vous contentez pas de supprimer la clé. L'opérateur avait, ou a tenté d'avoir, un accès shell interactif ; vous ne pouvez pas énumérer ce qu'il a touché à partir du seul code de l'implant. C'est aussi là que les angles morts de la journalisation de l'authentification cloud vous mordent : vérifiez vos journaux d'audit cloud pour toute utilisation de la console ou de l'API entre la mise à jour de l'extension et la divulgation, en filtrant sur les identifiants de l'utilisateur concerné.

A flashlight beam picking out one red-glowing key from a ring of keys, with a cable trailing into darkness
Hunt for the planted key by fingerprint, not by its label — the label is the first thing a cleanup deletes.

Durcissement : contenir l'éditeur, pas seulement l'extension

Après un incident, tout le monde demande ce qu'il faut changer. Ma position, que je défends : vous n'atteindrez jamais un écosystème d'extensions sûr par l'audit seul, donc vous devez contenir. Nous avons abordé le compromis général dans l'article sur le confinement face à la surveillance des agents IA, et cet incident en est l'étude de cas. La surveillance ne vous a rien appris ici avant qu'une société de sécurité tierce ne disseque le bundle par hasard.

  • Épingler les versions des extensions et désactiver la mise à jour automatique pour tout ce qui dispose de permissions étendues, et appliquer la même règle à tous les forks. Oui, vous hériterez de la lassitude des mises à jour. C'est moins cher qu'une réponse à incident.
  • Filtrage des sorties réseau sur les postes de développement. Tout le modèle d'accès reposait sur une connexion SSH sortante vers l'IP d'un VPS. Si vos machines de dev ne peuvent pas ouvrir de connexion sortante arbitraire sur le port 22, l'étape du tunnel meurt. Presque personne ne le fait, et cela aurait totalement neutralisé cet implant.
  • Surveiller l'état de SSH comme un canari. Alertez sur l'activation de Remote Login, les nouvelles clés dans authorized_keys et les processus sshd sur des machines qui n'ont aucune raison d'exécuter sshd.
  • Inventorier vos outils IA comme vous inventoriez vos serveurs. Sachez quelles extensions et quels CLI d'agents sont approuvés, quelles permissions ils détiennent et de quelle marketplace ils viennent. Si la réponse à « quel registre utilisent nos installations Cursor ? » est un haussement d'épaules, c'est votre projet du prochain trimestre.
  • Privilégier les agents qui tournent dans des bacs à sable ou des conteneurs lorsque c'est possible, avec des identifiants injectés par session plutôt que présents en permanence dans l'environnement. Le scraping des noms de variables par la balise fonctionne précisément parce que nous laissons traîner des secrets dans les environnements de processus.

Une marketplace d'extensions sans son propre pipeline de vérification n'est pas une marketplace, c'est un hébergeur de fichiers avec un bouton d'installation.

La conclusion dérangeante

Anthropic n'a pas été compromis. C'est la marketplace de Cursor qui l'a été, et la marque d'Anthropic a encaissé les dégâts quand même, car pour un développeur l'extension est le produit, quel que soit l'auteur de la version. C'est l'économie des écosystèmes dérivés : la façon la moins coûteuse d'atteindre une cible de grande valeur est d'attaquer le registre le plus faible auquel ses utilisateurs font confiance. Les assistants de codage IA se trouvent à l'intersection parfaite : une base d'installation énorme, un maximum de privilèges locaux, des identifiants partout, et une chaîne d'approvisionnement qui duplique et redistribue leurs artefacts sur des registres aux niveaux de sécurité très inégaux.

Cet épisode s'est terminé par le retrait des versions malveillantes au bout d'environ onze heures, et par un bug macOS qui a limité les dégâts. La réponse a été correcte. Le problème, c'est l'architecture, et elle n'a pas changé : votre éditeur est un gestionnaire de paquets qui exécute tout ce qu'il installe en tant que vous, se met à jour en silence, et dans un nombre croissant de configurations récupère ses paquets auprès d'un registre que personne dans votre équipe sécurité n'a jamais évalué. Corrigez les règles de sortie réseau, épinglez les extensions et inventoriez vos agents. La prochaine compromission d'un éditeur n'aura pas de bug de regex pour sauver vos utilisateurs.