GitHub Advisoryadvisory · PyPI

GHSA-23mf-xhv8-69c2 GitPython: arbitrary file read via attacker-controlled branch name in `Head.checkout()` (incomplete fix of CVE-2026-78679)

HIGH Published 2026-10-27 Modified 2026-10-27 Official page ↗
Ecosystem
PyPI
Packages
GitPython
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
Weaknesses
CWE-200, CWE-73, CWE-88
Reviewed
2026-10-27

Affected packages

• PyPI GitPython: < 3.2.0 → fixed in 3.2.0

Description

> 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 | `GitPython` (PyPI) | | Affected versions | **` List[str]: """Collect possible option spellings before command-line transformation.""" options = [ option for option in cls._unpack_args([arg for arg in args if arg is not None]) if option.startswith("-") ] ``` So `_option_candidates([], kwargs)` means: *check the keyword arguments, never check the positional arguments this method puts on the command line.* That is fine when the method has no attacker-influenced positional, or when it separates them with `--`. `git/refs/head.py:283-293` — `Head.checkout()` has neither: ```python if not allow_unsafe_options: Git.check_unsafe_options( options=Git._option_candidates([], kwargs), unsafe_options=Git.unsafe_git_pathspec_from_file_options, ) kwargs["f"] = force if kwargs["f"] is False: kwargs.pop("f") self.repo.git.checkout(self, kwargs) ``` `self` is the only positional. It serialises to the branch name, lands in the `git checkout` argv, and is not in the list the guard inspects. `_call_process` builds `args_list = opt_args + ext_args`, so keyword-derived options precede positionals — which is why the rest of the library puts `"--"` first (`Remote.fetch/pull/push`, `Repo._clone`, `Repo.archive`, `IndexFile.remove`, `Commit.count` all do). `Head.checkout` does not. Why the branch name is attacker-controlled Git accepts a refname whose branch component begins with `-` and contains `/` (`raw/02-refname-accepted-and-cloned.txt`): ``` >>> git check-ref-format 'refs/heads/--pathspec-from-file=/etc/passwd' exit=0 stderr='' => un refname que empieza con '-' y lleva '/' es VALIDO para git >>> git -C update-ref 'refs/heads/--pathspec-from-file=/etc/passwd' beea87239099 exit=0 stderr='' >>> git -C symbolic-ref HEAD 'refs/heads/--pathspec-from-file=/etc/passwd' exit=0 HEAD del bare = refs/heads/--pathspec-from-file=/etc/passwd ref en disco como arbol jerarquico: True >>> Repo.clone_from('file://', ) # la app solo clona ramas LOCALES creadas por el clone, sin que nadie las pida: refs/heads/--pathspec-from-file=/etc/passwd repo.active_branch.name = '--pathspec-from-file=/etc/passwd' coincide con el nombre hostil: True ``` The transcript above also shows the second half: `git clone` materialises that branch locally on its own** when the remote `HEAD` points at it — the consuming application never asks for it. A plain git server accepts the refname over `receive-pack`, with no `--force` and no special config (`raw/06-push-accepts-refname.txt`), so the attacker does not need filesystem ac…

Related

Weakness · CWE (3)

  • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor weakness
  • CWE-73 External Control of File Name or Path weakness
  • CWE-88 Improper Neutralization of Argument Delimiters in a Command weakness

References