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
- 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.
- Move to a Python with a working tarfile.data_filter so the validated filter='data' path is used.
- Re-download the artifact from a trusted publisher if the symlinks look hostile.
- 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
- Inspect symlink entries with `tar -tvf` before extracting.
- Use a Python with a working data_filter to delegate filtering to CPython.
- Extract untrusted archives in an isolated sandbox.
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
- Refusing to extract {member.name}: would write outside {dest
- 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/fb760a383087b53f.json.
Report an issue: GitHub.