はじめに:「コードを見ても見えない」攻撃が現実になった
2026年3月3〜9日、GitHubで史上最大規模のサプライチェーン攻撃が確認された。
433件以上のリポジトリが汚染された。npm、VSCode拡張マーケットプレイス、Open VSXを横断する攻撃だ。しかし最も衝撃的だったのはその規模ではなく、「見つけることが原理的に不可能に近い」攻撃手法だった。
攻撃者は悪意あるコードを、Unicodeの正規仕様に含まれる「幅ゼロの不可視文字」の中に丸ごと隠した。開発者がコードレビューをしても、GitHubで差分を見ても、linterを走らせても——一切何も表示されない。1行のコードのように見える部分に、18,000文字以上の不可視ペイロードが埋め込まれていた。
「GlassWorm(グラスワーム)」と命名されたこのキャンペーンは、2025年3月から断続的に進化を続けており、2026年3〜4月の第5波で一気に規模を拡大した。GitHubトークン・npmクレデンシャル・クラウドシークレット・暗号資産ウォレット——開発者が日常的に扱うあらゆる認証情報が、誰の目にも見えないコードによって静かに外部へ送り出されていた。
- Unicodeの「異体字セレクタ」(U+FE00〜FE0F・U+E0100〜E01EF)を悪用し、1行に見えるコードに18,000文字超の不可視マルウェアを埋め込む手法。あらゆるエディタ・GitHub・diffツールで完全に見えない
- GitHubトークンを窃取して正規リポジトリに force-push し、LLMが生成した「本物そっくりのコミット」で痕跡を隠蔽。433件以上を汚染
- 通常のサーバーではなくSolanaブロックチェーンのトランザクションメモをC2通信に使用。テイクダウン不可能な設計
難しい話は後のセクションに任せて、まず「何が起きているか」だけ把握してほしい。
① 侵入
攻撃者はまず何らかの方法で開発者の GitHub トークンを入手する。(別の感染済みパッケージ・フィッシング・CI 環境変数の漏洩など)
② 見えないコードを埋め込む
正規のリポジトリに、人間の目にはまったく見えない悪意あるコードを force-push する。コミットメッセージは AI が生成した自然な文面なので、履歴を見ても気づかない。
③ 被害者が「普通に使う」だけで発動
汚染されたパッケージや拡張機能をインストールして実行すると、見えないコードが静かに動き出す。開発者は何もおかしいことに気づかない。
④ 認証情報が盗まれる
GitHub トークン・AWS キー・npm トークン・暗号資産ウォレット……開発者が持つあらゆる認証情報を収集して、攻撃者のサーバーへ送信する。
1. 🔬 Unicodeの仕様を武器にする——「不可視コード」の仕組み
そもそも異体字セレクタとは
Unicodeには「異体字セレクタ(Variation Selectors)」と呼ばれる文字群が存在する。本来の用途は絵文字の表示バリアントを指定することだ。例えば「☎️」と「☎」のように、同じコードポイントでも絵文字スタイルとテキストスタイルを切り替える際に使われる。
GlassWormが悪用するのは次の2つの範囲だ:
| 範囲 | 文字数 | 本来の用途 |
|---|---|---|
| U+FE00〜U+FE0F | 16文字 | テキスト/絵文字バリアント切り替え |
| U+E0100〜U+E01EF | 240文字 | CJK統合漢字の字体変換 |
合計256文字——つまり1バイト(0x00〜0xFF)を完全に表現できる。
なぜ完全に見えないのか
これらの文字は、すべてのコードエディタ・ターミナル・GitHub・diffツールが 「表示すべき内容のない文字」 として処理するよう設計されている。幅はゼロ、カーソルも動かない。コードに貼り付けても何の変化も見えない。
一方でJavaScript/TypeScriptのエンジンは、これらの文字を有効なコードポイントとして処理する。つまりランタイムには「見えている」が、人間には「見えない」という状態が成立する。
どこに隠されているか——「文字列リテラル」と「コメント」の中
異体字セレクタを変数名に使うと JavaScript は Syntax Error を出してしまう。そのため GlassWorm が使う隠し場所は文字列リテラルの内側か、// コメントの後ろだ。どちらもエンジンが「文字列の中身」として処理する場所であり、構文エラーにならない。
// パターン1:文字列リテラルの内側に隠す
const config = "https://api.example.com";
// ↑この文字列の中にU+FE00〜FE0F/E0100〜E01EFで
// エンコードされた18,000文字超のペイロードが隠れている
// パターン2:コメントの後に隠す(自己参照型)
// setup module ← この行末以降に不可視コードが続いている
// fs.readFileSync(__filename) で自身のソースを読み取りデコードして eval()
異体字セレクタは1文字あたり3〜4バイトを消費する。18,000文字のペイロードなら約54〜72KBが見えないまま混入している計算になる。
例えば、通常の index.js が 1KB 程度なのに、GlassWorm 混入後は 75KB になっている——という状態だ。GitHub の Web UI 上では依然として「数行の変更」に見え、git diff でも変更行数は変わらないため、**ファイルサイズを確認しないと気づけない「静かなる肥大化」**になる。
怪しいと思ったら wc -c ファイル名 でバイト数を確認してみてほしい。コードの行数とファイルサイズが著しくアンバランスなファイルは要注意だ。
攻撃者のデコーダは起動時に次のように動作する:
// 概念的なデコーダ(glassworm-hunter が検出するパターン)
const chars = [...str].map(c => c.codePointAt(0));
const payload = chars
.filter(cp => (cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF))
.map(cp => cp >= 0xE0100 ? cp - 0xE0100 + 16 : cp - 0xFE00)
.map((hi, i, arr) => i % 2 === 0 ? (hi << 4) | arr[i+1] : null)
.filter(Boolean);
eval(String.fromCharCode(...payload));
各異体字セレクタをニブルまたはバイトにマッピングし、バイト配列を復元したあと eval() で実行する。視覚的には「空の文字列」が、ランタイムではフルサイズのマルウェアスクリプトとして動作する。
linter(リンター) とは、ソースコードを自動的に解析して「バグになりそうな書き方」「スタイル違反」「潜在的な問題」を警告してくれるツールのこと。JavaScript であれば ESLint、Python なら flake8 や Ruff が代表的だ。
コードを書きながらリアルタイムで指摘が入るため、コードレビュー前に問題を潰せる「自動の品質チェック係」として多くの開発チームで必須ツールになっている。
しかし GlassWorm が使う異体字セレクタは Unicode の正規仕様に含まれる合法な文字のため、linter は「問題のある記述」と判断できずそのまま通過してしまう。「悪いコードを検出する」ために設計されたツールが、「そもそも文字として見えないコード」には対応できないという盲点だ。
2. 🕵️ 攻撃の段階的アーキテクチャ——4ステージの多段ペイロード
GlassWormは単純なマルウェアではなく、4段階で動作する精緻なシステムだ。
Stage 1:ローダー(不可視コードのデコード〜C2接続)
汚染されたコードが実行されると、まず不可視Unicodeをデコードしてステージ1ローダーが起動する。ここで次の処理が走る:
process.env.LANGやIntl.DateTimeFormatでロシア語ロケールを検出した場合は実行を中断(地政学的な帰属隠しのため)- Solanaブロックチェーンに接続してC2サーバーのURLを取得(詳細は次のセクション)
- Stage 2ペイロードをダウンロードして実行
Stage 2:情報窃取
開発者環境に存在するあらゆる認証情報を収集して外部サーバーに送信する:
| カテゴリ | 対象 |
|---|---|
| 開発者認証情報 | GitHub トークン・npm トークン・SSH 秘密鍵・AWS/GCP/Azure・Heroku・DigitalOcean・Terraform |
| 暗号資産ウォレット | MetaMask・Phantom・Coinbase Wallet・Exodus 等 71種類のブラウザ拡張ウォレット |
| ブラウザデータ | Chrome/Edge のCookie・保存済みパスワード・クレジットカード情報・履歴 |
| システム情報 | 環境変数全体・実行中プロセス・インストール済みアプリ |
Stage 3a:ハードウェアウォレットフィッシング
.NET WPFバイナリ(Assaac.exe)がLedger LiveやTrezorを偽装したダイアログを表示し、USB接続を検出すると「リカバリーフレーズの確認」を求めてシードフレーズを窃取する。
Stage 3b:永続型WebSocket RAT
DHT + Socket.IOで永続的なリモートアクセスを確立する。コマンドセットには隠れリモートデスクトップ(start_hvnc)・SOCKSプロキシ(start_socks)・ブラウザデータ再窃取・任意コード実行(eval())が含まれる。
Stage 4:Chrome拡張 RAT
Google Docs Offlineを偽装したChrome拡張機能をインストールし、以後のブラウザ操作を恒常的に監視する。キーストローク全記録・クリップボード監視・スクリーンショット・DOMスナップショットをリアルタイムで収集。特筆すべきは .bybit.com が事前に監視対象として設定されており、secure-tokenとdeviceid Cookieを検出した瞬間にオペレーターへリアルタイム送信する。
3. ⛓️ Solanaブロックチェーンを「C2サーバー」にする——テイクダウン不可能な設計
GlassWormの技術的な目玉の1つが、ブロックチェーンをC2インフラとして利用する「デッドドロップ」方式だ。
通常のマルウェアは攻撃者のサーバーアドレスをコードにハードコードする。そのIPやドメインが当局に報告されれば、テイクダウンで封じ込められる。
GlassWormは違う。マルウェアは起動後、特定のSolanaウォレットアドレスのトランザクション履歴を5〜10秒おきにクエリし、最新トランザクションの 「メモフィールド」 にBase64でエンコードされたC2 URLを見つけ出す仕組みだ。
攻撃者のSolanaウォレット
└── 最新トランザクションのメモフィールド
└── Base64("https://evil-c2-server.example.com/payload")
└── デコード → Stage 2ペイロードをダウンロード
この方式の利点を攻撃者視点で整理すると:
- ブロックチェーンは不変・検閲不可能——ホスティング会社にテイクダウンを要請できない
- ウォレットアドレスを変えるだけでC2インフラを即座に切り替えられる
- 「Solana RPCへの通信」 は通常の金融アプリも行うため、ネットワーク監視でブロックしにくい
- バックアップとしてGoogle カレンダーのイベント(
uhjdclolkdn@gmail.com)にもURLを格納し、Solanaが使えない場合のフォールバックとして機能する
サーバー型C2の場合、インシデント対応チームはIPブロック・ドメイン停止でマルウェアを機能停止できた。
しかしSolana上のデータは誰にも消せない。ウォレット自体をブラックリストに登録することは技術的に可能だが、攻撃者は新しいウォレットを10秒で作れる。C2インフラの「耐削除性」が従来型マルウェアとは根本的に異なる。
4. 📊 キャンペーンの全貌——5波にわたる進化と被害規模
GlassWormは一度の攻撃ではなく、約1年かけて技術を高度化しながら繰り返し展開されたキャンペーンだ。
| Wave | 時期 | 主な内容 |
|---|---|---|
| Wave 1 | 2025年3月 | npm パッケージへの初期攻撃。シンプルな eval() ベース |
| Wave 2 | 2025年中頃 | 中東政府機関を標的とした攻撃。難読化を強化 |
| Wave 3 | 2025年後半 | Rust コンパイル済みバイナリで検出回避をさらに強化 |
| Wave 4 | 2025年12月 | macOS 対応。AES-256-CBC 暗号化。ハードウェアウォレットトロイ木馬を追加 |
| Wave 5 | 2026年3〜4月 | GitHub・npm・VSCode・Open VSX を横断した大規模同時攻撃。433件以上を汚染 |
2026年3月3〜9日の大規模GitHub攻撃の内訳:
| プラットフォーム | 汚染件数 |
|---|---|
| GitHub(JavaScript/TypeScript) | 151件以上 |
| GitHub(Python) | 200件以上 |
| VSCode Open VSX 拡張機能 | 72件以上 |
| npm パッケージ | 複数 |
確認された被害リポジトリの例として pedronauck/reworm(スター数1,460、JavaScript状態管理ライブラリ)が最大の被害例として報告されている。
さらに2026年4月26日には、Open VSXの73件の「スリーパー拡張機能」が新マルウェアキャンペーンを一斉発動。事前にインストールされた状態で長期間潜伏し、一定のタイミングで活性化するという手法で、感染拡大の初期タイミングをさらに特定しにくくした。
なぜ VSCode 拡張機能が特に狙われるのか
VSCode 拡張機能がサプライチェーン攻撃の標的として魅力的な理由は、プラットフォームの設計にある。
① インストールされれば起動は自動
拡張機能は VSCode の起動と同時に自動ロードされる。ユーザーが意識的に「実行する」操作は不要だ。感染した拡張機能を入れた瞬間から、次のエディタ起動でペイロードが動く。
② 更新も自動
デフォルト設定では拡張機能は自動更新される。最初は正常な拡張機能としてインストールされ、後のアップデートで悪意あるコードが追加されても、ユーザーは気づかない。GlassWormの「スリーパー拡張機能」はまさにこの仕組みを悪用した。
③ マーケットプレイスの審査に盲点がある
Microsoft の VSCode Marketplace と Open VSX はいずれも、公開時に静的な審査を行うが、不可視Unicode文字の有無を専用スキャンする仕組みが当時は存在しなかった。審査を通過した正規拡張機能と汚染済み拡張機能を、ダウンロード前に見分けることは事実上不可能だった。
④ 開発者の環境は認証情報の宝庫
エディタには環境変数・シェル連携・.env ファイルへのアクセスが当たり前のように設定されている。開発者の手元 PC は、侵害した場合の「収益」が最も高いターゲットの1つだ。
5. 🤖 AIが作り出す「完璧なカバーコミット」
GlassWormが特筆される理由のもう1つが、LLMを活用したコミット履歴の偽装だ。
なお、不可視Unicode文字を使ったコード攻撃の先行研究として Trojan Source(CVE-2021-42574) が2021年に発表されている。双方向制御文字(U+202A〜U+202E)でコードロジックを視覚的に偽装する手法だ。GlassWormはその技術的系譜を受け継ぎつつ、「異体字セレクタによる完全隠蔽」「AI生成コミット」「Solana C2」を組み合わせて実際の大規模攻撃に昇華させた。
攻撃の手順は次の通りだ:
- 窃取したGitHubトークンでターゲットリポジトリのコード・コミット履歴・コーディングスタイルを解析
- LLMがそのリポジトリ固有の文体・コメントスタイル・変数命名規則を模倣した「自然なコミットメッセージ」を生成
- 不可視Unicodeを挿入したコードを、「ドキュメント更新」「バージョンバンプ」「軽微なリファクタリング」に見せかけたコミットとして force-push
- 元コミットのメッセージ・作者名・作者日時を保持したまま履歴を書き換える
結果として、GitHub上の「最近のコミット」を見ても、コミットした人が書いたような自然なメッセージが並んでいるだけで、一見して異常を検知できない。
セキュリティの世界では長らく「ソースコードが公開されていること自体がセキュリティの証明」(オープンソースの透明性)という前提があった。
GlassWormはその前提を2つの方向から同時に破壊した:
① 見ても見えない——不可視Unicodeによりコードレビューが機能しない
② コミット履歴が信頼できない——LLM生成コミットにより「誰が・いつ・何のために」という情報が偽装される
「GitHubにソースコードがある=確認できる」は、もはや安全の根拠にならない。
6. 🛡️ 今すぐできる検出と防御
感染確認:IOC(侵害指標)
自分のリポジトリや利用しているパッケージが汚染されているか確認する手がかり:
# マーカー変数名をリポジトリ全体で検索(GlassWorm の特徴的な変数名)
grep -r "lzcdrtfxyqiplpd" .
# 不可視Unicode文字を含むファイルを検索(Python例)
python3 -c "
import os, glob
for f in glob.glob('**/*.js', recursive=True) + glob.glob('**/*.ts', recursive=True):
with open(f, 'rb') as fp:
content = fp.read().decode('utf-8', errors='ignore')
for cp in range(0xFE00, 0xFE10):
if chr(cp) in content:
print(f'FOUND in {f}')
break
"
# 持続性ファイルの確認
ls ~/init.json 2>/dev/null && echo "要調査"
疑わしいC2 IPアドレス(IOC):
45.32.150[.]251:4787(WebSocket RAT)217.69.3[.]152:80(データ送信エンドポイント)140.82.52[.]31:80/wall
検出ツールの活用
glassworm-hunter(AFINE社、OSSのPythonスキャナー)がもっとも包括的な検出ツールだ。ネットワークリクエストなしのローカル解析で、連続した異体字セレクタを検出する:
pip install glassworm-hunter
glassworm-hunter scan ./src
# 3個以上の連続: MEDIUM、10個以上: CRITICAL
anti-trojan-sourceは277種の紛らわしいUnicode文字を検出する:
npx anti-trojan-source --files='**/*.{js,ts,jsx,tsx,py,java,go,rs}'
根本的な防御策
| 対策 | 方法 |
|---|---|
| CI/CDに組み込む | glassworm-hunter を GitHub Actions のプルリクエストチェックに追加 |
| pre-commit フック | バリエーションセレクタを含むコミットを自動ブロック |
| force-push 保護 | GitHub の Branch Protection で force-push を禁止(全ブランチ) |
| 拡張機能の依存関係監査 | extensionPack/extensionDependencies フィールドを定期確認 |
| クレデンシャルのローテーション | GitHub トークン・npm トークン・SSH 鍵を即時ローテーション |
| 自動更新の制御 | VSCode 拡張機能の自動更新を無効化してバージョン変更を意図的に確認 |
# .github/workflows/security.yml に追加するだけ
- name: Scan for invisible Unicode
run: |
pip install glassworm-hunter
glassworm-hunter scan . --fail-on-critical
このステップを追加すれば、CRITICAL(10個以上の連続異体字セレクタ)を含むPRが自動的にブロックされる。コードレビューが機能しない場合でも、マージを防ぐ最後の防衛線になる。
まとめ:「見えないコード」が示したオープンソースの次の課題
| 観点 | 内容 |
|---|---|
| 手法 | Unicode異体字セレクタ(U+FE00〜FE0F・U+E0100〜E01EF)で悪意コードを完全不可視化 |
| 被害規模 | GitHub・npm・VSCode/Open VSX で433件以上を汚染(2026年Wave 5時点) |
| 窃取対象 | GitHub/npmトークン・クラウドシークレット・SSH鍵・暗号資産ウォレット71種 |
| C2インフラ | Solanaブロックチェーンのトランザクションメモを使用。テイクダウン不可能 |
| 隠蔽技術 | LLM生成カバーコミット・force-push で git 履歴を偽装 |
| 検出方法 | glassworm-hunter / anti-trojan-source / lzcdrtfxyqiplpd 変数名検索 |
今すぐ実行すべきチェックリスト
| 重要度 | 対策内容 | 実行の目安 |
|---|---|---|
| 最高(必須) | GitHub・npm トークン・SSH 鍵・クラウドシークレットを全てローテーション | 今すぐ |
| 最高(必須) | インストール済み VSCode 拡張機能を棚卸しし、IOC と照合 | 今すぐ |
| 高 | CI に glassworm-hunter を追加してスキャンを自動化 |
24時間以内 |
| 高 | Branch Protection で全ブランチへの force-push を禁止 | 24時間以内 |
| 中 | 開発環境で grep -r "lzcdrtfxyqiplpd" . を実行 |
今日中 |
| 中 | VSCode 拡張機能の自動更新をオフにして手動管理へ切り替え | 今日中 |
| 低 | ~/init.json の有無を確認 |
今週中 |
個人的な見方を書いておく。
GlassWormで最も印象的だったのは、攻撃者が「Unicodeの正規仕様」という、誰も改変できないルールの中にバックドアを見つけたことだ。GitHubもVSCodeもESLintも、これらの文字を「不審なもの」として扱うように設計されていなかった。当然だ——それらは合法な文字だから。
「コードを目視で確認する」という人間の認知能力と、「エンジンが処理する」という計算機の動作の間に、完全な乖離を作り出した。コードレビューが無効化されたのではなく、「コードを見れば判断できる」という前提自体が崩れた。
そして「Solanaの不変性」「LLMの文体模倣」という2026年の最新技術が組み合わさることで、従来型の対応(テイクダウン・目視レビュー)が通用しない攻撃になった。
オープンソースエコシステムへの信頼は、これからはコードそのものの目視ではなく、自動スキャン・アクセス制御・サプライチェーン全体の継続的な監視で維持するものになっていく——GlassWormはその転換点を象徴している。
①73件のOpen VSXスリーパー拡張機能のさらなる活性化の有無、②GitHub・npmによるプラットフォームレベルの不可視Unicode検出機能の導入可否、③Wave 6以降での新手法(他ブロックチェーンの活用など)の登場——これらは状況が変わり次第、記事を更新する予定。
よくある質問(FAQ)
Q. GlassWormに感染したパッケージを npm install しただけで被害を受ける?
汚染パッケージがインストールされただけではペイロードは動作しない。require() / import で実際にそのコードが実行された時点で起動する。ただし多くのパッケージはrequireされた瞬間に副作用を実行するため、インストールして使ったなら対象と考えるべきだ。
Q. VSCode拡張機能の場合はどうなる?
拡張機能は VSCode 起動時に自動ロードされる。感染した拡張機能をインストールしていれば、次のVSCode起動時にペイロードが動作した可能性がある。インストール済みの拡張機能リストを確認し、IOCに掲載されたものがあれば即アンインストールの上、開発者認証情報を全てローテーションすること。
Q. プライベートリポジトリも対象になる?
はい。攻撃者はまずGitHubトークンを何らかの方法(ログイン時・CI環境変数・別の感染済みパッケージ経由)で入手し、そのトークンでアクセスできるリポジトリ全てに対して force-push を試みる。パブリック・プライベートは問わない。
Q. Solanaブロックチェーン経由の通信をファイアウォールでブロックできる?
SolanaのRPCエンドポイントへのHTTPS通信をブロックすることは技術的には可能だが、同じエンドポイントを使う正規のWeb3アプリも動かなくなる。ブロックよりも「異常なSolana RPC通信を検出して警告する」SIEM/NDRルールの追加が現実的だ。
Q. Unicodeの不可視文字を使った攻撃はGlassWorm以外にもある?
Trojan Source(CVE-2021-42574)という先行研究が2021年に発表されており、双方向制御文字(U+202A〜U+202E)を使ったコードロジック偽装が実証されていた。GlassWormはその技術的系譜を受け継ぎつつ、Solana C2・AI生成コミットという独自要素を加えて実際の大規模攻撃に発展させたものだ。
参考
- 不可視文字でマルウエア混入 GitHubなどで汚染拡大、開発基盤の信頼揺らぐ(日経 xTECH)
- Glassworm Returns: Unicode-Based Supply Chain Attack(Aikido Security)
- GlassWorm Malware Hits 400+ Code Repos on GitHub, npm, VSCode, OpenVSX(BleepingComputer)
- GlassWorm Malware Uses Solana Dead Drops for Stealthy C2(The Hacker News)
- GlassWorm Attack Uses Stolen GitHub Tokens to Spread via Force-Push(The Hacker News)
- Defending Against Glassworm Supply Chain Attacks(Snyk)
- glassworm-hunter — OSSスキャナー(AFINE)
- GlassWorm:開発者エコシステムを侵食するサプライチェーン攻撃(ELW テックブログ)
- GlassWorm が拡散する 72 件の悪意のOpen VSX エクステンション(IoTOTSecNews)