こんにちは、株式会社TIMEWELLの濱本です。
画面に何も表示されない文字に、実行されるコードが入っている。
こう書くと奇術のようですが、実際に起きています。GlassWormという攻撃で、開発者が使うエディタの拡張機能を経由して広がりました。
この攻撃が厄介なのは、被害の大きさより前提を壊したことです。私たちはソフトウェアの安全を「人がコードを読んで承認する」という仕組みで守ってきました。読めないものは、承認も否認もできません。
何が起きたのか
事実から押さえます。2025年10月18日、Koi Securityが検出して公表しました1。
侵害されたのはVS Code拡張機能です。配布先であるOpenVSXなどのマーケットプレイスに、悪意あるコードを仕込まれた拡張機能が並びました。確認された侵害拡張機能は16件、総ダウンロード数は35,800件とされています1。
拡張機能が行うことは、おおむね次のとおりです1。
- npm、GitHub、OpenVSX、Gitの認証情報を盗む
- 49種類の暗号資産ウォレット拡張機能を対象に情報を取る
- 開発者の端末にSOCKSプロキシを立て、攻撃側の通信の中継点にする
- VNCサーバーを隠して設置し、遠隔操作できる状態にする
そして最も重いのが、盗んだ認証情報を使って、さらに別のパッケージや拡張機能を汚染するという点です1。開発者が1人感染すると、その人が公開権限を持つ資産が次の感染源になる。**自己増殖します。**これはVS Code拡張機能では初めて確認された挙動とされています2。
AIセキュリティ研修を、本気で
OWASP・NIST・ISO 42001・経産省ガイドライン全準拠の2日間集中講座。経営層と現場で分けて受講できます。
なぜ人の目に映らないのか
技術の核心はここです。丁寧に書きます。
使われているのは異体字セレクタ(Variation Selector)というUnicode文字です。範囲はU+FE00からU+FE0F(16文字)と、U+E0100からU+E01EF(240文字)。GlassWormが主に使ったのは後者で、256通りの値を表現できるため、0から255までのバイト値にそのまま対応します3。
異体字セレクタは、同じ文字の見た目の違いを指定するための文字です。日本語の漢字で、同じ字に複数の字形があるときに「こちらの形で表示せよ」と指定する、といった用途で使われます。**つまり、それ自体は何も描画しません。**直前の文字の表示方法を変える指示であって、表示される中身を持たない。
だから、エディタで見ると何もない行に見えます。GitHubの差分表示でも同じです。行が空に見えるだけで、そこに数百文字が入っていても分かりません3。
実行される仕組みはこうです3。
- 各文字のコードポイントから、計算でバイト値を取り出す
- 取り出したバイトを並べてBase64文字列を組み立てる
- デコードするとJavaScriptのソースコードになる
- それを
evalで実行する
つまり不可視文字そのものはデータであって、それを組み立てて動かす短いコードが別にあります。攻撃者から見れば、本体を「見えないデータ」に逃がして、目に見える部分を無害に見せられる、という構造です。
自社の開発体制がこの種のリスクに耐えられるかを整理したい方は、AIリテラシー診断で現在地を確認しておくと、この先の話が自社に当てはめやすくなります。
どの場面で踏むのか
具体的な状況を挙げます。どれも特別なことをしていません。
場面1。拡張機能を入れるとき。
エディタの拡張機能を探して、ダウンロード数が多いものを選ぶ。これが最も直接的な経路です。GlassWormで最も被害の大きかった拡張機能は3万5,000件超のインストールがありました1。**人気があることは、安全であることを意味しません。**むしろ人気のあるものほど、乗っ取られたときの被害が大きい。
しかも今回は、もともと正規だった拡張機能が後から汚染された形です。インストールした時点では問題がなくても、自動更新で入ってきます。「入れるときに確認した」では防げません。
場面2。プルリクエストをレビューするとき。
同僚が出したPRを開き、差分を目で追う。**空行が1行増えているように見える。**気にせず承認する。これで通ります。
ここが一番怖いところです。レビュアーの注意力の問題ではありません。**見えないものは、どれだけ注意深く見ても見えない。**熟練者ほど「差分が小さいから安全」と判断しやすいので、むしろ逆に働くこともあります。
場面3。依存パッケージが更新されるとき。
npmのパッケージを更新する。ロックファイルの差分を確認する。中身までは読まない。**そもそも数万行を毎回読める人はいません。**上流が汚染されていれば、そのまま自社のビルドに入ります。
場面4。AIにコードを書かせる、読ませるとき。
これは新しい経路です。AIコーディングエージェントにリポジトリを読ませて作業させる。エージェントはファイルをテキストとして読みます。**不可視文字は、テキストとしては普通に存在します。**人間には見えず、AIの出力にも現れない。そのまま次のファイルへコピーされることもあります。
開発ツールそのものが感染経路になる構造は、AIツールが感染経路になる時代でTanStackの事例を扱いました。AIが存在しないパッケージ名を提案してしまう問題はSlopsquattingにあります。
消せない指令系統、という新しさ
もう一点、対処が難しい要素があります。C2(指令サーバー)の置き場所です。
GlassWormは指令の受け取り口を三重にしています1。
主系統はSolanaブロックチェーンのトランザクションメモです。ブロックチェーンに書き込まれた情報は、性質上あとから消せません。通報しても止められない指令系統ということです。従来の対策である「悪性ドメインをテイクダウンする」「IPを遮断する」が効きません。
副系統として攻撃者のサーバーへの直接接続があり、さらに予備としてGoogleカレンダーのイベント名に符号化した指令を置く経路も用意されていました1。予備がここまで用意されているのは、運用として本気だということです。
この構造を見ると、「配布元を止める」だけでは終わらないことが分かります。すでに端末に入ったものは、指令を取り続けます。
目視レビューをやめる、という対策
では何をするか。方針は明確です。人の目に依存する検査を、機械の検査に置き換える。
ひとつめ。手元で検査してからPRを出す。
Node.jsの環境なら、次のコマンドで不可視文字を検査できます4。
npx anti-trojan-source src/index.js
これをコミット前の習慣にする。手元で落ちれば、レビュアーの時間を使いません。
ふたつめ。CIで機械的に落とす。
より確実なのはここです。GitHub Actionsであれば、次のような手順を挟みます4。
- name: Scan for invisible Unicode attacks
run: npx anti-trojan-source --files='**/*.{js,ts,jsx,tsx}' --json
検出されたらビルドを失敗させる。**人間の承認より前に、機械が止める。**扱う言語が増えたら拡張子を足します。ESLintのプラグインとして既存の検査に組み込む形もあります4。
ここで大事なのは、「気をつける」を対策にしないことです。今回の攻撃は、注意力では防げない設計になっています。注意力で防げないものは、仕組みで防ぐしかありません。
みっつめ。認証情報を入れ替え、減らす。
GlassWormの増殖は、盗んだ認証情報が燃料です。npm、GitHub、OpenVSXのトークンを定期的に入れ替える。そして使っていないトークンを開発機から消す4。個人の開発機に何年も前のトークンが残っているのは、かなりよくある話です。棚卸しを一度やるだけでも、増殖の燃料は減ります。
よっつめ。拡張機能を資産として管理する。
各自が好きに入れている状態をやめ、組織として何が入っているかを把握する。導入時の審査だけでなく、更新で後から汚染される経路があるので、継続的に見る必要があります。「入れるときに確認した」は、今回のような後付け汚染には効きません。
AIに読ませる時代に、この穴はどう効くか
最後に、少し先の話をします。
**人間が読まないコードの割合は、これから確実に増えます。**AIが書き、AIが読み、AIがレビューする。そのとき「人間の目に見えない文字」は、誰のチェックにも引っかからない領域になります。
AIはテキストとして不可視文字を扱えますが、それを異常として報告するとは限りません。「この行には異体字セレクタが240文字並んでいます」と言ってくれる保証はない。文字としては妥当なUnicodeなので、壊れたデータですらないからです。
だから、この検査は自動化のパイプラインに置くべきものです。人間のレビューにもAIのレビューにも頼らず、決定的なルールで弾く。バイト列を見て、想定外の文字が入っていたら止める。それだけです。
正直なところ、私がこの攻撃でいちばん引っかかったのは、技術的な高度さではなく着眼点です。Unicodeの異体字セレクタは、多言語の文字を正しく表示するために存在する仕様です。**善意の互換性のために用意された余白が、そのまま隠し場所になった。**新しい脆弱性を見つけたのではなく、既存の仕様の使われ方をずらしただけです。
そして、これは初めてではありません。
2021年、ケンブリッジ大学のNicholas BoucherとRoss Andersonが「Trojan Source」を公表しました。こちらはUnicodeの双方向テキスト制御文字を悪用するもので、アラビア語やヘブライ語のように右から左へ書く言語を正しく表示するための仕様を使います。トークンの並び順を表示上だけ入れ替えることで、人間が読むコードとコンパイラが解釈するコードを別物にできるという攻撃でした5。
影響範囲は広く、C、C++、C#、JavaScript、Java、Rust、Go、Pythonといった主要言語のコンパイラが対象になりました。CVE-2021-42574とCVE-2021-42694が割り当てられています5。
先ほど紹介した検査ツールの名前がanti-trojan-sourceなのは、この攻撃に対抗するために作られたからです。5年前の対策ツールが、今回の攻撃にもそのまま効きます。手法は違っても、「表示と実体がずれる」という穴の形が同じだからです。
この形の攻撃は、おそらくまた別の仕様でも起きます。異体字セレクタ、双方向制御、ゼロ幅文字、私用領域。Unicodeには「表示されないが有効な文字」がいくつもあり、そのすべてが候補です。だから対策も一般化しておくべきで、**「自分たちが読んでいるものと、機械が実行しているものが同じである」ことを、どこかで機械的に確かめる。**これが本質だと思っています。
社内のセキュリティ研修や開発体制の見直しについては、WARP SECURITYの考え方が参考になるかもしれません。具体的な状況に踏み込んだ相談はこちらからどうぞ。
Footnotes
-
Koi Security によるGlassWormの検出レポート(2025年10月18日検出、同日公開)。VS Code拡張機能を対象とし、侵害拡張機能は同レポートのIOCセクションで16件(OpenVSX 15件、VS Code 1件)が列挙され、総ダウンロード数は35,800件とされる。窃取対象はnpm認証トークン、GitHub認証情報、OpenVSX認証情報、Git認証情報、および49種類の暗号資産ウォレット拡張機能。感染端末にSOCKSプロキシおよび隠されたVNCサーバーを設置する。指令系統はSolanaブロックチェーンのトランザクションメモにBase64符号化したペイロードURLを埋め込む方式を主系統とし、攻撃者サーバーへの直接接続、およびGoogleカレンダーのイベントタイトルに符号化する方式を予備として持つ。窃取した認証情報を用いてさらに別のパッケージおよび拡張機能を侵害する自己増殖の循環を持つ。https://www.koi.ai/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace / 本記事では、具体的なウォレットアドレス、IPアドレス、URLなどのIOCは、悪用の可能性を避けるため記載しない。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
GlassWormはVS Code拡張機能を対象とする自己増殖型のワームとして初めて確認されたものとされる。https://www.veracode.com/blog/glassworm-vs-code-extension/ / https://www.bleepingcomputer.com/news/security/self-spreading-glassworm-malware-hits-openvsx-vs-code-registries/ ↩
-
使用された不可視文字は異体字セレクタ(Variation Selector)であり、範囲はVS1からVS16がU+FE00からU+FE0F(16文字)、VS17からVS256がU+E0100からU+E01EF(240文字)。GlassWormの実装では後者が主に使用され、0から255のバイト値に対応する256通りの値をサポートした。異体字セレクタは同一文字の異なる視覚的表現を選択する機能を持つため、それ自体は描画されず、多くのテキストエディタで視覚的に現れない。実行はコードポイントからバイト値を復元し、Base64文字列として再構成、デコードしてJavaScriptソースコードを生成したうえで
eval()を経由して行われる。https://www.endorlabs.com/reports/invisible-threats-glassworm-unicode-vscode ↩ ↩2 ↩3 -
対策の具体。目視のコードレビューのみに依存しないこと、
npx anti-trojan-source src/index.jsによるプルリクエスト送信前の手元検査、GitHub Actions でのnpx anti-trojan-source --files='**/*.{js,ts,jsx,tsx}' --jsonによるマージ前の自動検査と検出時のブロック、ESLintプラグインとして既存ワークフローへ組み込む選択肢、npm・GitHub・OpenVSXのトークンの定期的な入れ替えと不要な認証情報の削除、およびサードパーティコンポーネントの継続的な信頼性確認は、いずれも同記事による。https://snyk.io/articles/defending-against-glassworm/ ↩ ↩2 ↩3 ↩4 -
Trojan Source。2021年にケンブリッジ大学のNicholas BoucherおよびRoss Andersonが公表した攻撃手法で、Unicodeの双方向テキスト制御文字を悪用し、ソースコードの表示上の見え方と実際の実行内容を別のものにする。攻撃者はUnicodeの制御文字によりエンコーディングの層でトークンの並び順を入れ替えることができ、コンパイラと人間のレビュアーが異なる論理を見る状態を作り出せる。C、C++、C#、JavaScript、Java、Rust、Go、Pythonなど主要言語のコンパイラが影響を受け、CVE-2021-42574およびCVE-2021-42694が割り当てられている。https://access.redhat.com/security/vulnerabilities/RHSB-2021-007 / https://thehackernews.com/2021/11/new-trojan-source-technique-lets.html / https://krebsonsecurity.com/2021/11/trojan-source-bug-threatens-the-security-of-all-code/ ↩ ↩2






