Skip to main content
CVE-2026-87819VulnerabilityPublished Updated

cf2tf · GitPython: Denial of Service via catastrophic backtracking (ReDoS) in Actor.name_email_regex — commit author/committer

Affects cf2tfSource GCVE Database

0
Authoritative record:CVE-2026-87819· High 8.7

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

CVSS 4.0 base score
8.7High
How severe the flaw is on the 0–10 CVSS scale.
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:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 9 Sep 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq 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

ProductAffectedFixed in
Homebrewcf2tfpkg:brew/cf2tfOther threatsAffected
  • >=0.6.1 <0.9.2_9
Fixed in
  • 0.9.2_9

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

Community reviews

Loading reviews…