GHSA-cm62-gvxx-vmxxHigh· 8.6▾ TwilightDulwich: Symlink directory traversal in stash pop allows arbitrary file write via intermediate directory symlinks
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 47.3 · likelihood 0 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Dulwich's stash.py:pop() function is vulnerable to symlink directory traversal, allowing an attacker to write arbitrary files outside the repository worktree when a victim pops a stash in a malicious repository.
The pop() function at dulwich/stash.py:236 uses os.path.exists(parent_dir) to check if a parent directory exists before writing stashed files. os.path.exists() follows symlinks, so when an intermediate directory in the path is a symlink pointing outside the worktree (e.g., link → ../../.git/hooks), the check passes and subsequent file writes resolve through the symlink.
The validate_path() function (line 228) only validates path component names against INVALID_DOTNAMES — it performs zero filesystem symlink detection. On dulwich 1.2.7 (latest release), build_file_from_blob() has no symlink protection whatsoever.
An attacker can craft a malicious repository that, when a victim clones it and performs a stash pop operation, writes attacker-controlled content to arbitrary filesystem locations. Writing to .git/hooks/post-checkout achieves Remote Code Execution on the victim's machine on the next git checkout operation.
main containing link (symlink → ../../.git/hooks) and branch feature containing link/post-checkout (executable payload)main — symlink link exists in worktree)feature, makes changes, runs stash.push()main (restoring the link symlink)stash.pop(0) — stash contains link/post-checkoutos.path.exists("link") returns True (symlink to existing directory), os.makedirs skippedbuild_file_from_blob(blob, mode, "link/post-checkout") → open("link/post-checkout", "wb") follows the intermediate symlink → payload written to .git/hooks/post-checkoutBefore writing any file, verify that no component of the target path resolves through a symlink outside the worktree. Use os.path.realpath(parent_dir) and confirm it stays within the repository root. Alternatively, use os.open() with O_NOFOLLOW on each path component.
Reported by zx (Jace)
dulwich >= 0.22.5, <= 1.2.7Upgrade to a patched release:
dulwich 1.2.8Connected by shared product, vendor, weakness, or advisory.
GHSA-5fqc-mrg8-w798High· 8.6Dulwich: Symlink directory traversal in filter-branch index_filter via cross-commit state persistence
GHSA-8w8g-wq8h-fq33High· 8.6Dulwich: Symlink write-through in checkout(paths=[]) via raw os.open bypasses all symlink protections
GHSA-8mcx-5rqc-vhmfHigh· 8.8Dulwich: Arbitrary File Write (RCE) on Windows via Unvalidated Drive Letters in Tree Paths
CVE-2026-47712Low· 3.3Dulwich doesn't sanitize commit subjects in `porcelain.format_patch`
GHSA-35mr-4567-66vgMedium· 6.5Dulwich: Infinite Loop Denial of Service (DoS) in Packfile Object Resolution
CVE-2026-47734Medium· 5.7Dulwich has unbounded memory allocation in receive-pack from crafted thin packs