fix(deps): update dependency gitpython to v3.1.52 [security]#335
Open
renovate[bot] wants to merge 1 commit into
Open
fix(deps): update dependency gitpython to v3.1.52 [security]#335renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
==3.1.50→==3.1.52GitPython unsafe clone option gate bypass through joined short options
GHSA-v396-v7q4-x2qj
More information
Details
GitPythonversion3.1.50blocks unsafegit cloneoptions such as--upload-pack,-u,--config, and-cunless callers explicitly passallow_unsafe_options=True. However, the default unsafe-option gate does not recognize joined short-option forms such as-u/path/to/helper.Git itself accepts
-u<upload-pack>as the short form of--upload-pack=<upload-pack>. As a result,Repo.clone_from(..., multi_options=["-u<helper>"], allow_unsafe_options=False)can execute the helper command even though the equivalent long option is blocked.Affected package:
GitPython3.1.50gitpython-developers/GitPython3.1.50Relevant behavior:
Repo.unsafe_git_clone_optionscorrectly lists--upload-pack,-u,--config, and-cas unsafe clone options.Repo._clone()splitsmulti_optionswithshlex.split(" ".join(multi_options))and then callsGit.check_unsafe_options(...)._canonicalize_option_name("-u/path/to/helper")returns a string beginning withu..., not the canonical short optionu, so it does not match the blocked-uentry.--upload-pack=<helper>and executes the helper during clone.Preconditions:
An application must pass attacker-influenced clone options into
Repo.clone_from(..., multi_options=...)while relying on GitPython's default unsafe-option gate to block command-executing options.The local PoC uses only a local bare Git repository and a local helper script. It does not contact any third-party service.
Local reproduction:
The PoC creates a disposable bare Git repository, a helper script, and a sentinel file path. It first confirms that the long
--upload-pack=<helper>form is blocked by GitPython. It then callsRepo.clone_from(..., multi_options=["-u<helper>"], allow_unsafe_options=False).Observed sanitized output:
The clone fails because the helper exits nonzero, but the sentinel file proves that Git executed the helper despite
allow_unsafe_options=False.Impact:
An attacker who controls
multi_optionscan bypass GitPython's defaultallow_unsafe_options=Falseprotection and execute a local command via Git's--upload-pack/-uclone option. This is a residual bypass of an explicit GitPython security boundary, not merely a case where a caller opted into unsafe behavior.Duplicate / related advisory checks:
PyPI/GitPythonversion3.1.50returned no vulnerabilities.GHSA-x2qx-6953-8485/CVE-2026-42284andGHSA-rpm5-65cw-6hj4/CVE-2026-42215. Their public affected ranges are marked as fixed before 3.1.50.GHSA-x2qx-6953-8485describes validatingmulti_optionsbeforeshlex.split(...). GitPython 3.1.50 now validates after splitting, but the joined short option-u<value>still bypasses because the validator canonicalizes it tou<value>rather thanu.GHSA-rpm5-65cw-6hj4describes unsafe underscored kwargs such asupload_pack=.... The current PoC usesmulti_options=["-u<helper>"]against 3.1.50 and does not depend on underscored kwargs.upload-pack unsafe optionsfound historical related items, including CVE-2022-24439 and the earlier unsafe-options gate work, but no public issue describing this current joined-short-option residual bypass in 3.1.50.multi_options unsafefound PR #2130, which fixed splitting ofmulti_optionsbefore checking. The current issue remains after that split because-u<value>is treated as option nameu<value>, not blocked short optionu.u<upload-pack> unsafeand-cfooreturned no results.Suggested remediation:
When checking unsafe Git options, parse joined short options that take values. For clone,
-uVALUEand-cKEY=VALUEshould be canonicalized touandcrespectively before comparing against the unsafe option set.A safer approach is to maintain command-specific metadata for unsafe short options and recognize the bare option, split form, joined form, and long
--option=<value>/--option <value>forms.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist
GHSA-2f96-g7mh-g2hx
More information
Details
Command injection via long-option prefix abbreviation bypassing
check_unsafe_options(incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4)Component: gitpython-developers/GitPython (PyPI: GitPython)
Affected: all versions carrying the 3.1.47 blocklist fix, through current
main(verified at commit20c5e275,3.1.50-42)CWE: CWE-184 (Incomplete List of Disallowed Inputs) → CWE-78 (OS Command Injection)
Severity: inherits the parent CVE-2026-42215 surface; estimated High, ~8.8 (
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) — final scoring deferred to maintainer/CNA, mirroring the parent.Reporter: hackkim
Summary
The 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (
--upload-pack,--config,-c,-ufor clone;--upload-packfor fetch/pull;--receive-pack,--execfor push) so callers cannot reach command-executing options unless they passallow_unsafe_options=True.The fix canonicalizes an option name along one axis (underscore→hyphen via
dashify) and checks it against an exact-match dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (--upload-p,--upload-pa,--upload-pacall resolve to--upload-pack). So a kwarg key likeupload_pcanonicalizes toupload-p, misses the blocklist dict, and is emitted to git as--upload-p=<value>→ executed as--upload-pack=<value>→ command injection, in the defaultallow_unsafe_options=Falseconfiguration.The asymmetry (root cause)
The guard normalizes only
_→-and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option.Affected code (commit
20c5e275)git/cmd.py:948-960_canonicalize_option_namegit/cmd.py:963-974check_unsafe_optionsgit/cmd.py:1511transform_kwarg--<dashify(name)>=<value>to the CLIgit/repo/base.py:1411,1413git/remote.py:1074,1128,1201Bypass keys (verified)
upload_p,upload_pac--upload-packreceive_p--receive-packexe--execconf,confi--configMinimal PoC
Self-contained, no network egress (a local bare repo acts as the "remote"). Tested on current
main(git 2.50.1):Equivalent at the shell:
git clone --upload-p=/tmp/evil.sh src outrunsevil.sh.Confirmed behavior:
upload_pack(exact) → blocked;upload_p(abbrev) → passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form.allow_unsafe_options=Trueopt-out behaves as documented (out of scope).Honest scope note
Like the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg keys into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable — the vulnerability is in the library's documented defense-in-depth control (
allow_unsafe_options=False), which this variant defeats.On the
--configfamily:confbypasses the option blocklist, but weaponizing--config protocol.ext.allow=alwaysvia anext::URL is independently blocked by GitPython's protocol allowlist (allow_unsafe_protocols=False). The directly weaponizable family isupload-pack/receive-pack/exec. Reported transparently — not claiming Critical.Suggested remediation (any one)
startswithon the blocked canonical name, afterdashify).--end-of-optionsor invoke git in a way that disables long-option abbreviation.Remediation should also cover the
-c/--configfamily abbreviations, even though theext::route is currently gated by the protocol allowlist.Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: command injection via unguarded Git options in
Repo.archive(),git.ls_remote(), and arbitrary file overwrite viaRepo.iter_commits()/Repo.blame()GHSA-956x-8gvw-wg5v
More information
Details
Summary
GitPython spawns the real
gitbinary with an argument vector built from caller-supplied values. To prevent argument injection, GitPython maintains denylists of "unsafe" Git options (--upload-pack,--receive-pack,--exec,-c,--config, …) that can be abused to run arbitrary commands, and enforces them withGit.check_unsafe_options().That enforcement is only wired into the network commands —
clone_from,Remote.fetch,Remote.pull,Remote.push. Several other public APIs that also forward caller-controlled values into thegitargv have no guard at all:Repo.archive(ostream, treeish=None, prefix=None, **kwargs)forwards**kwargsverbatim intogit archive. An attacker-influenced options mapping such as{"remote": ".", "exec": "<cmd>"}becomesgit archive --remote=. --exec=<cmd> -- <treeish>, andgit archive --remote=<local repo>invokesgit-upload-archivewhose path is overridden by--exec→ arbitrary command execution under default Git configuration (noprotocol.ext.allowneeded).repo.git.ls_remote(<url>, upload_pack="<cmd>")(and the dynamic-command builder generally) turns theupload_packkwarg into--upload-pack=<cmd>with no guard → arbitrary command execution.Repo.iter_commits(rev)andRepo.blame(rev, file)place the caller'srevvalue into the argv before the--end-of-options separator and apply no leading-dash check. A benign-looking ref value such as--output=/path/to/fileis parsed bygit rev-list/git blameas the--outputoption, which opens and truncates an arbitrary file before Git even validates the revision → arbitrary file clobber (integrity/availability; can destroy keys, configs, lockfiles, or be aimed at files the host later sources).The first two are direct code execution; the third is an arbitrary file-overwrite primitive. All share one root cause: the
check_unsafe_options/ end-of-options discipline that GitPython applies to clone/fetch/pull/push was never extended to these sinks.Details
GitPython explicitly recognises these options as command-execution vectors.
git/remote.py:535:and enforces them via
Git.check_unsafe_options()(git/cmd.py:963):But
check_unsafe_optionsis invoked from only five sites, all network commands:The following sinks call
gitwith caller-controlled options/positionals and are not guarded:1.
Repo.archive— command execution (git/repo/base.py:1623)treeishandpathare correctly placed after--, but**kwargsare converted byGit.transform_kwarg(git/cmd.py:1487) into--<name>=<value>flags and inserted before the--by_call_process, with nocheck_unsafe_options.Repo.archivealready documents user-facing kwargs (format,prefix,path), so forwarding a caller options mapping is an expected usage. Final argv:git archive --remote=<repo>runs the upload-archive helper;--exec=<cmd>overrides the helper path, executing<cmd>on the host. This works with default Git config — it does not rely on theext::transport (which is blocked by default).2.
repo.git.ls_remote(..., upload_pack=...)— command execution (dynamic builder,git/cmd.py:1487)transform_kwargdashifiesupload_pack→--upload-pack=<value>.git ls-remote <local-repo> --upload-pack=<cmd>executes<cmd>. The dynamic builder makes both the flag name and value caller-controlled (repo.git.<anything>(**user_dict)), andls_remotehas nocheck_unsafe_options.This is exactly the underscore-kwarg-vs-hyphen-kwarg gap that CVE-2026-42215 fixed for
fetch/pull/push/clone_from— butls_remoteand the rest of the dynamic surface were left unpatched.3.
Repo.iter_commits/Repo.blame— arbitrary file overwrite (git/objects/commit.py:348,git/repo/base.py:1199)revis placed before--, with no leading-dash check anywhere in the path. A caller passingrev="--output=/path"(a value that looks like an ordinary ref/branch/tag string an app forwards from user input) produces:git rev-list/log/blamehonour--output=<file>, whichopen()s and truncates the file before validating the revision — so the file is destroyed even though Git then errors out on the bad revision.PoC
All three PoCs are self-contained, run against the released GitPython 3.1.50 under default Git configuration, and were executed live (git 2.51.0). Each prints a host-side marker proving the effect.
Install
PoC 1 — command execution via
Repo.archiveVerbatim output:
git config --get protocol.ext.allowreturns nothing (unset = default), confirming no special config is required.PoC 2 — command execution via
git.ls_remote(upload_pack=...)Verbatim output:
PoC 3 — arbitrary file overwrite via a benign-looking
revVerbatim output:
Severity
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL
GHSA-rwj8-pgh3-r573
More information
Details
Summary
Repo.clone_from()passes the caller-supplied remote URL throughGit.polish_url(), which on every non-Cygwin platform callsos.path.expandvars()on the URL before handing it togit clone. An attacker who controls the URL argument — the documented use case forclone_from()in "import repository from URL" features of CI servers, git-hosting mirrors, and dependency scanners — can embed$NAME/${NAME}tokens that are expanded server-side to the values of the hosting process's environment variables. The resulting URL, now containing the secret, is transmitted over the network to the attacker-named host. This crosses the trust boundary between an untrusted remote URL and the server's process environment, disclosing secrets such asAWS_SECRET_ACCESS_KEYorGITHUB_TOKENwith no precondition beyond the ability to submit a clone URL.Details
Affected versions:
gitpython(PyPI) — all releases up to and including3.1.50(latest at time of reporting); confirmed present on themainbranch.Git.polish_url()unconditionally applies environment-variable expansion to its input on the non-Cygwin branch:git/cmd.py(v3.1.50), lines 907–925:Repo._clone()— reached from the publicRepo.clone_from()(git/repo/base.py:1520) andRepo.clone()— runs the unsafe-protocol check on the raw URL and then passes the polished (post-expansion) URL to thegit clonesubprocess:git/repo/base.py(v3.1.50), lines 1407–1418:Because
os.path.expandvars()on POSIX substitutes$NAMEand${NAME}withos.environ[NAME]when set (and on Windows additionally%NAME%), an attacker-supplied URL such as:is rewritten server-side to embed the literal secret value in the path component, and
git clonethen issues an HTTP(S) request (and DNS lookup, if the token is placed in the host label) carrying that value toattacker.example. The clone itself will typically fail, but the secret has already left the server by that point.polish_url()was written as a local-path normalisation helper (Cygwin path conversion,~expansion, backslash fixing) and is applied indiscriminately to remote URLs. There is no scheme check, noexpand_vars=Falseopt-out for the clone URL, and no documentation that the URL undergoes environment expansion — theclone_fromdocstring describesurlonly as a "Valid git url". By contrast, the maintainers already flag env-var expansion as a security concern for the local repository path argument:Repo.__init__emits a deprecation warning ("The use of environment variables in paths is deprecated for security reasons",git/repo/base.py:226–231) and offersexpand_vars=False. The same treatment is missing for the network-bound clone URL.Secondary consequence (unsafe-protocol filter bypass). Because
check_unsafe_protocols()runs on the pre-expansion URL (line 1408) but the post-expansion URL is what reachesgit, an attacker who additionally controls any environment variable in the server process could set e.g.X=ext::sh -c '...'and submiturl="$X"; the raw string$Xpasses theext::filter, then expands to anext::remote-helper transport thatgitwill execute. This requires a second precondition (env-var write) and is noted as an aggravating factor rather than a separate vulnerability.PoC
Tested against
gitpython==3.1.50on Linux with Python 3 andgitonPATH.poc.py:Expected output:
The captured argv is the exact command line spawned by GitPython; against a real attacker-controlled host,
gitwould issue a DNS lookup and HTTP(S) request to that host with the secret embedded in the request path.Impact
Any application that calls
Repo.clone_from()(orRepo.clone()) with a URL that is wholly or partially attacker-controlled — the canonical pattern for "import/mirror repository from URL" features in CI systems, source-code hosting platforms, dependency scanners, and build pipelines — allows an unauthenticated or low-privileged attacker to exfiltrate arbitrary environment variables from the server process, one per request, by naming them in the URL. Cloud credentials, API tokens, and signing keys stored in the environment are the primary targets. Applications that do not accept clone URLs from untrusted sources, or that run the cloner in a process with a fully stripped environment, are not affected. There is no direct integrity or availability impact.Suggested fix: Remove the
os.path.expandvars()(andos.path.expanduser()) call fromGit.polish_url()for inputs that are remote URLs (contain://or matchuser@host:path), or remove the expansion entirely and require callers who want local-path env expansion to perform it themselves — mirroring the existing deprecation onRepo(path, expand_vars=…). Additionally, applycheck_unsafe_protocols()to the post-transformation URL so no futurepolish_urlchange can silently bypass theext::filter.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
gitpython-developers/GitPython (gitpython)
v3.1.52: SecurityCompare Source
GHSA-rwj8-pgh3-r573: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL
What's Changed
Full Changelog: gitpython-developers/GitPython@3.1.51...3.1.52
v3.1.51: - SecurityCompare Source
What's Changed
335c0f6to0a019a2by @dependabot[bot] in #21490a019a2to4950ea9by @dependabot[bot] in #2165New Contributors
Full Changelog: gitpython-developers/GitPython@3.1.50...3.1.51
Configuration
📅 Schedule: (in timezone Europe/Berlin)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.