cf2tf · GitPython: Denial of Service via catastrophic backtracking (ReDoS) in Actor.name_email_regex — commit author/committer
Affects cf2tfSource GCVE Database
Not known to be exploited · CVSS 8.7 High : upgrade to 0.9.2_9 soon.
Data as of 26 Oct 2026, 17:16 UTC
- EPSS
- 0.5% chance of exploitation in 30 days – higher than 43% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 0.9.2_9
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NExploitation evidence
Disclosed: evidence found.
CVE published 9 Sep 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponised: no evidence on record.
No public exploit code indexed in Metasploit or Exploit-DB
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.
What to do
Upgrade cf2tf to 0.9.2_9.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
Homebrewcf2tfpkg:brew/cf2tfOther threats | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
### Summary GitPython's `Actor.name_email_regex` regular expression (`git/util.py`, line 863) is vulnerable to catastrophic backtracking (ReDoS — Regular Expression Denial of Service). When GitPython parses the `author` or `committer` header of a git commit object that contains a long string with an unterminated `<` (no matching `>`), the Python regex engine enters quadratic backtracking, causing complete single-threaded CPU exhaustion proportional to the square of the input length. A single crafted commit object can block any GitPython API call that reads `.author` or `.committer` for **over two minutes per invocation**, enabling denial of service against CI runners, code-hosting backends, repository-scanning pipelines, or any service that processes commits from third-party or untrusted repositories. --- ### Details **Vulnerable file and line:** `git/util.py`, line 863: ```python name_email_regex = re.compile(r"(.*) <(.*?)>") ``` This regex is evaluated inside `Actor._from_string()` (line 909) every time GitPython resolves a commit's `.author` or `.committer` property. **Full call chain — from public API to vulnerable sink:** commit.author # any ordinary GitPython API call └── git/objects/commit.py:917 Commit._deserialize() └── git/objects/util.py:341 parse_actor_and_date(author_line) └── git/util.py:909 Actor._from_string(string) └── Actor.name_email_regex.search(string) ← VULNERABLE `author_line` is decoded directly from the raw bytes of the git commit object with **no length limit, character restriction, or timeout** applied at any point before reaching the regex engine. The same chain is triggered by: - `commit.author` - `commit.committer` - `repo.iter_commits()` - `repo.blame()` - Any web service / CI tool that displays or processes commit metadata **Why this pattern backtracks catastrophically:** The pattern `(.*) <(.*?)>` contains an unbounded greedy group `(.*)` followed by a literal space and `<`. When the input is a long string that contains `<` but no closing `>`, the regex engine must try every possible split position for the greedy group — O(n²) candidate positions for a string of length n — each of which then drives the inner lazy group into further sub-match attempts. This is the well-documented "catastrophic backtracking" failure mode for this family of patterns. **Empirically measured scaling (tested against GitPython 3.1.59, commit 52a6cba):** | Author field length (bytes) | Time to resolve `.author` | |-----------------------------|--------------------------| | 1,000 | 0.0035 s | | 5,000 | 0.084 s | | 10,000 | 0.341 s | | 20,000 | 1.525 s | | 40,000 | 5.963 s | | 60,000 | 13.360 s | | 80,000 | 23.871 s | | **200,000** | **150.488 s** | Each doubling of input size roughly quadruples processing time (e.g. 40,000 → 80,000 bytes: 5.96 s → 23.87 s ≈ 4.0×), confirming O(n²) growth. Git itself imposes **no practical size limit** on author name fields in the object format. **How the malicious object reaches a victim:** The PoC creates the commit object as a correctly SHA-1-hashed, zlib-compressed git loose object written directly into `.git/objects/`. `git cat-file -t <sha>` confirms it is a valid `commit` type and git's own read-side tools display it without error. Only git's write-side tooling (`git commit --author`, `git update-index`, explicit `git fsck`) applies the sanity checks that would reject a malformed author line. Delivery paths that bypass those checks include: - A git server with `receive.fsckObjects = false` (common in self-hosted deployments) - A `.git` directory shipped as a tarball, backup, or zip archive - A git bundle file - Any automated mirror or import tool that operates at the object level --- ### PoC **Environment used for testing:** - GitPython 3.1.59, installed in editable mode from source (no code modifications) - Python 3.12.3, git 2.43.0, Ubuntu 24.04 **Script 1 — craft the malicious repository (`craft_malicious_repo.py`):** ```python #!/usr/bin/env python3 """ Creates a git repository with one commit whose 'author' field is a large string containing an unterminated '<'. Bypasses git's write-side sanity checks by writing the raw object directly into .git/objects/. Usage: python3 craft_malicious_repo.py <target_dir> <payload_size_bytes> """ import hashlib, os, subprocess, sys, zlib def run(cmd, cwd): return subprocess.run(cmd, cwd=cwd, check=True, capture_output=True, text=True) def main(): if len(sys.argv) != 3: print(f"usage: {sys.argv[0]} <target_dir> <payload_size_bytes>") sys.exit(1) target_dir, payload_size = sys.argv[1], int(sys.argv[2]) os.makedirs(target_dir, exist_ok=True) run(["git", "init", "--quiet"], cwd=target_dir) run(["git", "config", "user.email", "poc@example.com"], cwd=target_dir) run(["git", "config", "user.name", "PoC"], cwd=target_dir) with open(os.path.join(target_dir, "README.txt"), "w") as f: f.write("GitPython ReDoS PoC repository\n") run(
References
- github.com/gitpython-developers/GitPython/security/advisories/GHSA-g5vv-9gxw-82hx (opens in a new tab)
- github.com/gitpython-developers/GitPython/pull/2215 (opens in a new tab)
- github.com/gitpython-developers/GitPython/commit/751473a5f3221d6f989291cbebcc404353fd3ba8 (opens in a new tab)
- github.com/gitpython-developers/GitPython (opens in a new tab)
- github.com/gitpython-developers/GitPython/releases/tag/3.1.60 (opens in a new tab)
- github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3984.yaml (opens in a new tab)
- vulncheck.com/advisories/gitpython-before-3.1.60-denial-of-service-via-redos (opens in a new tab)