FlawOpen

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

● CVE-2026-102437 · CVSS 7.8[2] · Высокий
Исследования · FlawOpen

CVE-2026-102437: внедрение команд Git в Reasonix

Связка .gitattributes/.git/config может запустить фильтр clean при просмотре diff в Reasonix. Обновите npm до 1.39.3 или Studio до 2.21.0.[1], [2], [3], [6], [7]

💡 Простыми словами (ELI5)

Представьте сортировочный стол, который должен только сравнивать две этикетки. На посылке может быть маршрутная записка с указанием запустить зарегистрированный помощник перед сравнением содержимого. Reasonix отключал некоторые механизмы Git, но фильтр clean для отдельных файлов был отдельным путём. Когда окно diff просило Git подготовить файл к сравнению, фильтр мог выполниться с правами разработчика.[1], [3]

Ключевые понятия и термины

Фильтр Git clean
Настроенное преобразование, которое Git может выполнить, чтобы привести файл из рабочего дерева к виду, хранящемуся в репозитории.[1], [3]
.gitattributes
Отслеживаемый в репозитории файл, назначающий именованный фильтр выбранным путям.[1]
.git/config
Локальная конфигурация репозитория, в которой задаётся команда именованного фильтра. Обычное клонирование не копирует этот локальный файл.[1], [3]
Окно diff
Функция, которая просит Git подготовить и сравнить рабочее содержимое файла с зафиксированным в репозитории.[1]

Связи со стандартами

  • Ссылка CWE для слабого места, описанного в этом руководстве: CWE-78

Анализ первопричины

Reasonix усиливал вызовы git diff параметрами --no-ext-diff и --no-textconv , но они управляют внешним diff и преобразованием текста, а не отключают контентные фильтры clean в Git. Окно изменений рабочего пространства вызывало git diff для выбранного пути. Если содержимое репозитория назначало этому пути фильтр через .gitattributes , а локальная конфигурация Git задавала команду драйвера, Git мог запустить её при подготовке сравниваемого blob. CVE описывает локального атакующего и требует, чтобы пользователь открыл diff. В публикации GitLab сказано, что обычное клонирование не переносит .git/config ; этот файл могут содержать архивы, синхронизированные каталоги, кэши или заранее собранные devcontainer-образы. Там же описан вариант, когда локальный агент с теми же правами записывает конфигурацию в репозиторий. Последствие — выполнение команд с правами пользователя Reasonix, а не удалённое выполнение кода при простом открытии репозитория на GitLab.[1], [3]

Пошаговый сценарий атаки

Шаг 1

Рабочая область получает недоверенное состояние репозитория

Разработчик открывает проект, где отслеживаемые атрибуты назначают файлу фильтр, а локальная конфигурация Git определяет этот фильтр. В уведомлении сопровождающего требуются оба элемента; одного обычного клонирования недостаточно для получения локальной конфигурации.[1], [3]

Шаг 2

Разработчик открывает diff файла

Окно изменений Reasonix запускает Git diff для выбранного файла. Это действие в интерфейсе и есть требуемое 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 и глобальными/системными файлами атрибутов, и задавать discoveryComplete только после успешного полного поиска. Эта функция проверяет переданные имена и формирует переопределения конфигурации в командной строке; она не обнаруживает и не проверяет набор драйверов. Уязвимый вызов приведён в раскрытии 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"
)

// Вызывающая сторона должна найти все фильтры, выбранные действующими атрибутами, и передать полный набор.
// Этот вспомогательный код лишь отключает эти ключи driver во всех областях конфигурации Git.
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
}

Чек-лист по защите системы для инженеров

Источники