{"id":"5e3c941d82282d43","repo":"pypa/pip","slug":"the-wheel-wheel-path-r-has-a-file-target-path-r","errorCode":null,"errorMessage":"The wheel {wheel_path!r} has a file {target_path!r} trying to install outside the target directory {dest_dir_path!r}","messagePattern":"The wheel (.+?) has a file (.+?) trying to install outside the target directory (.+?)","errorType":"exception","errorClass":"InstallationError","httpStatus":null,"severity":"critical","filePath":"src/pip/_internal/operations/install/wheel.py","lineNumber":495,"sourceCode":"    def record_installed(\n        srcfile: RecordPath, destfile: str, modified: bool = False\n    ) -> None:\n        \"\"\"Map archive RECORD paths to installation RECORD paths.\"\"\"\n        newpath = _fs_to_record_path(destfile, lib_dir)\n        installed[srcfile] = newpath\n        if modified:\n            changed.add(newpath)\n\n    def is_dir_path(path: RecordPath) -> bool:\n        return path.endswith(\"/\")\n\n    def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None:\n        if not is_within_directory(dest_dir_path, target_path):\n            message = (\n                \"The wheel {!r} has a file {!r} trying to install\"\n                \" outside the target directory {!r}\"\n            )\n            raise InstallationError(\n                message.format(wheel_path, target_path, dest_dir_path)\n            )\n\n    def root_scheme_file_maker(\n        zip_file: ZipFile, dest: str\n    ) -> Callable[[RecordPath], File]:\n        def make_root_scheme_file(record_path: RecordPath) -> File:\n            normed_path = os.path.normpath(record_path)\n            dest_path = os.path.join(dest, normed_path)\n            assert_no_path_traversal(dest, dest_path)\n            return ZipBackedFile(record_path, dest_path, zip_file)\n\n        return make_root_scheme_file\n\n    def data_scheme_file_maker(\n        zip_file: ZipFile, scheme: Scheme\n    ) -> Callable[[RecordPath], File]:\n        scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/operations/install/wheel.py#L477-L513","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before (wheel RECORD contains)\n../../etc/cron.d/payload\n\n# after\npayload/__init__.py","handlingStrategy":"validation","validationCode":"import zipfile, os\n\ndef validate_wheel_paths(whl):\n    with zipfile.ZipFile(whl) as z:\n        for info in z.infolist():\n            norm = os.path.normpath(info.filename)\n            if os.path.isabs(norm) or norm.startswith(\"..\") or f\"{os.sep}..\" in norm:\n                raise ValueError(f\"wheel {whl} contains unsafe path: {info.filename}\")","typeGuard":"import os\ndef path_is_within(path: str, base: str) -> bool:\n    ap = os.path.abspath(path)\n    ab = os.path.abspath(base)\n    return ap == ab or ap.startswith(ab + os.sep)","tryCatchPattern":null,"preventionTips":["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."],"tags":["wheel","path-traversal","security","install","zip-slip"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}