CVE
CVE-2026-67324
CWE
CWE-78
Affected Surface
- GitPython 3.1.50 on PyPI
- Applications that pass attacker-influenced `multi_options` into `Repo.clone_from()`
- Developer tooling, CI helpers, and web backends that clone repositories on behalf of users
- Build systems that rely on `allow_unsafe_options=False` as the last boundary against dangerous clone flags
CVE-2026-67324 matters because it breaks a control many GitPython callers probably assumed was already doing the dangerous part correctly. In GitPython 3.1.50, Repo.clone_from(..., allow_unsafe_options=False) is supposed to reject clone flags that can cross into command execution, including --upload-pack and -u. The bypass is that the guard matches the blocked short option name, but misses Git’s joined short-option form such as -u/path/to/helper.
That means a wrapper that accepts user-influenced clone options can still smuggle a helper command into git clone even though the application left the “unsafe options” escape hatch disabled.
Affected package and versions
The affected PyPI package is:
GitPython3.1.50
Patched version:
GitPython3.1.51
This is not “all GitPython usage is remote code execution.” The vulnerable path requires a specific application pattern:
- the application uses
Repo.clone_from() - it allows a less-trusted party to influence
multi_options - it relies on
allow_unsafe_options=Falseto block clone-time command execution flags
That pattern is more common than it sounds. It appears in Git importers, mirroring services, repository scanners, build helpers, internal developer portals, and products that let users tune clone behavior through query parameters or job configuration.
Where the guard fails
The upstream advisory describes the relevant path precisely:
Repo.unsafe_git_clone_optionscorrectly marks--upload-pack,-u,--config, and-cas unsafeRepo._clone()joins and splitsmulti_options- option canonicalization does not normalize
-u<value>to the bare short optionu - Git itself accepts the same token as
--upload-pack=<value>
The dangerous call site can look deceptively routine:
from git import Repo
Repo.clone_from(
repo_url,
checkout_path,
multi_options=[f"-u{helper_path}"],
allow_unsafe_options=False,
)
The caller thinks “unsafe options are disabled, so the library should block command-like flags.” But Git parses -u<helper> as the short form of:
--upload-pack=<helper>
That option tells git clone which helper program to execute on the other side of the clone operation. Once GitPython lets the token through, the real security boundary has already failed.
Why this is command execution, not just argument confusion
The most important nuance in the disclosure is that the clone itself can fail and the bug is still exploitable. The public reproduction creates a local helper that exits non-zero after touching a sentinel file. Git returns an error, but the sentinel proves the helper executed.
Sanitized advisory output:
gitpython_version=3.1.50
long_upload_pack_gate=BLOCKED:UnsafeOptionError
joined_short_upload_pack_gate=ALLOWED
clone_result=EXPECTED_EXCEPTION:GitCommandError
sentinel_exists=True
sentinel_text=GITPYTHON_UNSAFE_OPTION_BYPASS
That result is exactly what defenders should care about. The vulnerable boundary is not “can the clone succeed?” It is “can attacker-controlled input cause Git to execute a helper command on the host?” The answer for 3.1.50 is yes.
The code-level mistake
At a high level, the data flow is:
attacker-controlled multi_options
-> Repo._clone()
-> unsafe-option validation
-> joined short option survives canonicalization
-> git clone interprets -u<value> as --upload-pack=<value>
-> helper command executes
The core mistake is treating unsafe-option filtering as a simple name comparison problem, even though Git accepts multiple equivalent spellings for the same dangerous option:
- bare short form:
-u - split value form:
-u <helper> - joined short form:
-u<helper> - long form:
--upload-pack=<helper>
If the filter only catches one representation, the blocked capability is still reachable.
Realistic attack paths
The highest-risk deployments are not normal developer scripts run against trusted repositories. The real AppSec exposure is software that clones on behalf of someone else:
- self-service importers that accept clone parameters from users
- CI/CD helpers that construct clone flags from YAML or JSON job definitions
- internal tooling that mirrors arbitrary repositories into analysis sandboxes
- multi-tenant systems that let customers register external repos and optional Git arguments
- automation that passes through “advanced clone settings” under the assumption GitPython will reject the truly dangerous ones
Those products often run with network reachability, filesystem access, and credentials that matter. If the clone worker can reach build caches, cloud tokens, or artifact registries, helper execution during clone is not a low-severity edge case.
Scoping and detection
Start with dependency inventory:
python3 -m pip show GitPython
rg -n 'GitPython|gitpython' requirements*.txt pyproject.toml poetry.lock Pipfile.lock
Then hunt for clone wrappers that accept external input:
rg -n 'Repo\\.clone_from\\(' src app lib tools scripts
rg -n 'multi_options|allow_unsafe_options' src app lib tools scripts
The key review question is:
Can a user, tenant, job definition, webhook payload, or repository metadata
influence multi_options for Repo.clone_from()?
If the answer is yes and the resolved version is 3.1.50, treat the host as capable of clone-time command execution.
Remediation
Upgrade immediately:
python3 -m pip install --upgrade 'GitPython>=3.1.51'
Then verify the resolved version:
python3 -m pip show GitPython
If a direct upgrade is temporarily blocked:
- stop passing user-influenced data into
multi_options - reject all user-provided clone flags rather than trying to allowlist “mostly safe” ones
- isolate clone workers from package-publishing credentials, cloud tokens, and signing keys
- prefer fixed, application-owned clone options over pass-through user customization
For this bug class, “sanitize later” is the wrong model. The only safe default is that untrusted parties do not get to shape raw Git command options.
Response guidance
If an exposed service used GitPython 3.1.50 and accepted user-controlled clone options:
- assume the clone worker may have executed attacker-selected local helpers
- rotate secrets reachable from that worker
- review clone job definitions and audit logs for suspicious
multi_options - inspect temporary directories, helper scripts, and subprocess telemetry on affected hosts
The larger lesson is that Git wrappers are part of the command-execution boundary. Once a library advertises “allow_unsafe_options=False blocks dangerous clone flags,” that filter becomes security-critical infrastructure. CVE-2026-67324 shows how easy it is for one accepted spelling like -u<value> to punch straight through that boundary.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.