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

Security InstallationError raised while unzipping an archive when a member resolves to a path outside the destination directory (a Zip Slip / path-traversal attempt). For each zip entry pip computes the joined target path and rejects it via is_within_directory before any file is written. This blocks archives containing entries like `../../etc/passwd`.

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 d7d0d0a394)

Solutions

  1. Treat this as a security signal: do not whitelist; inspect the archive contents with `unzip -l file`.
  2. Re-download the artifact from the official index and verify its hash.
  3. If you control the archive, regenerate it without path-traversal entries.
  4. Pin your index URL and verify packages over HTTPS.

Example fix

// before
unzip_file('evil.whl', '/target')  # member '../../pwned' escapes

// after
# reject the artifact; rebuild from trusted source:
python -m build && twine check dist/*
Defensive patterns

Strategy: validation

Validate before calling

import zipfile, os
def safe_to_unpack(zip_path: str, dest: str) -> bool:
    dest = os.path.realpath(dest)
    with zipfile.ZipFile(zip_path) as z:
        for name in z.namelist():
            target = os.path.realpath(os.path.join(dest, name))
            if not target.startswith(dest + os.sep) and target != dest:
                return False
    return True

Type guard

null

Try / catch

from pip._internal.exceptions import InstallationError
try:
    unzip_file(archive, dest)
except InstallationError as e:
    if 'outside target directory' in str(e):
        # security event: quarantine the archive
        raise
    raise

Prevention

When it happens

Trigger: Calling unzip_file (or unpacking a .zip/.whl/.egg via unpack_file) where an entry's name, after join with the destination, escapes the destination. Triggered by a malicious or malformed archive containing `../` sequences or absolute paths.

Common situations: Installing a malicious wheel/sdist from an untrusted index; a corrupted release artifact; CI pulling an archive from an untrusted mirror.

Related errors


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