# GitPython unsafe clone option gate bypass through joined short options

GitPython 3.1.50's unsafe-option gate misses joined short form -u<helper>, letting attacker-controlled multi_options execute a command via git clone's upload-pack.

- Payout: Research
- Severity: high
- Program: GitPython
- Found by: Faze-up
- Disclosed: 2026-07-21
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-v396-v7q4-x2qj
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-v396-v7q4-x2qj
- 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:misconfiguration

**Target:** Repo.clone_from multi_options unsafe-option validator
**Root cause:** _canonicalize_option_name('-u/path/to/helper') yields 'u/path/to/helper' rather than the canonical short option 'u', so it fails to match the blocked '-u' entry even though git treats it as --upload-pack=<helper>.
**Steps:**
1. Create a local bare git repo and a helper script that writes a sentinel file.
2. Confirm `Repo.clone_from(..., multi_options=['--upload-pack=<helper>'], allow_unsafe_options=False)` raises UnsafeOptionError.
3. Call `Repo.clone_from(..., multi_options=['-u<helper>'], allow_unsafe_options=False)`.
4. Observe gate returns ALLOWED, clone raises GitCommandError from helper nonzero exit, but sentinel file exists proving helper executed.
**How found:** Noted the gate validates after shlex.split (per PR #2130) but canonicalization of joined short options like -uVALUE produces 'uVALUE' instead of 'u'; tested the joined form against the blocked long form.
**Exploitation:** Attacker who controls multi_options passed to Repo.clone_from executes an arbitrary local helper command via git's -u<upload-pack> joined short option, despite allow_unsafe_options=False.
**Insight:** When a denylist canonicalizes option names, test every syntactic form git accepts for the same option: bare, split, joined short (-uVALUE, -cKEY=VALUE), and long --opt=value / --opt value. Joined short options are the classic gap.

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

**clone_from call with joined -u helper bypass**

```
Repo.clone_from(..., multi_options=["-u<helper>"], allow_unsafe_options=False)
```

