FlawOpen

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

● CVE-2026-102437 · CVSS 7.8[2] · High
FlawOpen Security Research

CVE-2026-102437: Reasonix diff-view Git injection

A poisoned .gitattributes/.git/config pair can run a clean filter when Reasonix renders a diff. Update npm to 1.39.3 or Studio to 2.21.0.[1], [2], [3], [6], [7]

💡 Plain English Explainer (ELI5)

Think of a package-sorting desk that promises only to compare two labels. A parcel can carry a routing note that tells the desk to run a registered helper before it compares the contents. Reasonix disabled some Git helpers, but Git's per-file clean filter was a separate route. When the diff screen asked Git to prepare a file for comparison, that filter could run under the developer's account.[1], [3]

Core Concepts & Subsystem Terms

Git clean filter
A configured transformation Git may run to convert a file's working-tree contents into the form stored in the repository.[1], [3]
.gitattributes
A repository-tracked file that can assign a named filter to matching paths.[1]
.git/config
Repository-local Git configuration, which can define what command a named filter invokes. A normal clone does not copy this local file.[1], [3]
Diff viewer
A feature that asks Git to prepare and compare a file's working-tree and committed contents.[1]

Framework mappings

  • CWE reference for the weakness described in this guide: CWE-78

Root Cause Analysis

Reasonix hardened Git diff calls with --no-ext-diff and --no-textconv , but those flags govern external diff and text-conversion mechanisms; they do not disable Git's clean content filters. The workspace changes view called git diff for a selected path. If repository content assigned that path a filter through .gitattributes and local Git configuration supplied the driver's command, Git could invoke it while constructing the comparison blob. The CVE describes a local attacker and requires user interaction to view the diff. GitLab's disclosure says ordinary cloning does not transfer .git/config ; archives, synced directories, caches, or prepared devcontainers can carry it. It also describes a same-user local-agent route that can write local repository config. The impact is command execution with the Reasonix user's privileges, not remote code execution merely from opening a GitLab-hosted repository.[1], [3]

Step-by-Step Attack Flow

Step 1

A workspace receives untrusted repository state

A developer opens a project whose tracked attributes assign a file to a filter and whose local Git configuration defines that filter. The maintainer advisory requires both pieces; a normal clone alone does not provide the local configuration.[1], [3]

Step 2

The developer requests a file diff

The Reasonix workspace changes view runs its Git diff path for the selected file. This user-interface action is the interaction required by the CVE.[1], [3]

Step 3

Git applies the clean-filter path

Git prepares content for comparison and may invoke the configured clean filter. --no-ext-diff and --no-textconv disable different mechanisms and do not suppress this filter.[1], [3]

Step 4

The filter runs with the desktop user's authority

The configured filter command executes in the Reasonix process context. The documented consequence is local command execution with that user's privileges.[1], [2], [3]

Source Code: Flaw vs. Secure Implementation

This is an illustrative argument builder, not a drop-in patch. The caller must enumerate every driver selected by effective .gitattributes, .git/info/attributes, and global/system attribute files, setting discoveryComplete only after a successful complete scan. This function validates the supplied names and emits command-line overrides; it does not discover or verify the driver set. The vulnerable call is quoted from GitLab’s disclosure; the defensive pattern is an original synthesis of maintainer guidance and the merged hardening PRs.[1], [3], [4], [5]

Source excerpt: desktop/workspace_changes.go

UNPATCHED FLAW
// Source call quoted by GitLab Threat Research; this is the call site, not a complete function.
args := []string{"-C", base, "diff", "--no-ext-diff", "--no-textconv", "--relative", "HEAD", "--", filepath.FromSlash(rel)}
HARDENED SECURE PATCH
// Illustrative Go argument builder, not a verbatim vendor patch.
package gitguard

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

// The caller must discover every filter selected by effective attributes and pass the complete set.
// This helper only neutralizes those driver keys across all Git config scopes.
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 subsection names are case-sensitive: do not fold driver names.
        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
}

Engineering & System Hardening Checklist

Sources