pypa/pip · error · InstallationError
The wheel {!r} has a file {!r} trying to install outside the
Error message
The wheel {!r} has a file {!r} trying to install outside the target directory {!r} What it means
Raised as InstallationError by assert_no_path_traversal when a file extracted from a wheel resolves, after os.path.normpath/join, to a destination outside its target install directory. This blocks zip-slip / path-traversal attacks where archive entries use '..' or absolute paths to write outside the scheme directory.
Source
Thrown at src/pip/_internal/operations/install/wheel.py:501
def record_installed(
srcfile: RecordPath, destfile: str, modified: bool = False
) -> None:
"""Map archive RECORD paths to installation RECORD paths."""
newpath = _fs_to_record_path(destfile, lib_dir)
installed[srcfile] = newpath
if modified:
changed.add(newpath)
def is_dir_path(path: RecordPath) -> bool:
return path.endswith("/")
def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None:
if not is_within_directory(dest_dir_path, target_path):
message = (
"The wheel {!r} has a file {!r} trying to install"
" outside the target directory {!r}"
)
raise InstallationError(
message.format(wheel_path, target_path, dest_dir_path)
)
def root_scheme_file_maker(
zip_file: ZipFile, dest: str
) -> Callable[[RecordPath], File]:
def make_root_scheme_file(record_path: RecordPath) -> File:
normed_path = os.path.normpath(record_path)
dest_path = os.path.join(dest, normed_path)
assert_no_path_traversal(dest, dest_path)
return ZipBackedFile(record_path, dest_path, zip_file)
return make_root_scheme_file
def data_scheme_file_maker(
zip_file: ZipFile, scheme: Scheme
) -> Callable[[RecordPath], File]:
scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}View on GitHub (pinned to f399c37189)
Solutions
- Do not install the offending wheel; treat it as untrusted.
- Rebuild the wheel with a standard build backend (setuptools/hatchling/flit/wheel) that emits sane paths.
- Report the issue to the wheel author/maintainer.
- Verify the wheel's hash/signature against the publisher.
Defensive patterns
Strategy: validation
Validate before calling
import os
def is_within_directory(directory: str, target: str) -> bool:
abs_dir = os.path.realpath(directory)
abs_tgt = os.path.realpath(target)
return os.path.commonpath([abs_dir]) == os.path.commonpath([abs_dir, abs_tgt])
def wheel_paths_safe(record_paths, target: str) -> bool:
return all(is_within_directory(target, os.path.join(target, p)) for p in record_paths) Prevention
- Verify wheel hashes/signatures against the publisher before installing.
- Build wheels with standard backends that emit clean relative paths.
- Scan untrusted wheels for '..' or absolute paths before install.
When it happens
Trigger: The wheel archive contains a RECORD path whose normalized destination escapes the lib_dir or scheme path; is_within_directory(dest_dir_path, target_path) is false for root-scheme or data-scheme files.
Common situations: Malicious or corrupted wheel; buggy wheel-building tool that emitted absolute or parent-referencing paths; tampered artefact from an untrusted index.
Related errors
- Invalid script entry point name {entry.name!r}: the script w
- The zip file ({}) has a file ({}) trying to install outside
- path outside destination: %r
- Path {path!r} in pylock file obtained from a URL resolves ou
- Invalid member in the tar file {}: {}
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/b92d637c23de0135.
Report an issue: GitHub.