Jul 21, 2026·5 min read·16 visits
GitPython fails to validate abbreviated long options and clustered short options, allowing attackers to bypass the unsafe option filters and execute arbitrary shell commands.
GitPython prior to version 3.1.51 contains a command injection vulnerability due to flaws in its option validation routine. By exploiting Git's native argument parser behavior, specifically long-option abbreviation resolution and short-option clustering, attackers can bypass the check_unsafe_options blocklist to execute arbitrary OS commands.
GitPython is a Python library used to interact with Git repositories. Applications rely on its API to clone repositories, fetch commits, and manage git configurations. The library includes a defensive security component named check_unsafe_options designed to prevent remote command execution by validating command line options passed to git processes.
This security boundary is designed to block dangerous parameters such as --upload-pack and -u which can run arbitrary code on the underlying host. However, a major architectural limitation in how GitPython validates these parameters allows attackers to construct payloads that evade validation while resolving into executable payloads inside Git's native CLI wrapper.
The vulnerability is classified as CWE-78 (OS Command Injection) and CWE-184 (Incomplete Blacklist / Blocklist). It affects all deployments of GitPython prior to version 3.1.51 where untrusted inputs are passed to repository subcommand parameters.
The root cause of this vulnerability lies in the logical mismatch between GitPython's exact-match validation logic and Git's native command-line option parser implemented in parse-options.c.
First, Git permits long-option prefix abbreviations. If an option starting with a specific prefix is unique among all valid option names for a given subcommand, Git automatically resolves that prefix to the full option name. For example, Git automatically interprets --upload-p as --upload-pack because there are no other options starting with --upload-p for that command.
Second, Git natively supports short-option clustering and direct value attachment. This feature allows multiple single-dash flags to be combined in a single block (e.g., -fq instead of -f -q) and allows parameters to be directly attached to their target value without spaces or equal signs (e.g., -u/bin/sh).
GitPython's original check_unsafe_options implementation validated options by performing exact-match lookups against a static blocklist of dangerous strings. Because the blocklist only checked for literal strings like --upload-pack or -u, it was completely bypassed by options leveraging abbreviation (like --upload-p) or value attachment (like -utouch). When these bypassed arguments reached the Git process, the Git engine successfully expanded and executed them.
The original verification logic in GitPython parsed argument inputs without evaluating character boundaries or abbreviation paths.
# Vulnerable implementation in git/cmd.py (Simplified)
canonical_unsafe_options = {cls._canonicalize_option_name(option): option for option in unsafe_options}
for option in options:
candidate = cls._canonicalize_option_name(option)
# Exact-match validation failed to detect abbreviations or joined option strings
unsafe_option = canonical_unsafe_options.get(candidate)
if unsafe_option is not None:
raise UnsafeOptionError(f"{unsafe_option} is not allowed...")In the patched version (3.1.51), the developer added character-by-character analysis of short-option tokens and checked for long-option abbreviation paths:
# Patched implementation in git/cmd.py
canonical_unsafe_options = {cls._canonicalize_option_name(option): option for option in unsafe_options}
# Extract unsafe single-character options (e.g., 'u', 'c')
unsafe_short_options = {
canonical: option
for canonical, option in canonical_unsafe_options.items()
if option.startswith("-") and not option.startswith("--") and len(canonical) == 1
}
clusterable_short_options = frozenset("46flnqsv")
for option in options:
candidate = cls._canonicalize_option_name(option)
# Manual scanning of short-option characters and clustered values
option_token = option.split("=", 1)[0].split(None, 1)[0]
if option_token.startswith("-") and not option_token.startswith("--"):
for option_char in option_token[1:]:
unsafe_option = unsafe_short_options.get(option_char)
if unsafe_option is not None:
raise UnsafeOptionError(
f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
)
# Terminate scan early if a non-clusterable option is hit
if option_char not in clusterable_short_options:
breakAn attacker can exploit this vulnerability by submitting repository action parameters that leverage abbreviations to subcommands handled by GitPython.
# Exploitation using prefix abbreviations on vulnerable versions (< 3.1.51)
import git
repo = git.Repo("/tmp/repo")
# By using 'upload_p', the exact match validation is bypassed
repo.git.fetch("origin", upload_p="touch /tmp/rce_poc")Furthermore, critical evaluation of the patch reveals a secondary bypass vulnerability (CWE-184). The hardcoded set of safely clusterable flags clusterable_short_options = frozenset("46flnqsv") is incomplete.
Several subcommands contain valueless boolean flags not declared in this set. For instance, in git fetch, -p (prune) and -k (keep) are valueless boolean flags. An attacker can construct a payload such as -pu/path/to/payload. Because the parser encounters p (which is not in clusterable_short_options), it breaks execution scanning early, bypassing the checks while Git translates -pu as a combination of -p and -u, invoking the command.
Successful exploitation of this vulnerability results in arbitrary shell command execution with the privileges of the running Python application. In standard web applications running background workers or ingestion services, this allows attackers to execute commands directly on internal servers.
An attacker could retrieve environment variables containing API tokens, read sensitive files, modify repository branches, or move laterally into the internal network. The estimated CVSS score of 8.8 reflects the severity of unauthenticated remote execution contexts where user inputs are automatically processed.
Organizations running pipelines, automation platforms, or platform-as-a-service providers that accept user-provided Git repositories or parameters are at high risk.
The direct remediation path is upgrading GitPython dependencies to version 3.1.51 or higher, which introduces advanced scanning and token validation logic to catch option abbreviations and clustered parameters.
If patching is not immediately viable, developers must implement strict validation on all options passed to repository commands. Avoid allowing users to pass arbitrary argument variables or keyword arguments directly to low-level wrappers.
Sanitize and validate arguments using structured whitelists, allowing only expected values (such as branch names and remote URLs) and rejecting any inputs containing dashes or special symbols.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
GitPython gitpython-developers | < 3.1.51 | 3.1.51 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78, CWE-184 |
| Attack Vector | Network (Remote) |
| CVSS v3.1 Score | 8.8 |
| Exploit Status | poc |
| KEV Status | Not Listed |
The application fails to correctly neutralize or validate inputs that are subsequently passed as command options to an OS shell execution command, allowing for option injection bypasses.
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.
CVE-2026-73654 is a high-severity prototype pollution vulnerability in Trigger.dev. The flaw occurs during the handling of run-metadata updates through the PUT /api/v1/runs/:runId/metadata endpoint. Because user-supplied keys are parsed directly by the @jsonhero/path library without sanitization, an authenticated attacker with low privileges can pollute the global Object.prototype. This causes database queries via Prisma ORM to fail validation and induces unhandled exceptions in the Prometheus metrics client, resulting in a process-wide denial of service.
CVE-2026-73559 is an uncontrolled resource consumption vulnerability in the vLLM engine, specifically within the /v1/completions API endpoint, allowing authenticated attackers to cause application-level denial of service via unbounded prompt arrays.
A critical security bypass vulnerability in Argo Workflows allows authenticated attackers with workflow submission privileges to bypass 'Strict' or 'Secure' template referencing restrictions. By injecting unvalidated fields into the nested ArtifactGC configuration, attackers can execute arbitrary pod patches, leading to host namespace escape and cluster-wide privilege escalation.
A Server-Side Request Forgery (SSRF) / Confused Deputy vulnerability has been identified in Pydantic AI UI Adapters (such as VercelAIAdapter). Under certain conditions, a malicious client can supply manipulated message history with provider metadata that forces the server to resolve files within privileged cloud environments (AWS S3, Google Cloud Storage) or model providers. This occurs because the adapters deserialize client-provided metadata structures directly into UploadedFile instances without validation, which are subsequently fetched using high-privilege server credentials.
A path traversal vulnerability in the optional dashboard server of atomic-agents-stack before version 1.1.0 allows unauthenticated remote attackers to read arbitrary files from the host filesystem.