A AegiFlow
HIGHCVSS 8.4

GHSA-956x-8gvw-wg5v

GitPython: command injection via unguarded Git options in `Repo.archive()`, `git.ls_remote()`, and arbitrary file overwrite via `Repo.iter_commits()` / `Repo.blame()`

Published
2026-07-21
Modified
2026-07-21
Sources
github-advisory

Summary

## Summary GitPython spawns the real `git` binary 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 with `Git.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 the `git` argv have **no guard at all**: 1. **`Repo.archive(ostream, treeish=None, prefix=None, **kwargs)`** forwards `**kwargs` verbatim into `git archive`. An attacker-influenced options mapping such as `{"remote": ".", "exec": " "}` becomes `git archive --remote=. --exec= -- `, and `git archive --remote= ` invokes `git-upload-archive` whose path is overridden by `--exec` → **arbitrary command execution under default Git configuration** (no `protocol.ext.allow` needed). 2. **`repo.git.ls_remote( , upload_pack=" ")`** (and the dynamic-command builder generally) turns the `upload_pack` kwarg into `--upload-pack= ` with no guard → **arbitrary command execution**. 3. **`Repo.iter_commits(rev)`** and **`Repo.blame(rev, file)`** place the caller's `rev` value 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/file` is parsed by `git rev-list` / `git blame` as the `--output` option, 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`: ```python unsafe_git_fetch_options = [ # Arbitrary command execution. "--upload-pack", "--receive-pack", # Arbitrary file overwrite. "--exec", ] ``` and enforces them via `Git.check_unsafe_options()` (`git/cmd.py:963`): ```python def check_unsafe_options(cls, options, unsafe_options): ... if unsafe_option is not None: raise UnsafeOptionError(f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.") ``` But `check_unsafe_options` is invoked from **only five sites**, all network commands: ``` git/remote.py:1071 Remote.fetch git/remote.py:1125 Remote.pull git/remote.py:1198 Remote.push git/repo/base.py:1410 / :1412 Repo.clone_from ``` The following sinks call `git` with caller-controlled options/positionals and are **not** guarded: ### 1. `Repo.archive` — command execution (`git/repo/base.py:1623`) ```python def archive(self, ostream, treeish=None, prefix=None, **kwargs): ... self.git.archive("--", treeish, *path, **kwargs) return self ``` `treeish` and `path` are correctly placed after `--`, but `**kwargs` are converted by `Git.transform_kwarg` (`git/cmd.py:1487`) into `-- = ` flags and inserted **before** the `--` by `_call_process`, with no `check_unsafe_options`. `Repo.archive` already documents user-facing kwargs (`format`, `prefix`, `path`), so forwarding a caller options mapping is an expected usage. Final argv: ``` git archive --remote=. --exec= -- ``` `git archive --remote= ` runs the upload-archive helper; `--exec= ` overrides the helper path, executing ` ` on the host. This works with **default Git config** — it does not rely on the `ext::` transport (which is blocked by default). ### 2. `repo.git.ls_remote(..., upload_pack=...)` — command execution (dynamic

Affected packages

EcosystemPackageAffected versionsFixed versions
PyPIGitPython3.1.51

Remediation: Upgrade to 3.1.51 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.