KEVWire

Security intelligence, with context.

Response snapshot Oct 27, 2026, 16:01 UTC

Back to the wire
Permalink
VulnerabilityGHSA-f9j4-qggq-h239

GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command execution

`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.

At a glance

Exploited
Not known
Severity
Not scored yet
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: GitPython: 3.2.0. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-f9j4-qggq-h239 (opens a new tab)

What happened

## Summary

`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

Continue reading

`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 `refspec`. The check builds its candidate list with `Git.

_option_candidates([], kwargs)`, passing an empty list in place of the positionals, so the `refspec` value is never compared against `unsafe_git_pull_options = ["--upload-pack"]`. The normalized `refspec` is then forwarded verbatim to the `git pull` invocation.

The leading `--` separator that neutralizes an option-shaped refspec for `git fetch` and `git push` does not protect `git pull`, because `git pull` re-forwards its trailing positionals to an internal `git fetch` without re-inserting a `--` separator, so the positional becomes an option to that internal fetch.

```text Affected function: git.remote.Remote.pull Source: git/remote.py (guard at the check_unsafe_options call using _option_candidates([], kwargs); sink at the self.repo.git.pull("--", self, refspec, ...) invocation) ```

## Impact

An attacker who controls the `refspec` value passed to `Remote.pull()` achieves arbitrary operating-system command execution as the process invoking GitPython, with no additional privileges and no user interaction, on the default code path.

Exploitation requires that the pulled remote uses a transport that runs upload-pack on the local side, namely a local filesystem path or SSH; over HTTPS the `--upload-pack` value is inert because the server ignores a client-supplied upload-pack program. This transport requirement narrows the population of vulnerable deployments but does not add work for the attacker once a vulnerable target is reached.

Working checkouts commonly use local-path or SSH origins, so the exposed population is substantial.

## Proof of Concept

```text Environment: GitPython from the affected source tree; system git 2.43.0. The pulled origin uses a local filesystem transport (protocol.file.allow=always for git >= 2.38). ```

Reproduction steps:

1. Create an origin repository reachable over a local-path transport. 2. In a consumer repository, register that origin and call `Remote.pull()` with an attacker-controlled refspec. 3. Observe that the injected command executes.

```python import git repo = git.Repo("consumer") # has an origin using a local-path or ssh transport origin = repo.remote("origin") # Attacker-controlled refspec value; default arguments (allow_unsafe_options=False): origin.pull(refspec="--upload-pack=touch /tmp/pwned; git-upload-pack") ```

```text cmdline: git pull -v -- origin --upload-pack=touch /tmp/pwned; git-upload-pack [attack] marker created (RCE) : True [isolate] upload_pack passed as KWARG blocked : True (only the positional refspec escapes) [sibling] same value via git fetch : safe (pull-specific behavior) [control] benign refspec : safe ```

## Attack Chain

1. **Exposure:** An application calls `Remote.pull(refspec= )` on the default path, where ` ` is derived from attacker-influenced input such as a branch or refspec request parameter. 2. **Control:** The attacker supplies the string `--upload-pack= ; git-upload-pack`. The value passes through `Git._unpack_args`, which only flattens and stringifies it, preserving the option shape. 3.

**Path:** The protocol loop calls `Git.check_unsafe_protocols` on the value, which only matches remote-helper transport URLs (a ` ::` form) and does not flag an `--upload-pack=` token. 4. **Guard:** The unsafe-option guard calls `check_unsafe_options(options=Git._option_candidates([], kwargs), ...)`.

Because positionals are passed as an empty list, the positional `refspec` is never inspected against `unsafe_git_pull_options`. 5. **Primitive:** The value is forwarded to `git pull -v -- --upload-pack= ; git-upload-pack`; `git pull` re-forwards the positional to an internal `git fetch` without a `--` separator, so it is honored as the upload-pack program. 6.

**Result:** For a local-path or SSH transport, git executes ` ` as the upload-pack program, giving arbitrary command execution as the calling process user.

## Bypass Evidence

The relevant guard is `Remote.pull()`'s own `check_unsafe_options` call, present in the current released artifact. It is incomplete: it inspects `Git._option_candidates([], kwargs)`, which only considers keyword arguments, so the positional `refspec` is never checked.

This was confirmed empirically: passing the identical value as the `upload_pack=` keyword argument raises `UnsafeOptionError`, while passing it as the positional `refspec` does not, proving that only the positional escapes the guard.

The `--` separator on the `git pull` command line was tested as a possible mitigation and refuted: with system git 2.43.0, `git pull -- '--upload-pack= '` executed the injected command, while the identical value supplied to `git fetch -- ...` and `git push -- ...` did not execute, because those subcommands honor the `--` terminator whereas `git pull` re-forwards positionals to an internal fetch without one.

The `check_unsafe_protocols` transport check was also examined and does not flag the `--upload-pack=` token because it has no ` ::` form. No prior published advi

Affected package ranges reported by GitHub: GitPython: >= 3.1.30, < 3.2.0

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-f9j4-qggq-h239 (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-f9j4-qggq-h239, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 27). GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command execution. https://kevwire.com/record/GHSA-f9j4-qggq-h239

MLA

"GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command execution." KEVWire, 27 Oct. 2026, kevwire.com/record/GHSA-f9j4-qggq-h239.

Chicago

KEVWire. "GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command execution." Last modified October 27, 2026. https://kevwire.com/record/GHSA-f9j4-qggq-h239.

Evidence summary (plain text)

GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command execution (Oct 27, 2026).
`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`).

`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 `refspec`. The check builds its candidate list with `Git._option_candidates([], kwargs)`, passing an empty list in place of the positionals, so the `refspec` value is never compared against `unsafe_git_pull_options = ["--upload-pack"]`. The normalized `refspec` is then forwarded verbatim to the `git pull` invocation. The leading `--` separator that neutralizes an option-shaped refspec for `git fetch` and `git push` does not protect `git pull`, because `git pull` re-forwards its trailing positionals to an internal `git fetch` without re-inserting a `--` separator, so the positional becomes an option to that internal fetch.
Sources:
GitHub-reviewed advisory GHSA-f9j4-qggq-h239: https://github.com/advisories/GHSA-f9j4-qggq-h239
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: GHSA-f9j4-qggq-h239.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 27, 2026, 16:00 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail