GHSA-CM62-GVXX-VMXX
Vulnerability from github – Published: 2026-10-02 18:52 – Updated: 2026-10-02 18:52Summary
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.
Root Cause
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.
Impact
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.
Attack Scenario
- Attacker creates a repository with branch
maincontaininglink(symlink →../../.git/hooks) and branchfeaturecontaininglink/post-checkout(executable payload) - Victim clones the repository (landing on
main— symlinklinkexists in worktree) - Victim checks out
feature, makes changes, runsstash.push() - Victim checks out
main(restoring thelinksymlink) - Victim runs
stash.pop(0)— stash containslink/post-checkout os.path.exists("link")returns True (symlink to existing directory),os.makedirsskippedbuild_file_from_blob(blob, mode, "link/post-checkout")→open("link/post-checkout", "wb")follows the intermediate symlink → payload written to.git/hooks/post-checkout- Next checkout operation triggers the hook → RCE
Suggested Fix
Before 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)
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.2.7"
},
"package": {
"ecosystem": "PyPI",
"name": "dulwich"
},
"ranges": [
{
"events": [
{
"introduced": "0.22.5"
},
{
"fixed": "1.2.8"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-22",
"CWE-59"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-02T18:52:53Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nDulwich\u0027s `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.\n\n## Root Cause\n\nThe `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 \u2192 ../../.git/hooks`), the check passes and subsequent file writes resolve through the symlink.\n\nThe `validate_path()` function (line 228) only validates path component names against `INVALID_DOTNAMES` \u2014 it performs zero filesystem symlink detection. On dulwich 1.2.7 (latest release), `build_file_from_blob()` has no symlink protection whatsoever.\n\n## Impact\n\nAn 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\u0027s machine on the next git checkout operation.\n\n## Attack Scenario\n\n1. Attacker creates a repository with branch `main` containing `link` (symlink \u2192 `../../.git/hooks`) and branch `feature` containing `link/post-checkout` (executable payload)\n2. Victim clones the repository (landing on `main` \u2014 symlink `link` exists in worktree)\n3. Victim checks out `feature`, makes changes, runs `stash.push()`\n4. Victim checks out `main` (restoring the `link` symlink)\n5. Victim runs `stash.pop(0)` \u2014 stash contains `link/post-checkout`\n6. `os.path.exists(\"link\")` returns True (symlink to existing directory), `os.makedirs` skipped\n7. `build_file_from_blob(blob, mode, \"link/post-checkout\")` \u2192 `open(\"link/post-checkout\", \"wb\")` follows the intermediate symlink \u2192 payload written to `.git/hooks/post-checkout`\n8. Next checkout operation triggers the hook \u2192 RCE\n\n## Suggested Fix\n\nBefore 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.\n\nReported by **zx (Jace)**",
"id": "GHSA-cm62-gvxx-vmxx",
"modified": "2026-10-02T18:52:53Z",
"published": "2026-10-02T18:52:53Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jelmer/dulwich/security/advisories/GHSA-cm62-gvxx-vmxx"
},
{
"type": "WEB",
"url": "https://github.com/jelmer/dulwich/commit/40a542cb02f9ac39a9eeac1193472903098698f9"
},
{
"type": "PACKAGE",
"url": "https://github.com/jelmer/dulwich"
},
{
"type": "WEB",
"url": "https://github.com/jelmer/dulwich/releases/tag/dulwich-1.2.8"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Dulwich: Symlink directory traversal in stash pop allows arbitrary file write via intermediate directory symlinks"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.