Open actionable advisories and recorded vendor fixes. This public package view has no inventory attached and cannot establish whether your deployment is affected. A vendor fix never resolves your finding; verify your installed version and the source guidance.
Open advisories
44 actionable advisories, newest first. Withdrawn advisories and news are excluded. Coverage is partial.
> A note on the referenced files. This report was submitted through GitHub's private > vulnerability reporting API, which carries no attachments. Every transcript this report leans > on is reproduced inline above, verbatim. The file names it mentions (`poc.sh`, `poc.log`, > per-case transcripts, `evidence.md`, `negative-controls.md`, blob-sweep tables) are the local > evidence bundle those inline excerpts were extracted from, with a checksum manifest. I am happy > to send the bundle by any channel you prefer, or to open a private fork — just say which. The > PoC scripts are self-contained and reproduce the result from a clean environment. --- Affected: ` "The audit also found that reset's positional commit could carry the option before its argument > separator. […] Check reset's positional commit as well as keyword options" `HEAD.reset` got the fix. `Head.checkout` did not, and `1b0d2d9b` did not come back for it. Affected Asset | | | |---|---| | Package |…
Recommended action: Upgrade to 3.2.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
GitPython's public `Submodule.move()` API verifies that its destination is lexically inside the parent repository, but it does not reject symbolic links in the destination's intermediate components before filesystem operations. On systems that check out Git symlinks, an untrusted repository can therefore redirect a fixed, application-selected move destination outside the parent worktree. The reporter reproduced this on the latest release, GitPython 3.1.62, and current `main`. In the strict scenario, the repository commits `vendor -> ../outside`, while the consuming application independently passes the fixed literal `vendor/module`. `Submodule.move()` follows `vendor`, moves the initialized submodule checkout to `outside/module`, rewrites `.gitmodules` and the index to the symlinked path, and leaves the index unable to produce a tree. The same result occurs when the committed symlink contains an absolute path to an external directory. No attacker-controlled API argument,…
Recommended action: Upgrade to 3.2.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
`Remote.pull()` in GitPython forwards its positional `refspec` argument directly to the `git pull` child process while its unsafe-option guard inspects only keyword arguments. An application that passes attacker-influenced data as the `refspec` (for example a user-supplied branch or refspec parameter in a web or CI service) can supply an option-shaped value such as `--upload-pack= `. The value bypasses the `unsafe_git_pull_options` check, is placed after the `--` separator on the `git pull` command line, and is executed as the upload-pack program for local-path or SSH transports. This yields arbitrary command execution with the privileges of the calling process using the documented default arguments (`allow_unsafe_options=False`). Root Cause `Remote.pull()` is documented to reject dangerous options unless `allow_unsafe_options=True`, and `--upload-pack` is explicitly listed as such an option. The guard, however, only screens keyword arguments and never the positional…
Recommended action: Upgrade to 3.2.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
GitPython has spent 2022-2026 rolling out two reusable guards method-by-method: `Git.check_unsafe_protocols(url)` (raises `UnsafeProtocolError` for ` ::` remote-helper URLs - CVE-2022-24439) and `Git.check_unsafe_options(...)` (raises `UnsafeOptionError` for denylisted option spellings - CVE-2023-40267, CVE-2026-42215, CVE-2026-67323). Three wrapper methods that build a `git` argv from caller-controlled values still have neither: 1. `Git.ls_remote` (git/cmd.py:1128-1143) - checks options (:1139-1141) but forwards the URL positional unchecked; no `allow_unsafe_protocols` parameter exists. The same URL through `clone_from` raises `UnsafeProtocolError`. 2. `Repo.merge_base` (git/repo/base.py:895-936) - forwards `kwargs`/`*rev` to `self.git.merge_base` with no guard; guarded sibling `iter_commits` sits 12 lines up. Its exception handler additionally swallows every non-128 `GitCommandError` into `[]`, masking the injection. 3. `IndexFile.move`**…
Recommended action: Upgrade to 3.2.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
`Repo._clone()`'s handling of `separate_git_dir` never disables `Git.polish_url()`'s environment-variable expansion, unlike every other caller of that helper that handles attacker-influenced data. Since `separate_git_dir` can be derived from an attacker-controlled `.gitmodules` submodule name, a completely ordinary `clone` + `submodule_update(init=True, recursive=True)` on a malicious repository expands an environment variable of the attacker's choosing and exposes its value both as a persistent on-disk directory name and, whenever DEBUG-level logging is enabled (a common CI/verbose-logging configuration), in a `Popen(...)` log line that is not redacted the way credential-bearing arguments are. Root cause / Details `git/repo/base.py`'s `_clone()` has: ```python sep_dir = kwargs.get("separate_git_dir") if sep_dir: kwargs["separate_git_dir"] = Git.polish_url(sep_dir) ``` This is the only remaining `Git.polish_url()` call site handling attacker-influenced data that does…
Recommended action: Upgrade to 3.2.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
This is a residual of GHSA-g5vv-9gxw-82hx / CVE-2026-87819. The 3.1.60 fix made `Actor._from_string` regex-free, but `parse_actor_and_date` still runs `_re_actor_epoch` on the same author/committer/tagger line one step earlier. That pattern backtracks quadratically when the trailing epoch or timezone is missing or malformed, so the same class of slow parse is still reachable in 3.1.62. Details `git/objects/util.py:322`: ```python _re_actor_epoch = re.compile(r"^.+? (.*) (\d+) ([+-]\d+).*$") ``` `.+?` and `(.*)` can both absorb the same run of spaces, and the required ` (\d+) ([+-]\d+)` tail is only checked after them. On a long line with no valid tail, the search retries every split point. It is called from `parse_actor_and_date` (`git/objects/util.py:335`), which runs on the author and committer lines in `Commit._deserialize` (`git/objects/commit.py:917`, `:931`) and on the tagger line in `git/objects/tag.py:129`. The published payload (letters plus an unterminated `…
Recommended action: Upgrade to 3.2.0 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
createsubmodule(name, path, url) clones a submodule into the repo's own .git directory before it rejects the path. A path of ".git/hooks" is accepted by the pre-clone check, so the submodule is cloned straight into the live hooks directory, and a file named post-checkout in the submodule becomes an executable .git/hooks/post-checkout. On 3.2.0+ the later index check does raise ValueError, but only after the clone already wrote to disk, and it does not remove the planted hook; on 3.1.x there is no such check and the call succeeds silently. Either way the hook runs on the next git operation (e.g. checkout), giving code execution. Details The checkout path is validated too late, after the clone has already mutated disk. This breaks the "validate before mutation" invariant that commit 43a43cd8 ("Validate submodule checkout and metadata paths before mutation") set out to enforce. Submodule.add() calls the permissive path check before cloning, and the strict check only reaches the…
Recommended action: Upgrade to 3.2.1 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Affected: GitPython 3.1.61 (latest release) and main — git/objects/submodule/base.py. git diff 3.1.61 origin/main -- git/objects/submodule/ is empty, so both are identical here. --- The gap The fix for GHSA-hmq2-w58f-27jc added Submodule.validatedname() and wired it into update() and five siblings, closing the .gitmodules name → .git/modules/<name traversal. The other attacker-controlled .gitmodules field, path, is read raw: and GitPython's own containment guard is applied in only two of the places that consume it: update() validates only the name and then uses the path-derived absolute location directly: So path = ../../../tmp/escaped in an attacker-authored .gitmodules selects the directory that gets created and, on the clone path, populated from the submodule URL. The same absolute location is what forceremove hands to shutil.rmtree. The asymmetry is the argument: this is not a missing concept — the project wrote torelativepath() precisely for this, and add()/move() use it.…
Recommended action: Upgrade to 3.1.62 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
GitPython's Actor.nameemailregex regular expression (git/util.py, line 863) is vulnerable to catastrophic backtracking (ReDoS — Regular Expression Denial of Service). When GitPython parses the author or committer header of a git commit object that contains a long string with an unterminated < (no matching ), the Python regex engine enters quadratic backtracking, causing complete single-threaded CPU exhaustion proportional to the square of the input length. A single crafted commit object can block any GitPython API call that reads .author or .committer for over two minutes per invocation, enabling denial of service against CI runners, code-hosting backends, repository-scanning pipelines, or any service that processes commits from third-party or untrusted repositories. --- Details Vulnerable file and line: git/util.py, line 863: This regex is evaluated inside Actor.fromstring() (line 909) every time GitPython resolves a commit's .author or .committer property. Full call chain —…
Recommended action: Upgrade to 3.1.60 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
GitPython 3.1.59 blocks a previously available local-file read path through unsafe git diff options such as -O/--orderfile. However, the high-level diff API still permits --no-index with the default allowunsafeoptions=False. --no-index changes the semantics of the paths arguments: instead of repository-relative pathspecs, Git interprets them as arbitrary filesystem paths. When combined with the still-allowed -I/--ignore-matching-lines option, this creates a content-dependent Boolean oracle over a caller-selected local file. This was reproduced against the published GitPython 3.1.59 wheel. The original unsafe-option path is blocked in 3.1.59, while this alternate path remains reachable without setting allowunsafeoptions=True. Details Confirmed API surface: repo.index.diff( None, noindex=True, I=pattern, paths=[baselinepath, targetpath], createpatch=True, ) The relevant behavior is: 1. --no-index makes the two values supplied via paths filesystem operands rather than repository…
Recommended action: Upgrade to 3.1.60 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Repo.init decides which directory is the git directory by testing candidate paths in an order that considers the real .git last. Two earlier tests can be satisfied by ordinary tracked files. Git reserves only the literal name .git, so HEAD, objects/, refs/, config, gitdir, commondir and hooks/ at a repository root are all legal tracked content. Consequently, after a victim opens or clones an attacker's repository, GitPython resolves gitdir to the working-tree root while real git correctly resolves <root/.git. Everything GitPython then treats as "inside the git directory" is attacker-authored content — including hooks/, which it executes. CVE-2026-87817 Affected code (3.1.59) The discovery loop in git/repo/base.py tests, in order: 1. git/repo/base.py:299 — isfile(curpath/gitdir) and isfile(curpath/commondir) and isfile(curpath/HEAD) 2. git/repo/base.py:320 — isgitdir(curpath) 3. git/repo/base.py:341 — dotgit = osp.join(curpath, ".git") ← the real git dir, considered last…
Recommended action: Upgrade to 3.1.60 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
TagReference.create() forwards a caller-influenced positional reference value into git tag without it ever being inspected by the unsafe-option guard, allowing an arbitrary file read (the file's contents are returned in-band as the annotated tag message). This is an incomplete-fix bypass of commit 3af0c251 (the fix for GHSA-3f7w-8rr8-f37f's tag instance). Root Cause The fix 3af0c251 added unsafegittagoptions = ["--file","-F"] and a guard call, but the guard is Git.checkunsafeoptions(options=Git.optioncandidates([], kwargs), unsafeoptions=...) at git/refs/tag.py:139 — it passes an EMPTY args list and inspects kwargs only. The dangerous values path and reference are POSITIONALS (args = (path, reference), tag.py:156), placed before any --. A user-influenced reference="--file=<path" therefore reaches git tag as the exact --file option the fix intended to block, creating an annotated tag whose message is the file's contents. Impact Arbitrary local file read at the privileges of…
Recommended action: Upgrade to 3.1.59 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
- CWE: CWE-73 (External Control of File Name or Path) / CWE-22 (Path Traversal, in the "escapes intended base directory" sense) - Affected component: git/repo/base.py, Repo.unsafegitcloneoptions (class attribute, lines 153-165) and Repo.clone() (lines 1477-1520), reached via the public Repo.clonefrom() (line 1626) and Repo.clone() (line 1567) APIs. - Affected version: GitPython at HEAD (9729ed3b948f2bde09f1f188c5311e172212b67e, 2026-08-05, VERSION 3.1.58) Reachability Repo.clonefrom(url, topath, kwargs) (and Repo.clone()) forward arbitrary keyword arguments to the underlying git clone invocation. Before forwarding, GitPython builds a candidate option list from the kwargs (Git.optioncandidates) and checks it against a denylist, Repo.unsafegitcloneoptions, via Git.checkunsafeoptions() — unless the caller passes allowunsafeoptions=True. This denylist mechanism is exactly the guard that the last ~16 published GHSAs against this repo (2026-07-12 → 2026-08-05) have repeatedly found…
Recommended action: Upgrade to 3.1.59 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Repo.blame() / Repo.blameincremental() guard forwarded revision options against unsafegitrevisionoptions, but that denylist only contains the file-WRITE options --output/-o. git blame also honors --contents <file and -S <file, which cause the file's lines to be echoed into the blame result — an arbitrary file READ. Neither option is in the denylist, so a caller-influenced revision value of --contents=<path passes the guard and leaks file contents. This is a distinct sink-option and impact class (READ) from GHSA-956x-8gvw-wg5v (which addressed the blame --output WRITE), directly analogous to GHSA-539m-9xh6-q6rr (archive READ gap accepted separately from the archive write/exec advisory). Root Cause unsafegitrevisionoptions = ["--output","-o"] (git/repo/base.py:188). The rev string is passed to optioncandidates([rev], kwargs) and placed BEFORE the -- separator (base.py:841). The canonical name of --contents=... is contents, which is not on the denylist, so no UnsafeOptionError…
Recommended action: Upgrade to 3.1.59 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
- CWE: CWE-88 (Argument Injection) / CWE-94 (Code Injection) — via a read-then-corrupt-on-rewrite config round trip, not a direct setter argument - Affected component: git/config.py — GitConfigParser.read() (multi-line value decoding, lines 444-541, esp. stringdecode() at line 460 and its call sites at 519/541) and GitConfigParser.write()/writesection() (serialization, lines ~694-712, esp. line 708) - Affected version: GitPython at HEAD (9729ed3b948f2bde09f1f188c5311e172212b67e, 2026-08-05, VERSION 3.1.58) Reachability GitPython added UNSAFECONFIGCHARSRE / valuetostringsafe() / assureconfignamesafe() guards (commits c417af46, 1ed1b924, a495ccd3, and PR #2176) to reject a Python string containing a raw \r/\n/NUL byte, or syntax-bearing characters, when it is passed as an argument to set(), setvalue(), addvalue(), or addsection(). This closed the four config-injection GHSAs above. That guard is applied only on the write-argument surface. It is never consulted for values that entered…
Recommended action: Upgrade to 3.1.59 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
[HIGH] Arbitrary local file content disclosure via [include] directive in untrusted .gitmodules (SubmoduleConfigParser never disables mergeincludes) - CWE: CWE-200 (Exposure of Sensitive Information) / CWE-73 (External Control of File Name or Path) - Affected component: git/objects/submodule/base.py, Submodule.configparser() (~line 273) constructing SubmoduleConfigParser(fpmodule, readonly=readonly); git/config.py, GitConfigParser.init (mergeincludes default), GitConfigParser.read()/includedpaths() (include-path resolution, ~lines 630-685), GitConfigParser.read() (~line 493-498, MissingSectionHeaderError) - Affected version: GitPython at HEAD (9729ed3b948f2bde09f1f188c5311e172212b67e, 2026-08-05, VERSION 3.1.58) Reachability GitConfigParser.init defaults mergeincludes=True: any config file it parses has its [include] (and, when a repo= is supplied, [includeIf ...]) directives followed and merged in. The maintainers already recognized this as dangerous for one specific case and fixed…
Recommended action: Upgrade to 3.1.59 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
The checkunsafeoptions guard can be bypassed on every guarded method (clone/clonefrom, fetch/pull/push, lsremote, itercommits, blame, archive) by combining a single-character kwarg with splitsinglecharoptions=False. The guard's candidate list omits the smuggled option, but transformkwarg emits a JOINED -n<value argv token that git parses as --upload-pack=<cmd, yielding arbitrary command execution at the default allowunsafeoptions=False. This is an incomplete-fix bypass of commit e8d0fbf7 (the fix for GHSA-r9mr-m37c-5fr3), which only emits value-derived candidates when splitsinglecharoptions is True. Root Cause optioncandidates derives value-token candidates only under if len(key)==1 and splitsinglecharoptions: (cmd.py:1048, added by e8d0fbf7). With splitsinglecharoptions=False, optioncandidates([], {"n":"utouch <cmd;git-upload-pack"}) returns only ['-n'] (not on the denylist), so the guard passes. But transformkwarg('n', value, splitsinglecharoptions=False) emits the JOINED…
Recommended action: Upgrade to 3.1.58 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
GitPython's config-name validator only neutralizes CR/LF/NUL for the "option" label; it does not reject =, #, ;, [, ], or whitespace in an option name. writesection writes the option name verbatim into the config file, so an option name such as sshCommand = touch <cmd is written as \tsshCommand = touch <cmd = <value, which git parses as core.sshCommand = touch <cmd (the trailing comments out the intended value). This forges arbitrary config directives (core.sshCommand, core.hooksPath, alias.) → RCE on the next git operation. This is a distinct field (option name, not section name) and distinct character class (=/#/space, not newline/bracket) from GHSA-3rp5-jjmw-4wv2 (section-name bracket injection) and GHSA-mv93-w799-cj2w / GHSA-v87r-6q3f-2j67 (newline injection). Root Cause assureconfignamesafe(name, label) (git/config.py:897) applies the bracket/quote state machine ONLY when label == "section"; for the "option" label it falls through with just the UNSAFECONFIGCHARSRE…
Recommended action: Upgrade to 3.1.58 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
GitPython computes the on-disk location of a submodule's separate Git directory (.git/modules/<name) from the submodule's .gitmodules section name with no validation. Because that name is fully attacker-controlled content of a cloned repository, a malicious repository can set a submodule name to a traversal string (e.g. ../../../../home/victim/.something) and cause GitPython to create and initialize a full Git repository at an attacker-chosen filesystem path outside the intended clone directory. The only precondition is that a victim clones the malicious repository with GitPython and runs submodule initialization (submoduleupdate(init=True) / sm.update(init=True)), a very common and often automatic step. Core Git itself already blocks this exact attack class (CVE-2018-11235), but GitPython's independent reimplementation never adopted an equivalent check. Details src/GitPython/git/objects/submodule/util.py smname() strips the submodule " / " wrapper from a .gitmodules…
Recommended action: Upgrade to 3.1.58 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
IndexFile.remove() and Head.checkout() forward kwargs into git rm and git checkout with no guard. Passing --pathspec-from-file=<file together with --pathspec-file-nul makes Git treat the whole file as a single NUL-delimited pathspec, and the unmatched-pathspec error quotes it verbatim. GitPython surfaces that through GitCommandError.stderr, so the entire contents of a caller-chosen file are returned to the caller in band. This is the same primitive as Instance 2 of GHSA-3f7w-8rr8-f37f - TagReference.create() with -F, arbitrary file read returned in band - at two sites that advisory assessed and cleared. Prior art, and why I am filing rather than commenting GHSA-3f7w-8rr8-f37f's sweep table lists these four sites with the assessment "--pathspec-from-file only reads a pathspec; no write or disclosure primitive found": | Call site | git command | that advisory's assessment | |---|---|---| | IndexFile.remove() | rm | --pathspec-from-file only reads a pathspec; no write or…
Recommended action: Upgrade to 3.1.58 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Repo.init() forwards kwargs verbatim to git init with no unsafe-option guard and no allowunsafeoptions parameter. git init --template=<dir copies <dir/hooks/ into the new repo's .git/hooks, so an attacker-controlled template kwarg plants a hook that executes on the next git operation → arbitrary code execution. --template is already recognized as unsafe for clone (it is on unsafegitcloneoptions, and GHSA-6p8h-3wgx-97gf covers the clone path), but Repo.init is a distinct method that never received a guard and needs an independent fix. Root Cause Repo.init(path, mkdir, odbt, expandvars, kwargs) is a bare git.init(kwargs) (git/repo/base.py:1435) with no checkunsafeoptions and no allowunsafeoptions. Impact Arbitrary code execution (hook fires on next git op) at the privileges of the host process. Two preconditions raise attack complexity (AC:H): the app must forward a template= kwarg (KEY control) AND the attacker must stage an executable hook directory at a known path — the same…
Recommended action: Upgrade to 3.1.58 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
IndexFile.fromtree, IndexFile.reset (→ fromtree) and IndexFile.mergetree append caller-influenced treeish strings positionally to git read-tree with no unsafe-option guard, no allowunsafeoptions parameter, and no -- separator. git read-tree --index-output=<file writes the resulting index to an arbitrary path, and last-occurrence-wins lets an injected --index-output override the method's internal temp path — clobbering an arbitrary file with a valid git-index blob. This is a distinct, never-guarded sink: commit 3af0c251 (GHSA-3f7w-8rr8-f37f) guarded only checkoutindex and tag; readtree was left unprotected (it is among the acknowledged unguarded call sites in that advisory's sweep but was never reported or fixed). Root Cause fromtree (index/base.py:388), reset (delegates to fromtree), and mergetree (index/base.py:291) call repo.git.readtree(arglist) with no checkunsafeoptions and no --. The treeish is caller-influenced and positional. Impact Arbitrary file overwrite /…
Recommended action: Upgrade to 3.1.58 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Commit.count() forwards kwargs into revlist with no checkunsafeoptions guard (the guard exists only in the sibling iteritems, commit.py:341). git rev-list --output=<path opens and truncates the target file to 0 bytes before revision parsing, so count(output='/victim') destroys/blanks an arbitrary file. Root Cause commit.py:290-291 calls self.repo.git.revlist(self.hexsha, kwargs) with no checkunsafeoptions and no allowunsafeoptions parameter. The sibling iteritems (commit.py:341) is guarded; count is not. This is a distinct, uncovered sink — GHSA-956x-8gvw-wg5v fixed itercommits/blame, not count. Impact Destroy/blank an arbitrary file at process privilege (integrity/availability). Reachability is key-control only (count uses self.hexsha, not a user ref), and the write is a 0-byte truncation (no content control), so MEDIUM. Proof of Concept Attack Chain 1. Entry: app forwards user options - commit.count(output='/victim'). Guard: none. Bypass proof: itercommits(output=) raises…
Recommended action: Upgrade to 3.1.56 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Target: gitpython-developers/GitPython Tested: HEAD 07e80555 (2026-07-25), latest release 3.1.55, git version 2.50.1 Summary Repo.archive() does call the option guard, so this is not a missing-guard report. The guard is present and working; the denylist it consults is incomplete. The comment on --output states the protected class in the project's own words: an option that lets the caller name a filesystem path is unsafe. --output is blocked because it writes to a caller-chosen path. git archive also accepts --add-file=<path and --add-virtual-file=<path:content (both present in current git; verified against git version 2.50.1). --add-file reads a caller-chosen path — including an absolute path outside the repository — and places the bytes into the archive the caller receives. Neither option is in the list, and no other layer references them: Net effect: the guard blocks arbitrary file write at this sink while permitting arbitrary file read at the same sink. Reachability proof…
Recommended action: Upgrade to 3.1.57 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.
Target: gitpython-developers/GitPython Tested: HEAD 07e80555 (2026-07-25), latest release 3.1.55, git version 2.50.1 Reported instances: 2 exploitable, from a sweep of 14 unguarded call sites Summary GitPython blocks dangerous git options through Git.checkunsafeoptions(), gated per method by an allowunsafeoptions parameter. That guard is applied per call site, so any API that forwards kwargs into a git command without calling it passes caller-controlled options straight to git. A mechanical sweep of every method that forwards kwargs into a .git.<command(...) call found 14 sites with no guard. Two reach a git option that takes a filesystem path: | | Call site | git option | Impact | |---|---|---|---| | 1 | IndexFile.checkout() → git checkout-index | --prefix=<path | arbitrary file overwrite with repository-controlled content | | 2 | TagReference.create() → git tag | -F <file / --file=<file | arbitrary file read, returned in-band | This is the same defect class already fixed in…
Recommended action: Upgrade to 3.1.57 or later, then redeploy and verify.A vendor fix does not resolve your finding until you verify it in your own systems.