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
- Verify the package source is trusted and the archive is not corrupted (re-download from the official index).
- Report the package to the index/maintainer if it contains suspicious traversal entries — it may be compromised.
- Avoid installing from untrusted direct URLs; prefer packages from vetted indices.
- 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
- Only install packages from trusted indices (PyPI) that scan for malicious archives.
- Pre-validate downloaded archives for path traversal before extraction.
- Prefer wheels over sdists when available — wheels are less prone to traversal issues.
- Treat Zip-Slip detection as a security incident, not a routine error.
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
- Invalid member in the tar file
- path outside destination: %r
- The wheel has a file trying to install outside the target…
- Invalid script entry point name
- Path in pylock file obtained from a URL resolves outside…
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)