| Package | Affected versions | Patched versions |
|---|---|---|
| dulwich | ≥ 0.22.5, < 1.2.8 | 1.2.8 |
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 and confirm it stays within the repository root. Alternatively, use with on each path component.
Running one of these products? Scan your site to see whether it exposes a vulnerable version.
Scan my site freeos.path.realpath(parent_dir)os.open()O_NOFOLLOWReported by zx (Jace)