pypa/pip · critical · InstallationError
The wheel {wheel_path!r} has a file {target_path!r} trying t
Error message
The wheel {wheel_path!r} has a file {target_path!r} trying to install outside the target directory {dest_dir_path!r} What it means
Raised as InstallationError by assert_no_path_traversal() (wheel.py:489-497) when a file inside a wheel resolves to a destination outside its target directory. The check uses is_within_directory(dest_dir_path, target_path) for every root-scheme and data-scheme file before it is written, refusing to write anything that escapes the install prefix.
Source
Thrown at src/pip/_internal/operations/install/wheel.py:495
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 d7d0d0a394)
Solutions
- List the wheel contents and look for absolute or parent-traversal paths: python -c "import zipfile; zipfile.ZipFile('pkg.whl').printdir()".
- Do not install the offending wheel; report it to upstream maintainers.
- Pin to a known-good version of the package that has clean record paths.
- If you built the wheel yourself, fix the build to emit only paths relative to the scheme root.
Example fix
# before (wheel RECORD contains) ../../etc/cron.d/payload # after payload/__init__.py
Defensive patterns
Strategy: validation
Validate before calling
import zipfile, os
def validate_wheel_paths(whl):
with zipfile.ZipFile(whl) as z:
for info in z.infolist():
norm = os.path.normpath(info.filename)
if os.path.isabs(norm) or norm.startswith("..") or f"{os.sep}.." in norm:
raise ValueError(f"wheel {whl} contains unsafe path: {info.filename}") Type guard
import os
def path_is_within(path: str, base: str) -> bool:
ap = os.path.abspath(path)
ab = os.path.abspath(base)
return ap == ab or ap.startswith(ab + os.sep) Prevention
- Scan third-party wheels for path traversal before installing in production.
- Build wheels with modern setuptools/hatchling that guarantee safe paths.
- Run dependency audit tools (e.g. pip-audit) in CI.
When it happens
Trigger: A wheel archive contains a member whose record path (after os.path.normpath and os.path.join) resolves above the target lib/scripts/include directory, e.g. '../../etc/cron.d/evil' or an absolute path. Encountered via root_scheme_file_maker or data_scheme_file_maker.
Common situations: A malicious or misbuilt wheel (Zip Slip vulnerability). Common in security audits of third-party packages. Rare in well-formed wheels built by modern setuptools.
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
- Invalid script entry point: {entry_point} - A callable suffi
- Unexpected file in {wheel_path}: {record_path!r}. .data dire
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/5e3c941d82282d43.json.
Report an issue: GitHub.