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
- Treat this as a security signal: do not whitelist; inspect the archive contents with `unzip -l file`.
- Re-download the artifact from the official index and verify its hash.
- If you control the archive, regenerate it without path-traversal entries.
- 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
- Only install from trusted, HTTPS indexes with hash verification.
- Scan sdists/wheels with `unzip -l` / `tar -tvf` in CI.
- Pin index URLs and use --require-hashes.
- Never extract user-supplied archives directly with pip on untrusted input.
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
- The wheel {wheel_path!r} has a file {target_path!r} trying t
- Path {path!r} in pylock file obtained from a URL resolves ou
- Invalid member in the tar file {}: {}
- The tar file ({}) has a file ({}) trying to install outside
- path outside destination: %r
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/ab7b442c0f9d607a.json.
Report an issue: GitHub.