pypa/pip · critical · InstallationError

The zip file ( ) has a file ( ) trying to install outside…

Error message

The zip file ({}) has a file ({}) trying to install outside target directory ({})

What it means

Raised as InstallationError by unzip_file (unpacking.py:144) when a member of a zip archive would be extracted outside the target directory. This is the classic Zip-Slip / path-traversal protection: a malicious zip with entries like ../../etc/cron.d/evil could overwrite system files. The is_within_directory check at line 139 verifies every member's resolved path stays inside `location`; any violation raises before the file is written.

Solutions

  1. Verify the package source is trusted and the archive is not corrupted (re-download from the official index).
  2. Report the package to the index/maintainer if it contains suspicious traversal entries — it may be compromised.
  3. Avoid installing from untrusted direct URLs; prefer packages from vetted indices.
  4. If you control the archive, rebuild it without ../ or absolute paths in member names.
Defensive patterns

Strategy: validation

Validate before calling

import zipfile, os

def check_zip_for_traversal(zip_path: str, dest: str) -> list[str]:
    """Return list of zip members that would escape the destination directory."""
    dest = os.path.abspath(dest)
    dangerous = []
    with zipfile.ZipFile(zip_path) as zf:
        for name in zf.namelist():
            target = os.path.abspath(os.path.join(dest, name))
            if not target.startswith(dest + os.sep) and target != dest:
                dangerous.append(name)
    return dangerous

Type guard

import os

def is_within_directory(directory: str, target: str) -> bool:
    """True if target path stays within directory (no traversal)."""
    abs_dir = os.path.realpath(directory)
    abs_target = os.path.realpath(target)
    return abs_target == abs_dir or abs_target.startswith(abs_dir + os.sep)

Try / catch

from pip._internal.exceptions import InstallationError

try:
    # unzip / install operation
    pass
except InstallationError as e:
    if 'trying to install outside target directory' in str(e):
        # Zip-Slip detected: do NOT extract; report as security issue
        pass

Prevention

When it happens

Trigger: During installation of a sdist or archive, unzip_file iterates zip members, computes the extraction path fn at line 137, and checks is_within_directory(location, fn) at line 139. A member with ../ sequences or absolute paths that escape `location` triggers the raise.

Common situations: A malicious or buggy sdist package whose archive contains path-traversal entries. A corrupted download that altered zip member names. A package built with a broken build tool that generated invalid relative paths in the archive.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/ab7b442c0f9d607a. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_internal/utils/unpacking.py:144

    """
    ensure_dir(location)
    zipfp = open(filename, "rb")
    try:
        zip = zipfile.ZipFile(zipfp, allowZip64=True)
        leading = has_leading_dir(zip.namelist()) and flatten
        for info in zip.infolist():
            name = info.filename
            fn = name
            if leading:
                fn = split_leading_dir(name)[1]
            fn = os.path.join(location, fn)
            dir = os.path.dirname(fn)
            if not is_within_directory(location, fn):
                message = (
                    "The zip file ({}) has a file ({}) trying to install "
                    "outside target directory ({})"
                )
                raise InstallationError(message.format(filename, fn, location))
            if fn.endswith(("/", "\\")):
                # A directory
                ensure_dir(fn)
            else:
                ensure_dir(dir)
                # Don't use read() to avoid allocating an arbitrarily large
                # chunk of memory for the file's content
                fp = zip.open(name)
                try:
                    with open(fn, "wb") as destfp:
                        shutil.copyfileobj(fp, destfp)
                finally:
                    fp.close()
                    if zip_item_is_executable(info):
                        set_extracted_file_to_default_mode_plus_executable(fn)
    finally:
        zipfp.close()

View on GitHub (pinned to f399c37189)