python-poetry/poetry · error · ValueError
Refusing to extract {member.name}: would write outside {dest
Error message
Refusing to extract {member.name}: would write outside {dest} What it means
Raised by poetry.utils.helpers.extractall (zip=False path) when extracting a tar archive on a Python build that lacks a functional tarfile.data_filter (Python without the attr, or the broken 3.10.12 / 3.11.4 patch levels). For each tar member it computes the absolute destination path and rejects any member whose resolved path is not contained under dest. This is an explicit Zip-Slip / path-traversal guard that mirrors what CPython's data_filter would otherwise enforce.
Source
Thrown at src/poetry/utils/helpers.py:310
if (
hasattr(tarfile, "data_filter")
and sys.version_info[:3] not in broken_tarfile_filter
):
archive.extractall(dest, filter="data")
else:
# Validate all member paths before extraction
#
# Attention: Path.absolute() is not sufficient because it does not
# normalize, i.e. does not remove "..".
#
# We want to avoid Path.resolve() because it is significantly slower
# than os.path.abspath()!
dest = Path(os.path.abspath(dest))
safe_members = []
for member in archive.getmembers():
member_path = Path(os.path.abspath(dest / member.name))
if not member_path.is_relative_to(dest):
raise ValueError(
f"Refusing to extract {member.name}: "
f"would write outside {dest}"
)
if member.issym():
link_target = Path(
os.path.abspath(member_path.parent / member.linkname)
)
if not link_target.is_relative_to(dest):
raise ValueError(
f"Refusing symlink {member.name}: "
f"target {member.linkname} outside {dest}"
)
elif member.islnk():
link_target = Path(os.path.abspath(dest / member.linkname))
if not link_target.is_relative_to(dest):
raise ValueError(
f"Refusing hardlink {member.name}: "
f"target {member.linkname} outside {dest}"View on GitHub (pinned to 92b74dcfe3)
Solutions
- Inspect the archive with `tar -tvf <file>` and identify members with leading '/', absolute paths, or '..' segments, then rebuild or re-download a clean archive.
- Run on a Python version that ships a working tarfile.data_filter (>=3.10.13, >=3.11.5, or >=3.12) so extractall uses archive.extractall(dest, filter='data') instead of the manual check.
- If the archive is third-party and looks malicious, discard it and re-acquire it from a trusted source.
- Patch the producing tool so it writes member names relative to the archive root.
Example fix
# before
extractall(Path('untrusted.tar'), Path('/app/out'), zip=False)
# after - reject pathological members first
import tarfile, os
from pathlib import Path
with tarfile.open('untrusted.tar') as a:
dest = Path(os.path.abspath('/app/out'))
for m in a.getmembers():
if not Path(os.path.abspath(dest / m.name)).is_relative_to(dest):
raise ValueError(f'unsafe member {m.name}')
a.extractall(dest) Defensive patterns
Strategy: validation
Validate before calling
import os, tarfile
from pathlib import Path
def is_safe_tar(source: Path, dest: Path) -> bool:
dest = Path(os.path.abspath(dest))
with tarfile.open(source) as a:
for m in a.getmembers():
if not Path(os.path.abspath(dest / m.name)).is_relative_to(dest):
return False
return True
# call before extractall(..., zip=False)
assert is_safe_tar(source, dest) Try / catch
from poetry.utils.helpers import extractall
try:
extractall(source, dest, zip=False)
except ValueError as e:
# path-traversal member; refuse to proceed
raise Prevention
- Only extract archives from trusted publishers.
- Run on a Python version with a functional tarfile.data_filter so filter='data' is used.
- Extract into a disposable directory/container and inspect members first.
When it happens
Trigger: Calling poetry.utils.helpers.extractall(source, dest, zip=False) where the tar archive contains a member name that resolves outside dest, e.g. '../../etc/passwd', an absolute '/etc/...', or any name using '..' segments, while running on a Python whose tarfile has no (working) data_filter.
Common situations: A crafted or compromised sdist tarball recorded with absolute paths; archives produced by tooling that preserve leading slashes or '..'; CI runners pinned to Python 3.10.12 or 3.11.4 (the two versions the code blocklists as having a broken data_filter).
Related errors
- Refusing symlink {member.name}: target {member.linkname} out
- Refusing hardlink {member.name}: target {member.linkname} ou
- {root} is not a valid repository cache
- Attempting to write {path} outside of the target directory T
- Expected a value for {setting_key} setting.
AI-assisted analysis of python-poetry/poetry@92b74dcfe3 (2026-08-04).
Data as JSON: /data/errors/f85dd7a9615a2f2a.json.
Report an issue: GitHub.