🛡️ CVE-2026-52726 — dulwich

🟠 CVSS 8.0 — High ✅ No Known Exploit CWE-22 NVD
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

Dulwich's submodule path traversal in porcelain.submodule_update / porcelain.clone(recurse_submodules=True) yields RCE via attacker-dropped .git/hooks payload

Summary

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.

Affected

  • Package: dulwich (PyPI)
  • Affected versions: >=0.23.2, <1.2.5
  • Affected platforms: all (Linux, macOS, Windows). Exploitation does not require a case-insensitive or NTFS filesystem, because the path written is a literal .git/hooks rather than a case- or short-name-aliased form.

Affected entry points:

  • dulwich.porcelain.submodule_update(repo, init=True, recursive=True)
  • dulwich.porcelain.clone(source, target, recurse_submodules=True)
  • dulwich submodule update CLI / dulwich clone --recurse-submodules CLI

Vulnerable code

The submodule path from the tree's gitlink entry (and matching .gitmodules) is consumed without validation in [dulwich/porcelain/submodule.py](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fac39e79ca354327897e133/dulwich/porcelain/submodule.py#L154-L234).

The attacker-controlled path enters the loop from iter_cached_submodules ([submodule.py#L154-L168](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fac39e79ca354327897e133/dulwich/porcelain/submodule.py#L154-L168)):

```python

for path, target_sha in submodules_to_update:

path_str = (

path.decode(DEFAULT_ENCODING) if isinstance(path, bytes) else path

)

submodule_name: bytes | None = None

for sm_path, sm_url, sm_name in read_submodules(gitmodules_path):

if sm_path == path:

submodule_name = sm_name

break

if not submodule_name:

continue

```

It flows unchecked into os.path.join and the filesystem ([submodule.py#L187-L188](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fac39e79ca354327897e133/dulwich/porcelain/submodule.py#L187-L188)):

```python

submodule_path = os.path.join(r.path, path_str)

submodule_git_dir = os.path.join(r.controldir(), "modules", path_str)

```

Finally, the attacker tree's contents are materialized into that directory via build_index_from_tree with no validate_path_element argument, defaulting to the lax validator ([submodule.py#L229-L234](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fac39e79ca354327897e133/dulwich/porcelain/submodule.py#L229-L234)):

```python

build_index_from_tree(

submodule_path,

sub_repo.index_path(),

sub_repo.object_store,

tree_id,

)

```

Three issues compound:

1. path_str originates from the parent repository's tree gitlink entry (attacker-controlled) and is never validated against .git, .., or other path-traversal patterns. The same value is read from the attacker-supplied .gitmodules blob via [read_submodules](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fac39e79ca354327897e133/dulwich/config.py#L1637-L1665), which also performs no validation.

2. submodule_path = os.path.join(r.path, path_str) therefore resolves to an attacker-chosen directory anywhere on disk (e.g. <worktree>/.git/hooks).

3. [build_index_from_tree](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fac39e79ca354327897e133/dulwich/index.py#L2034-L2044) is called without validate_path_element, so it defaults to validate_path_element_default, which only rejects literal .git, ., and ... It does not refuse a root_path that is itself inside the parent's .git directory, and it honors the attacker tree's file modes including executable bits (0o100755).

Reachability

A direct production call path from a user invocation: porcelain.clone(source, target, recurse_submodules=True) at [dulwich/porcelain/__init__.py:1548-1551](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fac39e79ca354327897e133/dulwich/porcelain/__init__.py#L1548-L1551) calls submodule_update(repo, init=True, recursive=True) once the parent clone completes, reaching the unsanitized loop at [submodule.py#L154-L234](https://github.com/jelmer/dulwich/blob/8efb7d19eac519cd7fa

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality none, integrity none, availability high.

Weakness class

CVE-2026-52726 is classified as CWE-22: Path Traversal. A file path built from user input is not confined to the intended directory, letting an attacker reach files elsewhere on the filesystem.

Affected software

CVE-2026-52726 is recorded against 2 packages.

  • dulwich (from 0.23.2 up to 1.2.5)
  • unknown

Timeline and source

Published on 2 July 2026 and last revised on 13 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Package)
github.com (Web)

CVE-2026-52726 on other distributions

Each distribution ships its own build and its own fixed version. Pick the one you run:

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE CWE-22
Public Exploit ✅ No
Source NVD
Published 2026-07-02
Updated 2026-08-12
Modified 2026-07-13
Fix URL N/A

Affected Packages

Software From version Fixed in
dulwich 0.23.2 1.2.5
unknown

Site Security Check

Is dulwich part of your stack?

CVE-2026-52726 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.