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

  1. Inspect the tar file with 'tar -tf <file>' to identify the offending member path
  2. Do not install the package if it contains path-traversal entries — report it to the index/maintainer
  3. Reinstall from the official PyPI source or a trusted mirror to get a clean archive
  4. 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

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


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)