Vol de jeton GitHub en un clic via un bug de VSCode
(blog.ammaraskar.com)- github.dev utilise un jeton OAuth transmis par github.com pour permettre, dans VSCode au navigateur, la consultation de fichiers, les PR et les commits ; ce jeton n’est pas limité à un dépôt précis et permet donc de lire et d’écrire sur l’ensemble des dépôts auxquels l’utilisateur a accès
- Les webviews de VSCode sont isolées dans des iframes
vscode-webview://..., mais pour l’UX des raccourcis clavier, lekeydownde la webview est transmis à la fenêtre principale via un messagedid-keydown, ce qui permet à des scripts non fiables d’envoyer des événements comme s’il s’agissait de frappes utilisateur - Une saisie de texte arbitraire ne fonctionne pas à cause des balises HTML
<input>, mais en combinant le raccourci par défautCtrl+Shift+A, la notification d’installation d’extension recommandée, les local workspace extensions et des raccourcis clavier personnalisés, il est possible d’exécuter la commande d’installation d’extension - Le PoC exécute du JavaScript depuis une cellule Markdown d’un notebook Jupyter, accepte l’installation d’une extension recommandée, installe ensuite l’extension choisie via un nouveau raccourci clavier, puis affiche le jeton de l’API GitHub et la liste des dépôts privés
- VSCode Desktop est lui aussi vulnérable, mais l’attaquant doit convaincre la cible de cloner le dépôt et d’ouvrir le notebook ; les utilisateurs de github.dev doivent effacer les données du site pour faire réapparaître la boîte de dialogue de confirmation initiale comme mesure défensive
Présentation de la vulnérabilité
- github.dev ouvre une version légère de VSCode exécutée dans le navigateur si l’on remplace, sur une URL de dépôt GitHub accessible,
github.compargithub.devou si l’on clique sur l’entrée de menu correspondante - Ce VSCode dans le navigateur permet de consulter les fichiers du dépôt, d’ouvrir aussi des dépôts privés, ainsi que d’envoyer des PR et de créer des commits
- github.com envoie à github.dev, via POST, un jeton OAuth permettant d’interagir avec GitHub au nom de l’utilisateur, et ce jeton n’est pas limité au dépôt spécifique avec lequel l’utilisateur a interagi
- Un attaquant peut voler un jeton GitHub disposant de droits de lecture et d’écriture par un simple clic sur un lien, y compris sur des dépôts privés
Isolement des webviews et problème de transmission des frappes clavier
- Les webviews VSCode isolent l’exécution JavaScript en utilisant une
<iframe>d’une origin différente de celle de la fenêtre principale de VSCode - La sortie des notebooks Jupyter est rendue dans une
<iframe>d’originvscode-webview://..., tandis que la fenêtre principale Electron utilise l’originvscode-file://... - Grâce à cet isolement, même si un notebook utilise de l’affichage HTML ou des widgets interactifs en JavaScript, il ne peut pas appeler l’API Node.js d’Electron ni l’API VSCode depuis l’intérieur de l’iframe
- Les fonctionnalités où la fenêtre principale et la webview doivent coopérer, comme l’aperçu Markdown, échangent des messages via l’API Window.postMessage()
- VSCode transmet au volet principal les événements
did-keydownafin que des raccourcis commeCtrl+Shift+Pfonctionnent même quand le focus est dans la webview - Un script non fiable exécuté dans la webview peut déclencher directement des événements
keydownpour se faire passer pour l’utilisateur
Chaîne d’attaque
- Il est possible d’ouvrir la palette de commandes avec
Ctrl+Shift+P, mais comme elle utilise un<input>HTML, une approche consistant à saisir une chaîne arbitraire ne fonctionne pas - En revanche, on peut utiliser des entrées traitées via
keydown, comme les flèches directionnelles etEnter, ainsi que l’ensemble des raccourcis par défaut de VSCode Ctrl+Shift+Acorrespond au raccourci par défaut de « Notifications: Accept Notification Primary Action » et clique sur le bouton principal de la dernière notification VSCode- Si l’on ajoute une extension recommandée dans
.vscode/extensions.json, VSCode affiche une notification d’installation, mais le système de confiance des éditeurs de VSCode 1.97 ouvre une boîte de dialogue distincte lors de l’installation d’une extension provenant d’un nouvel éditeur - Il est possible de naviguer entre les boutons avec
Tab, mais l’actionEnterdu bouton « Trust Publisher & Install » est liée aukeydowndu bouton lui-même, ce qui rend l’installation difficile par cette seule voie - Les local workspace extensions permettent d’installer directement les extensions situées dans
.vscode/extensionsdans un workspace de confiance, et github.dev / les web workspaces sont toujours considérés comme fiables - Si l’on tente d’exécuter directement une extension locale de workspace, le worker d’extension s’attend à une extension venant de
vscode-cdn.net, ce qui provoque une erreur CSP - À la place, on peut ajouter un raccourci clavier personnalisé dans le
package.jsond’une extension locale de workspace, afin que ce raccourci appelleworkbench.extensions.installExtensionavec le contexteskipPublisherTrust
Fonctionnement du PoC et impact
- La configuration nécessaire est un dépôt contenant un notebook Jupyter et une local workspace extension
- Une cellule Markdown du notebook peut exécuter du JavaScript via l’attribut
onerrord’une image - La charge utile attend que VSCode affiche la notification d’installation d’extension recommandée, puis envoie l’événement
Ctrl+Shift+Apour accepter l’action principale de la notification - Elle attend ensuite que l’extension soit installée et activée, puis que le raccourci clavier personnalisé soit en place, avant de déclencher l’installation de l’extension choisie via l’événement
Ctrl+F1 - L’extension installée dans le PoC récupère le jeton de l’API GitHub et interroge
https://api.github.com/user/repospour obtenir les dépôts privés accessibles, puis affiche le jeton et la liste des dépôts dans une boîte d’information - Après l’exécution du PoC, il faut effacer les données de github.dev ou supprimer l’extension du PoC ; sans cela, elle persiste sur toutes les pages github.dev
- VSCode Desktop présente la même vulnérabilité, mais l’attaquant doit convaincre la victime de cloner le dépôt et d’ouvrir un notebook contenant la charge utile de script de webview
- S’il existe une autre XSS dans la webview ouverte par la victime, cela peut en pratique conduire à une exécution de code à distance quasi totale sur Desktop également
Défense et éléments d’atténuation
- Si l’on n’a jamais utilisé github.dev auparavant, une boîte de dialogue nécessitant un clic apparaît à l’ouverture du site, ce qui offre une occasion d’échapper à la page d’attaque
- Effacer les cookies et les données locales du site de github.dev peut faire réapparaître cette boîte de dialogue initiale
- Dans Chrome, on peut cliquer sur l’icône de la barre d’adresse, puis aller dans Cookies and site data > Manage on-device site data pour supprimer les données des domaines concernés
- Si l’utilisateur a déjà passé la boîte de dialogue github.dev et n’a pas effacé le stockage local du navigateur, github.dev ne dispose pas de protections de type jeton CSRF, ce qui permet à n’importe quel lien sur Internet de rediriger vers l’attaque
- VSCode ne s’appuie pas uniquement sur l’isolement par iframe, mais utilise aussi une Content Security Policy stricte et DOMPurify
- En utilisant
script-src 'none'dans l’aperçu Markdown de la page d’extension, l’exécution de JavaScript arbitraire est bloquée, ce qui empêche un impact plus grave où un simple lien d’extension deviendrait une RCE en un clic sur Desktop
Contexte de la divulgation et calendrier
- Le MSRC a par le passé corrigé discrètement des signalements de bugs VSCode sans attribuer de crédit et en les marquant comme sans impact sécurité
- Récemment, le signalement du bug XSS VSCode par Starlabs a lui aussi été marqué comme non éligible et de faible sévérité
- L’équipe VSCode avait peut-être besoin de plus de temps pour trouver un équilibre entre UI/UX et sécurité, mais une divulgation complète a été choisie au motif qu’il ne faut pas considérer comme acquises le temps et les efforts des chercheurs en sécurité
- Le 2 juin 2026, une heure avant la publication, la divulgation prévue a été communiquée à un contact existant de l’équipe sécurité GitHub
- Le même jour, la vulnérabilité a été rendue publique et enregistrée aussi dans le tracker d’issues VSCode
1 commentaires
Réactions sur Hacker News
C’était un bon récapitulatif et, plus globalement, le fait même que l’éditeur VSCode embarqué dans le web soit connecté à GitHub est regrettable
Indépendamment de la défense en profondeur, c’est ce péché originel qui crée une énorme surface d’attaque. C’est comparable au fait de laisser en clair sur le poste de travail un token d’API GitHub avec tous les droits, afin qu’un paquet NPM malveillant puisse le trouver
Dans l’idéal, l’IDE dans le navigateur fonctionnerait avec un périmètre d’autorisations temporaire, limité au dépôt concerné, ou avec un token équivalent ne permettant que le pull/push sur ce dépôt, sans aucune session web github.com. Si l’on a besoin de l’interface web complète de GitHub, on retourne sur github.com, et github.dev reste un service centré sur un seul dépôt
Mais ce serait moins pratique pour l’utilisateur, difficile à mettre en œuvre, et il est probable que toute la boîte à outils github.dev repose historiquement sur des hypothèses contraires
github.dev devrait sérieusement envisager cette approche
[1] https://orca.security/resources/blog/hacking-github-codespac...
Le pire, c’est que même les développeurs ne semblent pas vraiment s’en soucier
keydownSur desktop, il vaudrait mieux qu’Electron les intercepte directement et supprimer cette fonctionnalité ; sur le web, la désactiver par défaut semble plus approprié
Je ne sais pas vraiment si d’autres hébergeurs Git proposent une fonction similaire
Honnêtement, les agents LLM devraient aussi fonctionner comme ça. Les laisser pousser directement me paraît imprudent
Cette attaque est particulièrement délicate parce que les extensions VSCode s’exécutent avec le même niveau de confiance que l’éditeur lui-même, et que la plupart des développeurs en installent des dizaines sans jamais vérifier leurs permissions
Si une extension malveillante ou compromise exfiltre discrètement un token GitHub, c’est difficile à détecter sans supervision réseau, ce qui plaide pour exécuter les extensions dans des profils isolés
La meilleure approche consiste à quitter GitHub, migrer vers un GitLab/Forgejo interne auto-hébergé, et bloquer complètement GitHub
J’ai vécu quelque chose de similaire récemment. Mes tokens GitHub et Cloudflare ont été volés
Même en prenant la sécurité au sérieux, avec suffisamment de temps, on finit par se faire avoir. Le mieux qu’on puisse faire, c’est cloisonner et limiter l’étendue des dégâts
Il ne faut faire confiance à personne ni à rien, utiliser OrbStack, et toujours travailler en partant du principe que les tokens finiront un jour par fuiter
Mon flux de travail a été complètement interrompu, mais heureusement, ceux qui ont récupéré les tokens ressemblaient davantage à des bots spammeurs. Ils ont créé une masse de fausses pages de spam et tenté de faire du minage de cryptomonnaies
Le sentiment qui reste le plus fortement, c’est celui d’avoir été violé. Faites tous attention
Le passage sur l’expérience affreuse consistant à signaler le bug VSCode au MSRC pour qu’il soit corrigé discrètement, c’est du MSRC typique. Ils ont visiblement compris que les chercheurs signaleraient de toute façon gratuitement, donc ils n’ont aucune raison de changer
Je ne connais pas les détails précis de ce cas, mais j’ai déjà géré des programmes de bug bounty via Bountysource et HackerOne. Il arrive parfois qu’un rapport soit transmis à l’équipe de développement avant même que l’équipe sécurité ne l’ait entièrement évalué
À ce stade, un développeur peut le corriger discrètement. Parfois parce qu’il craint, rationnellement ou non, qu’un bug de sécurité lui donne mauvaise image ou nuise à ses perspectives d’avancement. Résultat : quand l’équipe sécurité essaie de reproduire le problème, la vulnérabilité a déjà disparu
Du point de vue du MSRC, ils constatent seulement que les étapes de reproduction fournies ne fonctionnent plus. Ils n’ont pas de visibilité sur l’historique interne du bug ni sur le fait que quelqu’un l’a déjà patché. Le rapport est donc clos comme invalide, même si la découverte initiale était parfaitement légitime
Merci d’avoir en pratique fait don du temps passé sur cet exploit pour montrer qu’il fallait améliorer la réponse de sécurité de VS Code. Tu aurais pu laisser tomber, mais tu continues quand même à aider
Je ne comprends pas très bien pourquoi davantage de développeurs n’essaient pas Neovim
C’est peut-être une question de goût, mais j’aime les petites configurations où l’on peut savoir ce qui est installé et ce qui tourne. Quand on mélange VSCode, un IDE dans le navigateur, des extensions, la synchronisation, des tokens et des plugins arbitraires, il devient difficile de savoir quoi a accès à quoi
C’était une fonctionnalité de l’extension Python officielle de Microsoft et, sur d’autres aspects, c’était plus ou moins la seule extension vraiment utilisable, mais elle installait des définitions de types pour une version de bibliothèque différente de celle utilisée par mon projet. Ça m’a énormément inquiété, parce que cela donnait l’impression d’exécuter sans problème du code tiers non vérifié, et il ne semblait même pas possible de le désactiver dans les paramètres
J’aimerais dire que « je n’ai jamais regardé en arrière depuis », mais honnêtement, depuis 1 à 2 ans, Neovim s’est mis à casser régulièrement ma configuration à presque chaque mise à jour. Il y avait des signes que cela finirait par arriver. Techniquement, même après 10 ans, nvim n’a toujours pas publié sa première version stable, donc on ne peut pas vraiment lui reprocher son instabilité, mais c’est bon à garder en tête
J’envisage de revenir à Vim pur. Je perdrai beaucoup de fonctions pratiques, mais j’aimerais au moins avoir moins souvent à déboguer des fonctionnalités cassées en plein travail
Pas besoin d’installer une montagne de plugins ni d’utiliser quelque chose comme SpaceVim. Ça vaut le coup d’y jeter un œil, tu pourrais aimer
Il faut du temps pour s’habituer à nvim, mais une fois le cap passé, c’est plus rapide. Ça explique quand même pourquoi beaucoup de gens restent dans leur zone de confort
La divulgation publique était une bonne chose. Il y a beaucoup trop de gens mécontents du MSRC, et comme dans le cas Nightmare Eclipse, cela commence maintenant à déborder
Si ce genre de divulgations s’accumule, peut-être que le MSRC finira par se remettre en question et par comprendre que le problème, c’est eux. Ça paraît peu probable, mais on peut espérer
Cela dit, j’estime qu’il aurait au moins dû essayer, ou prévenir plusieurs jours avant de publier. On ne sait jamais ce qui peut se passer
L’article était très bon, mais la fin m’a un peu embrouillé. Je veux vérifier si j’ai bien compris
L’auteur dit qu’avec le nouveau système de confiance des éditeurs, on ne peut pas installer directement une extension malveillante uniquement avec le tour des raccourcis, et que le contournement via une extension locale de l’espace de travail sans vérification d’éditeur est bloqué par la CSP
La solution semble être d’installer une extension locale de l’espace de travail qui associe le raccourci « installer une extension sans vérification de l’éditeur »
Donc je me demande 1) si cela veut dire qu’il faut deux extensions. La première serait une extension locale qui ne fait qu’ajouter le raccourci, et la seconde la véritable extension malveillante qui, à cause de la CSP, n’a pas besoin d’être locale et ne peut d’ailleurs pas vraiment l’être, et 2) si la CSP ne bloque que le JS de l’extension locale, mais pas
package.jsonni la capacité d’ajouter des raccourcisOn pourrait essayer d’ajouter
my-extension/extension.jspour l’exécution la plus directe, mais la CSP le bloque. En revanche, comme la CSPscript-srcne bloque que les scripts, le chargement depackage.jsonreste autorisé. C’est donc utilisé pour déclarer le raccourci clavierLa situation du MSRC est vraiment difficile à croire
Il existe peut-être de meilleures ressources, mais je trouve que cette vidéo de The Primeagen est une bonne introduction
https://www.youtube.com/watch?v=9kxx5xp5nTQ
J’ai un petit désaccord sur la phrase « la seule manière d’autoriser ce comportement est que deux pages web d’origines différentes coopèrent via l’API
Window.postMessage()»Un iframe ou une fenêtre parente peut aussi communiquer en modifiant la propriété
location.anchor