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

  1. Inspect the archive with `tar -tvf <file>` and identify members with leading '/', absolute paths, or '..' segments, then rebuild or re-download a clean archive.
  2. 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.
  3. If the archive is third-party and looks malicious, discard it and re-acquire it from a trusted source.
  4. 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

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


AI-assisted analysis of python-poetry/poetry@92b74dcfe3 (2026-08-04). Data as JSON: /data/errors/f85dd7a9615a2f2a.json. Report an issue: GitHub.