pypa/pip · error · InstallationError
The tar file ( ) has a file ( ) trying to install outside…
Error message
The tar file ({}) has a file ({}) trying to install outside target directory ({}) What it means
Raised by pip's tar extraction fallback path (_untar_without_filter, used on Python < 3.12 without tarfile.data_filter) when a tar member's resolved path escapes the destination directory. This is a security guard against path-traversal attacks where a malicious archive contains filenames with '..' sequences that would write outside the target install directory.
Solutions
- Inspect the tar file with 'tar -tf <file>' to identify the offending member path
- Do not install the package if it contains path-traversal entries — report it to the index/maintainer
- Reinstall from the official PyPI source or a trusted mirror to get a clean archive
- Upgrade to Python 3.12+ so pip uses tarfile.data_filter for stronger, upstream-maintained extraction filtering
Example fix
// before pip install suspicious-package==1.0 // after # verify the archive is clean first tar -tf suspicious-package-1.0.tar.gz | grep '\.\.' # if clean, reinstall from official source pip install --index-url https://pypi.org/simple/ suspicious-package==1.0
Defensive patterns
Strategy: validation
Validate before calling
import tarfile, os
def tar_is_safe_to_extract(tar_path: str, dest: str) -> bool:
dest = os.path.abspath(dest)
with tarfile.open(tar_path) as tar:
for member in tar.getmembers():
target = os.path.join(dest, member.name)
if not os.path.abspath(target).startswith(dest + os.sep):
return False
if member.issym() or member.islnk():
link_target = os.path.join(os.path.dirname(target), member.linkname)
if not os.path.realpath(link_target).startswith(dest + os.sep):
return False
return True
# before calling untar_file / unpack_file
if not tar_is_safe_to_extract('pkg.tar.gz', '/tmp/extract'):
raise SecurityError('tar contains path-traversal entries') Type guard
import os
def is_safe_member_path(member_name: str, dest: str) -> bool:
target = os.path.abspath(os.path.join(dest, member_name))
return target == os.path.abspath(dest) or target.startswith(os.path.abspath(dest) + os.sep) Try / catch
from pip._internal.exceptions import InstallationError
try:
unpack_file(filename, location)
except InstallationError as e:
if 'trying to install outside target directory' in str(e):
# treat as malicious archive; do not retry
raise SecurityError(f'rejecting unsafe archive: {e}')
raise Prevention
- Only install packages from trusted indexes (official PyPI)
- Use Python 3.12+ so pip uses tarfile.data_filter for robust extraction
- Audit sdists from untrusted sources with 'tar -tf' before installing
- Run pip install in a container or sandbox to limit blast radius
When it happens
Trigger: A tar archive (sdist or source distribution) being unpacked contains a member whose name, after os.path.join with the location, resolves outside that location — e.g. a member named '../../etc/cron.d/evil'. The check is performed by is_within_directory() at line 293-300, both with and without symlink resolution.
Common situations: Installing a malicious or corrupted source distribution from an untrusted index. A typo-squatted or compromised package on PyPI containing a crafted tarball. Rarely, a legitimately misconfigured build tool that packs absolute paths into the tar.
Related errors
- Invalid member in the tar file
- Invalid script entry point name
- path outside destination: %r
- Path in pylock file obtained from a URL resolves outside…
- The wheel has a file trying to install outside the target…
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/c728b71955f496f3.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/utils/unpacking.py:300
# PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure.
# This feature is fully supported from CPython 3.12 onward.
for member in tar.getmembers():
fn = member.name
if leading:
fn = split_leading_dir(fn)[1]
path = os.path.join(location, fn)
# The plain check rejects textual ".." escapes; resolving symlinks also
# catches a later member redirected outside by an earlier member's
# symlink (e.g. "link/../file").
if not is_within_directory(location, path) or not is_within_directory(
location, path, resolve_symlinks=True
):
message = (
"The tar file ({}) has a file ({}) trying to install "
"outside target directory ({})"
)
raise InstallationError(message.format(filename, path, location))
if member.isdir():
ensure_dir(path)
elif member.issym():
# Reject symlinks resolving outside the destination, so a later
# member cannot be written through them.
target = os.path.join(os.path.dirname(path), member.linkname)
if not is_within_directory(location, target, resolve_symlinks=True):
message = (
"The tar file ({}) has a file ({}) trying to install "
"outside target directory ({})"
)
raise InstallationError(
message.format(filename, member.name, member.linkname)
)
if not is_symlink_target_in_tar(tar, member):
message = (
"The tar file ({}) has a file ({}) trying to install "
"outside target directory ({})"View on GitHub (pinned to f399c37189)