python-poetry/poetry · error · ValueError

Refusing symlink {member.name}: target {member.linkname} out

Error message

Refusing symlink {member.name}: target {member.linkname} outside {dest}

What it means

Raised by extractall during the manual (non-data_filter) tar validation, specifically for symbolic-link members (member.issym()). The link target is resolved relative to the member's parent directory and, if it falls outside dest, extraction is refused. This blocks symlink-based traversal where a symlink inside the archive points at a file outside the extraction directory.

Source

Thrown at src/poetry/utils/helpers.py:319

                #  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}"
                            )
                    safe_members.append(member)
                archive.extractall(dest, members=safe_members)


_DEPRECATED_DOWNLOAD_EXPORTS = {
    "Downloader",
    "download_file",
    "HTTPRangeRequestSupportedError",

View on GitHub (pinned to 92b74dcfe3)

Solutions

  1. Run `tar -tvf <file>` and look for symlink entries ('l'/'s' type flags) whose targets escape the archive root, then rebuild the archive without them.
  2. Move to a Python with a working tarfile.data_filter so the validated filter='data' path is used.
  3. Re-download the artifact from a trusted publisher if the symlinks look hostile.
  4. Extract into a throwaway directory/container to confirm safety first.

Example fix

# before
extractall(Path('pkg.tar'), dest, zip=False)
# after - vet symlink targets before extracting
import tarfile, os
from pathlib import Path
with tarfile.open('pkg.tar') as a:
    dest = Path(os.path.abspath(dest))
    for m in a.getmembers():
        if m.issym():
            t = Path(os.path.abspath((dest / m.name).parent / m.linkname))
            if not t.is_relative_to(dest):
                raise ValueError(f'symlink escapes dest: {m.name}')
    a.extractall(dest)
Defensive patterns

Strategy: validation

Validate before calling

import os, tarfile
from pathlib import Path

def tar_symlinks_safe(source: Path, dest: Path) -> bool:
    dest = Path(os.path.abspath(dest))
    with tarfile.open(source) as a:
        for m in a.getmembers():
            if m.issym():
                t = Path(os.path.abspath((dest / m.name).parent / m.linkname))
                if not t.is_relative_to(dest):
                    return False
    return True

Try / catch

try:
    extractall(source, dest, zip=False)
except ValueError as e:
    # symlink escaped dest; do not extract
    raise

Prevention

When it happens

Trigger: Extracting a tar (zip=False, on a Python without a working data_filter) that contains a symlink member whose linkname resolves outside dest, e.g. a symlink 'run -> ../../../etc/passwd' or 'link -> /root/.ssh/id_rsa'.

Common situations: Malicious sdists planting symlinks to harvest host secrets; legitimately-bundled symlinks that assume a different install layout; CI on blocklisted Python patch versions.

Related errors


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