FlawOpen

flawopen.com/Teardowns/cve-2026-102437-deepseek-reasonix-git-filter-command-injection

● CVE-2026-102437 · CVSS 7.8[2] · 高
セキュリティ研究 · FlawOpen

CVE-2026-102437:Reasonixのdiff表示でGitコマンド注入

細工した.gitattributesと.git/configの組み合わせで、Reasonixのdiff表示時にGit cleanフィルターが動作します。npmは1.39.3、Studioは2.21.0へ更新。[1], [2], [3], [6], [7]

💡 わかりやすい解説 (ELI5)

2枚のラベルを比べるだけだと約束する仕分け台を想像してください。荷物には、内容を比べる前に登録済みの補助処理を実行するよう指示する経路メモを付けられます。ReasonixはGitのいくつかの補助機能を無効にしていましたが、ファイル単位のcleanフィルターは別の経路でした。差分画面がGitにファイルの準備を依頼すると、このフィルターが開発者の権限で実行される可能性がありました。[1], [3]

主要な概念と専門用語

Git cleanフィルター
作業ツリー内のファイル内容をリポジトリに保存される形式へ変換するため、Gitが実行する設定済みの変換処理。[1], [3]
.gitattributes
特定のパスに名前付きフィルターを割り当てられる、リポジトリで追跡されるファイル。[1]
.git/config
名前付きフィルターが呼び出すコマンドを定義できるリポジトリローカルのGit設定。通常のcloneではこのローカルファイルはコピーされない。[1], [3]
差分ビューアー
Gitにファイルの作業ツリー上の内容を準備させ、コミット済みの内容と比較する機能。[1]

フレームワーク対応

  • このガイドで説明する弱点の CWE 参照: CWE-78

根本原因の分析 (Root Cause)

Reasonixは git diff に --no-ext-diff と --no-textconv を付けていましたが、これらは外部diffとテキスト変換を制御するオプションであり、Gitのcleanコンテンツフィルターは無効にしません。ワークスペースの変更ビューは選択されたパスに対して git diff を呼び出します。リポジトリの .gitattributes がそのパスにフィルターを割り当て、ローカルGit設定がドライバーのコマンドを定義していると、比較用blobの生成中にGitがそのコマンドを実行する可能性があります。このCVEはローカル攻撃者を想定し、ユーザーが差分を表示する操作を必要とします。GitLabの開示によると、通常のcloneでは .git/config は転送されません。アーカイブ、同期フォルダー、キャッシュ、事前構築されたdevcontainerには含まれる場合があります。また、同じユーザー権限で動作するローカルエージェントがリポジトリのローカル設定を書き込む経路も説明されています。影響はReasonixユーザーの権限でのローカルコマンド実行であり、GitLab上のリポジトリを開くだけで発生するリモートコード実行ではありません。[1], [3]

ステップ・バイ・ステップの攻撃フロー

ステップ 1

ワークスペースに信頼できないリポジトリ状態が入る

追跡対象の属性がファイルにフィルターを割り当て、ローカルGit設定がそのフィルターを定義するプロジェクトを開きます。メンテナーのアドバイザリーでは両方が必要です。通常のcloneだけではローカル設定は含まれません。[1], [3]

ステップ 2

開発者がファイルの差分を要求する

Reasonixの変更ビューが選択したファイルについてGit diffを実行します。このUI操作がCVEで必要となるユーザー操作です。[1], [3]

ステップ 3

Gitがcleanフィルターを適用する

Gitは比較用の内容を準備し、設定済みのcleanフィルターを呼び出すことがあります。 --no-ext-diff と --no-textconv は別の機能を無効にするため、このフィルターは抑止しません。[1], [3]

ステップ 4

フィルターがユーザー権限で実行される

設定されたフィルターコマンドはReasonixプロセスの権限で動作します。記録されている影響は、そのユーザー権限でのローカルコマンド実行です。[1], [2], [3]

ソースコード比較:脆弱 vs 堅牢化

これは説明用の引数ビルダーであり、そのまま適用できるパッチではありません。呼び出し側は .gitattributes、.git/info/attributes、およびグローバル/システムの属性ファイルで選択されるすべての driver を列挙し、完全な走査が成功した場合に限り discoveryComplete を設定する必要があります。この関数は渡された名前を検証してコマンドライン設定の上書きを生成するだけで、driver の検出や確認は行いません。脆弱な呼び出しは GitLab の開示から引用し、防御パターンはメンテナーの指針とマージ済みの強化 PR を独自に統合したものです。[1], [3], [4], [5]

ソース抜粋: desktop/workspace_changes.go

脆弱な実装
// GitLab Threat Research が引用したソースの呼び出し箇所です。関数全体ではありません。
args := []string{"-C", base, "diff", "--no-ext-diff", "--no-textconv", "--relative", "HEAD", "--", filepath.FromSlash(rel)}
堅牢化されたセキュアパッチ
// Go の引数ビルダーを示す例であり、ベンダーパッチの逐語的な再現ではありません。
package gitguard

import (
    "errors"
    "fmt"
    "path/filepath"
    "regexp"
)

// 呼び出し側は有効な属性が選択するすべてのフィルターを検出し、完全な集合を渡す必要があります。
// このヘルパーが行うのは、Git の全設定スコープでそれらの driver キーを無効化することだけです。
var filterDriverName = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`)

func hardenedDiffArgs(base, rel string, drivers []string, discoveryComplete bool) ([]string, error) {
    if !discoveryComplete {
        return nil, errors.New("cannot safely diff: filter-driver discovery failed")
    }
    args := []string{"-C", base}
    seen := map[string]struct{}{}
    for _, driver := range drivers {
        if !filterDriverName.MatchString(driver) {
            return nil, fmt.Errorf("cannot safely diff: unsupported Git filter driver name %q", driver)
        }
        // Git config のサブセクション名は大文字小文字を区別します。driver 名の大小文字を変換しないでください。
        key := driver
        if _, ok := seen[key]; ok {
            continue
        }
        seen[key] = struct{}{}
        for _, setting := range []string{"clean", "smudge", "process", "required"} {
            args = append(args, "-c", "filter."+driver+"."+setting+"=")
        }
    }
    return append(args, "diff", "--no-ext-diff", "--no-textconv", "--relative", "HEAD", "--", filepath.FromSlash(rel)), nil
}

エンジニアリング&システム堅牢化チェックリスト

参考資料