VSCodeのバグによる1クリックGitHubトークン窃取
(blog.ammaraskar.com)- github.dev は github.com から渡される OAuth トークンでブラウザ版VSCode上のファイル閲覧、PR、コミットを実行し、このトークンは特定のリポジトリに限定されないため、ユーザーがアクセス可能なすべてのリポジトリを読み書きできる
- VSCode webview は
vscode-webview://...iframe で分離されているが、キーボードショートカットのUXのために webview のkeydownをdid-keydownメッセージとしてメインウィンドウに転送しており、信頼されていないスクリプト がユーザーのキー入力のようにイベントを送信できる - 任意のテキスト入力は HTML
<input>のため通用しないが、デフォルトのショートカットCtrl+Shift+A、おすすめ拡張機能のインストール通知、local workspace extensions、カスタムキーバインドを組み合わせることで拡張機能のインストールコマンドを実行できる - PoC は Jupyter notebook のMarkdownセルで JavaScript を実行しておすすめ拡張機能のインストールを承認し、新しいキーバインドで選択した拡張機能をインストールした後、GitHub API トークンとプライベートリポジトリ一覧を表示する
- デスクトップ版VSCodeにも同じ脆弱性があるが、攻撃者はリポジトリの複製と notebook のオープンを誘導する必要があり、github.dev ユーザーはサイトデータを削除して初回確認ダイアログを再表示させる防御が必要
脆弱性の概要
- github.dev は、アクセス可能な GitHub リポジトリURLを
github.comからgithub.devに変えるか、メニュー項目をクリックすると、ブラウザ上で動作する軽量版VSCodeを開く - このブラウザ版VSCodeではリポジトリのファイルを閲覧でき、プライベートリポジトリも開けて、PRの送信やコミットの作成も可能
- github.com はユーザーの代わりに GitHub とやり取りできる OAuth トークンを github.dev に POST し、このトークンはユーザーがやり取りしていた特定のリポジトリに限定されない
- 攻撃者はリンクのクリックだけで読み書き権限を持つ GitHub トークンを窃取でき、対象にはプライベートリポジトリも含まれる
Webview分離とキー入力転送の問題
- VSCode webviews はメインのVSCodeウィンドウとは異なる origin の
<iframe>を使って JavaScript 実行を分離する - Jupyter notebook の出力は
vscode-webview://...origin の<iframe>でレンダリングされ、メインの Electron ウィンドウはvscode-file://...origin を使う - この分離のおかげで、notebook が HTML 表示や JavaScript ベースのインタラクティブウィジェットを使っても、iframe 内から Electron の Node.js API や VSCode API を呼び出すことはできない
- Markdown プレビューのようにメインウィンドウと webview が協調する必要がある機能は、Window.postMessage() API でメッセージをやり取りする
- VSCode は webview 内をクリックした状態でも
Ctrl+Shift+Pのようなショートカットが動作するよう、did-keydownイベントをメインウィンドウへ転送する - webview 内の信頼されていないスクリプトは
keydownイベントを直接発火させ、ユーザーがキーを押したかのように偽装できる
攻撃チェーン
Ctrl+Shift+Pでコマンドパレットを開くことはできるが、コマンドパレットは HTML<input>を使うため、任意の文字列を入力する方法は通用しない- 矢印キーや
Enterのようにkeydownで処理される入力は使え、VSCode のデフォルトショートカット群も活用できる Ctrl+Shift+Aは「Notifications: Accept Notification Primary Action」のデフォルトキーバインドで、最後のVSCode通知の主要ボタンを押す.vscode/extensions.jsonにおすすめ拡張機能を入れると VSCode がインストール通知を表示するが、VSCode 1.97 の publisher trust システムは新しい publisher の拡張機能をインストールする際に別途信頼ダイアログを表示するTabでボタン移動はできても、「Trust Publisher & Install」ボタンのEnter処理はボタン自身のkeydownに結び付いているため、この経路だけでインストールを完了するのは難しい- local workspace extensions は、信頼された workspace 内の
.vscode/extensionsにある拡張機能を直接インストールでき、github.dev/web workspace は常に信頼済み状態になっている - ローカル workspace 拡張機能を直接実行しようとすると、extension worker が
vscode-cdn.net由来の拡張機能を想定しているため CSP エラーが発生する - 代わりに、ローカル workspace 拡張機能の
package.jsonにカスタムキーバインドを追加し、そのキーバインドがworkbench.extensions.installExtensionをskipPublisherTrustコンテキストで呼び出すようにできる
PoCの動作と影響
- 必要な構成は Jupyter notebook と local workspace extension を含むリポジトリである
- notebook のMarkdownセルは画像の
onerror属性を通じて JavaScript を実行できる - ペイロードは、VSCode がおすすめ拡張機能のインストール通知を表示するまで待機した後、
Ctrl+Shift+Aイベントを送り、通知の主要アクションを承認する - その後、拡張機能がインストール・有効化され、カスタムキーバインドが反映されるまで待機し、
Ctrl+F1イベントで選択した拡張機能のインストールをトリガーする - PoC でインストールされた拡張機能は GitHub API トークンを取得し、
https://api.github.com/user/reposを照会してアクセス可能なプライベートリポジトリを取得した後、トークンとリポジトリ一覧を情報ボックスに出力する - PoC 実行後は github.dev のデータを消去するか PoC 拡張機能を削除する必要があり、削除しないとすべての github.dev ページに残り続ける
- デスクトップ版VSCodeにも同じ脆弱性があるが、攻撃者は被害者にリポジトリを clone させ、webview スクリプトペイロードを含む notebook を開かせる必要がある
- 被害者が開く webview に別の XSS があれば、デスクトップでも実質的に完全なリモートコード実行につながる可能性がある
防御と緩和要素
- 過去に github.dev を使ったことがなければ、サイトに入る際にクリックが必要なダイアログが1つあり、攻撃ページから離脱する機会が生まれる
- github.dev のCookieとローカルサイトデータを削除すると、この初回ダイアログを再表示できる可能性がある
- Chrome では URL バーのアイコンを押して Cookies and site data > Manage on-device site data に進み、関連ドメインのデータを削除できる
- すでに github.dev のダイアログを通過しており、ブラウザのローカルストレージを消していない場合、github.dev には CSRF トークンのような保護がないため、インターネット上のどのリンクからでも攻撃へリダイレクトできる
- VSCode は iframe 分離だけに依存せず、厳格な Content Security Policy と DOMPurify も併用している
- 拡張機能ページのMarkdownプレビューでは
script-src 'none'を使って任意の JavaScript 実行を防いでいるため、拡張機能リンクだけでデスクトップ版の1クリックRCEになるより大きな影響は防がれている
公開の背景とスケジュール
- MSRC は過去の VSCode バグ報告をひそかに修正し、クレジットを与えず、セキュリティへの影響なしと表示していた
- 最近の StarlabsのVSCode XSSバグ報告 も不適格かつ低重大度として扱われた
- VSCode チームには UI/UX とセキュリティの間でバランスを取る時間がさらに必要だった可能性はあるが、セキュリティ研究者の時間と労力を当然視すべきではないという理由から、完全公開が選ばれた
- 2026年6月2日の公開1時間前に、GitHub セキュリティ側の既存の連絡先へ公開予定が伝えられた
- 同日、脆弱性が公開され、VSCode issue tracker にも登録された
1件のコメント
Hacker Newsの反応
よくまとまっているし、大きく見れば、Webに埋め込まれたVSCodeエディタがGitHubにログイン済みであること自体が残念
多層防御の有無とは別に、その原罪のせいで攻撃対象領域が大きく広がっている。悪意あるNPMパッケージが見つけられるように、ワークステーション上に全権限を持つGitHub APIトークンを平文で置いているのと似ている
理想を言えば、ブラウザIDEはそのリポジトリに対するpull/pushだけが可能な一時的なリポジトリ単位の権限スコープやトークンで動き、github.comのWebセッションはまったく持たないでほしい。GitHubの完全なWeb UIが必要ならgithub.comに戻り、github.devは単一リポジトリのサービスとして置く、という形がよさそう
ただし、ユーザーにとって不便で、実装も難しく、github.devのツール全般に歴史的に埋め込まれた前提である可能性が高い
github.devでもこの方式を真剣に検討すべき
[1] https://orca.security/resources/blog/hacking-github-codespac...
さらに悪いのは、開発者たちでさえあまり気にしていないように見えること
keydownハンドラにも伝播しないよう修正すべきデスクトップではElectronが直接横取りするように変えて、この機能は削除したほうがよく、Webではデフォルトで無効化するのが妥当だと思う
他のGitホスティングにも同様の機能があるかはよく分からない
正直、LLMエージェントもこうあるべき。LLMに直接pushさせるのは無謀に見える
この攻撃が特に厄介なのは、VSCode拡張機能がエディタ本体と同じ信頼レベルで実行され、しかも大半の開発者が権限を確認していない拡張機能を何十個もインストールしている点
悪意ある、あるいは乗っ取られた拡張機能がGitHubトークンをひそかに流出させても、ネットワーク監視がなければ気づきにくく、拡張機能は隔離されたプロファイルで実行すべきだという根拠になる
最善の方法はGitHubから離れ、セルフホストの内部GitLab/Forgejoに移行し、GitHubを完全に遮断すること
最近、似たようなことを経験した。GitHubトークンとCloudflareトークンが盗まれた
セキュリティを本気でやっていても、十分に長い時間があれば結局やられるものだと思う。最善策は分離し、被害範囲を制御すること
誰も、何も信用せず、OrbStackを使い、トークンはいずれ漏洩する前提で常に作業すべき
ワークフローは完全に寸断されたが、幸い、トークンを持っていった側はスパムボットに近かったようだ。偽のスパムページを大量に作り、暗号通貨のマイニングを試みていた
何より強く残る感情は、侵害されたという感覚。みんな気をつけてほしい
MSRCにVSCodeのバグを報告したとき、ひっそり修正されてしまうというひどい経験だった、という話は典型的なMSRCらしさがある。研究者がどうせ無償で報告してくると分かっていて、わざわざ変える理由がないと考えているように見える
この件の詳細は分からないが、以前BountysourceやHackerOneを通じてバグバウンティプログラムを運営していたことがある。ときどき、セキュリティチームが十分に評価する前に、報告書が先に開発チームへ流れてしまうことがある
その時点で開発者がひっそり修正してしまうことがある。セキュリティバグに関わると自分の評価が悪く見えたり、昇進の機会に影響したりするのではないかという、合理的かどうかは別として、懸念が理由のこともある。結果として、セキュリティチームが再現しようとする頃には、脆弱性はすでに消えている
MSRC側から見えるのは、提供された再現手順がもはや動作しないということだけ。内部のバグ履歴や、誰かがすでにパッチを当てたかどうかは見えない。そのため、本来は正当な発見であっても、報告は無効として閉じられてしまう
このエクスプロイトに費やした時間を事実上寄付して、VS Codeのセキュリティ対応改善の必要性を知らせてくれたことに感謝したい。諦めることもできたのに、なお助けようとしている
なぜもっと多くの開発者が Neovim を試さないのか、正直よく分からない。
好みの問題かもしれないが、何がインストールされていて何が実行中なのか把握できる小さな構成が良い。VSCode、ブラウザ IDE、拡張、同期、トークン、任意のプラグインが混ざると、何が何にアクセスしているのか分かりにくくなる
それは Microsoft 公式の Python 拡張の機能で、他の面ではまだまともに使えるほぼ唯一の拡張だったが、自分のプロジェクトで使っているものとは違うバージョンのライブラリの型定義をインストールしていた。検証されていないサードパーティコードを平然と実行しているように見えて非常に不安だったし、設定で無効にもできなさそうだった。
「その後は振り返らなかった」と言いたいところだが、正直ここ 1〜2 年は Neovim がほぼアップグレードのたびに自分の設定を定期的に壊すようになっている。いつかそうなる気配はあった。厳密には 10 年経っているのに nvim はまだ最初の安定版を出しておらず、だから不安定さを責めることはできないが、覚えておく価値はある。
素の Vim に戻るべきか考えている。便利な機能はかなり失うだろうが、仕事の途中で壊れた機能をデバッグしなければならない場面は減ってほしい
プラグインを大量に入れたり SpaceVim のようなものを使ったりする必要がない。一度試してみると気に入るかもしれない
nvim に慣れるには時間がかかるが、慣れればより速い。それでも多くの人が居心地のいい領域にとどまる理由はよく分かる
完全公開 は良い判断だったと思う。MSRC に不満を持つ人があまりにも多く、Nightmare Eclipse の件のように、もう今にもあふれ出しそうになっている。
こうした公開が積み重なれば、MSRC が自省して自分たちこそ問題だと気づくかもしれない。可能性は低そうだが、期待することはできる
それでも少なくとも試してみるか、公開する数日前には知らせておくべきだったと思う。どうなるかは分からないのだから
記事はとても良かったが、最後の部分で少し混乱した。自分の理解が合っているか確認したい。
著者は、新しい発行元信頼システムのせいで、ショートカットのトリックだけでは悪意ある拡張を直接インストールできないと言っており、発行元チェックのないローカルワークスペース拡張でこれを回避できるが、CSP に阻まれるとも言っていた。
解決策は、「発行元確認なしで拡張をインストールする」ショートカットをバインドする ローカルワークスペース拡張 をインストールすることに見える。
つまり、1) 拡張が 2 つ必要という意味なのか気になる。1 つ目はキー バインドだけを行うローカル拡張で、2 つ目は実際の悪意ある拡張であり、CSP のためローカルである必要はなく、実際ローカルにはできないのか。2) CSP はローカル拡張の JS だけを止めて、
package.jsonやショートカット追加機能までは止めないのか、という点が気になる最も直接的な実行のために
my-extension/extension.jsを入れてみることはできるが、CSP によって阻まれる。ただし、script-srcCSP はスクリプトだけを止めるものなので、package.jsonの取得は許可される。そのため、それを利用してキー バインドを提供しているMSRC の状況 は本当に信じがたい。
もっと良い資料もあるだろうが、The Primeagen のこの動画は良い導入資料だと思う。
https://www.youtube.com/watch?v=9kxx5xp5nTQ
「この動作を許可する唯一の方法は、異なるオリジンの 2 つのウェブページが
Window.postMessage()API で協調することだ」という部分には、小さな nitpick がある。iframe や親ウィンドウが
location.anchorプロパティを変更する方法でも通信できる