From 863417457a0633db7ea5aed4fd01e0b291a41162 Mon Sep 17 00:00:00 2001 From: "GPT 5.6" Date: Wed, 22 Jul 2026 15:42:44 +0200 Subject: [PATCH 1/2] fix: prevent environment expansion in remote URLs Remote and submodule creation expanded environment-variable references in caller-supplied URLs before storing them in Git configuration. This could expose process environment values through a later network request or a tracked .gitmodules file. Preserve variables literally in the remaining untrusted URL callers while retaining Git.polish_url() expansion for intentional local-path normalization. Add regression tests covering remote configuration and submodule configuration. Git baseline: git remote add and git submodule add accept URL arguments literally; Git does not perform shell-style environment expansion on those arguments. Co-authored-by: Sebastian Thiel --- git/objects/submodule/base.py | 2 +- git/remote.py | 2 +- test/test_remote.py | 9 +++++++++ test/test_submodule.py | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 7984db340..97fc9e111 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -608,7 +608,7 @@ def add( # END verify url ## See #525 for ensuring git URLs in config-files are valid under Windows. - url = Git.polish_url(url) + url = Git.polish_url(url, expand_vars=False) # It's important to add the URL to the parent config, to let `git submodule` know. # Otherwise there is a '-' character in front of the submodule listing: diff --git a/git/remote.py b/git/remote.py index 0c3dbfe15..e2d5cbc1d 100644 --- a/git/remote.py +++ b/git/remote.py @@ -808,7 +808,7 @@ def create(cls, repo: "Repo", name: str, url: str, allow_unsafe_protocols: bool """ scmd = "add" kwargs["insert_kwargs_after"] = scmd - url = Git.polish_url(url) + url = Git.polish_url(url, expand_vars=False) if not allow_unsafe_protocols: Git.check_unsafe_protocols(url) repo.git.remote(scmd, "--", name, url, **kwargs) diff --git a/test/test_remote.py b/test/test_remote.py index 64c41290e..505d283af 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -4,6 +4,7 @@ # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ import gc +import os import os.path as osp from pathlib import Path import random @@ -685,6 +686,14 @@ def test_multiple_urls(self, rw_repo): # Will raise fatal: Will not delete all non-push URLs. self.assertRaises(GitCommandError, remote.delete_url, test3) + @with_rw_repo("HEAD", bare=False) + def test_create_does_not_expand_environment_variables_in_url(self, rw_repo): + url = "https://example.com/${GITPYTHON_TEST_SECRET}/repo.git" + with mock.patch.dict(os.environ, {"GITPYTHON_TEST_SECRET": "sensitive-value"}): + remote = rw_repo.create_remote("untrusted", url) + + assert remote.url == url + def test_fetch_error(self): rem = self.rorepo.remote("origin") msg = ( diff --git a/test/test_submodule.py b/test/test_submodule.py index 265ee2ffb..9879759dd 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -1355,6 +1355,20 @@ def test_add_no_clone_multi_options_argument(self, rwdir): with self.assertRaises(cp.NoOptionError): sm_config.get_value("core", "eol") + @with_rw_directory + def test_add_does_not_expand_environment_variables_in_url(self, rwdir): + parent = git.Repo.init(osp.join(rwdir, "parent")) + url = osp.join(rwdir, "${GITPYTHON_TEST_SECRET}") + source = git.Repo.init(url) + Path(source.working_tree_dir, "file").write_text("content") + source.index.add(["file"]) + source.index.commit("initial") + + with mock.patch.dict(os.environ, {"GITPYTHON_TEST_SECRET": "sensitive-value"}): + sm = Submodule.add(parent, "new", "new", url) + + assert sm.url == Git.polish_url(url, expand_vars=False) + @with_rw_repo("HEAD") def test_submodule_add_unsafe_url(self, rw_repo): with tempfile.TemporaryDirectory() as tdir: From 681c82c9c296f934635c81fa8294d4b6b6791b7e Mon Sep 17 00:00:00 2001 From: Byron Date: Thu, 23 Jul 2026 04:51:07 +0200 Subject: [PATCH 2/2] prepare release --- VERSION | 2 +- doc/source/changes.rst | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 8fc4b1976..5b1840f1e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.54 +3.1.55 diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 84d7b34a7..a27ff48a2 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,6 +2,19 @@ Changelog ========= +3.1.55 +====== + +A security fix for + +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-94p4-4cq8-9g67 + +If you can, also try and provide feedback on the upcoming v4 branch +https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome. + +See the following for all changes. +https://github.com/gitpython-developers/GitPython/releases/tag/3.1.55 + 3.1.54 ====== @@ -9,7 +22,7 @@ A security fix for * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-r9mr-m37c-5fr3 * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-6p8h-3wgx-97gf -* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-fjr4-x663-mwxc#event-857190 +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-fjr4-x663-mwxc If you can, also try and provide feedback on the upcoming v4 branch https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.