GitPython: OS Command Injection via Single-Character Kwarg Value Token Smuggling
GitPython's safety guard against dangerous git options can be completely bypassed by hiding a dangerous flag inside the value of a single-character keyword argument, letting attackers run arbitrary…

The problem
GitPython's check_unsafe_options guard inspects only kwarg KEYS when building its candidate option list. Passing a single-character kwarg (e.g. n=) causes _option_candidates to produce ['-n'], which is not on the denylist, so the guard passes silently.
However, transform_kwarg('n', '--upload-pack=cmd', split_single_char_options=True) emits TWO argv tokens: ['-n', '--upload-pack=cmd']. Git parses the second token normally and executes the attacker-supplied command. Every guarded method is affected: clone_from, fetch, pull, push, ls_remote, iter_commits, blame, and archive.
Proof of concept
A working proof-of-concept for this issue in GitPython, with the exact payload below.
from git import Repo
# Guard passes because _option_candidates sees only '-n' (the key), not the value.
# transform_kwarg then emits ['-n', '--upload-pack=touch /tmp/ACE;git-upload-pack'].
# Git executes the second token -> arbitrary command execution.
Repo.clone_from(
"file:///path/to/bare.git",
"/tmp/out",
n="--upload-pack=touch /tmp/ACE;git-upload-pack"
)
# /tmp/ACE is created. Confirmed live on GitPython 3.1.53.
# File-write variant using iter_commits:
# repo.iter_commits('HEAD', g='--output=/etc/cron.d/evil')
# Candidate: ['-g'] -> not on denylist -> passes guard.
# Argv: ['-g', '--output=/etc/cron.d/evil'] -> victim file truncated/written.The root cause is a scope mismatch between the guard and the argument transformer. _option_candidates([], kwargs) derives each candidate from the kwarg KEY only (e.g. 'n' -> '-n'), never from the value. The guard therefore never sees --upload-pack embedded in the value string.
When transform_kwarg runs afterwards with split_single_char_options=True, it detects that the value starts with -- and splits it into a separate argv token. That second token reaches git's argument parser unchecked. The patch (commit e8d0fbf, PR #2180, released in 3.1.54) fixes _option_candidates to also emit candidates derived from the values of single-character kwargs when the split behaviour is active, so check_unsafe_options evaluates the smuggled option before it ever reaches git.
CWE-88 (Argument Injection) and CWE-78 (OS Command Injection) both apply.
The fix
Upgrade GitPython to 3.1.54 or later (commit e8d0fbf, PR #2180). The patch makes _option_candidates extract and check candidates from single-character kwarg VALUES in addition to keys, closing the scope gap before transform_kwarg runs. If an immediate upgrade is not possible, avoid passing user-controlled kwargs to any GitPython method without an explicit allowlist of safe key-value pairs.
Reported by zx (Jace).
Related research
- high · 8.1GitPython Argument Injection via Diffable.diff Enables Arbitrary File Overwrite
- high · 7.5GitPython: OS Command Injection via --template in clone_from
- high · 8.4GitPython Command Injection and Arbitrary File Overwrite via Unguarded Git Options
- highGitPython unsafe clone option gate bypass via joined short options