{"id":"c728b71955f496f3","repo":"pypa/pip","slug":"the-tar-file-has-a-file-trying-to-instal","errorCode":null,"errorMessage":"The tar file ({}) has a file ({}) trying to install outside target directory ({})","messagePattern":"The tar file \\((.+?)\\) has a file \\((.+?)\\) trying to install outside target directory \\((.+?)\\)","errorType":"exception","errorClass":"InstallationError","httpStatus":null,"severity":"critical","filePath":"src/pip/_internal/utils/unpacking.py","lineNumber":300,"sourceCode":"    # PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure.\n    # This feature is fully supported from CPython 3.12 onward.\n    for member in tar.getmembers():\n        fn = member.name\n        if leading:\n            fn = split_leading_dir(fn)[1]\n        path = os.path.join(location, fn)\n\n        # The plain check rejects textual \"..\" escapes; resolving symlinks also\n        # catches a later member redirected outside by an earlier member's\n        # symlink (e.g. \"link/../file\").\n        if not is_within_directory(location, path) or not is_within_directory(\n            location, path, resolve_symlinks=True\n        ):\n            message = (\n                \"The tar file ({}) has a file ({}) trying to install \"\n                \"outside target directory ({})\"\n            )\n            raise InstallationError(message.format(filename, path, location))\n        if member.isdir():\n            ensure_dir(path)\n        elif member.issym():\n            # Reject symlinks resolving outside the destination, so a later\n            # member cannot be written through them.\n            target = os.path.join(os.path.dirname(path), member.linkname)\n            if not is_within_directory(location, target, resolve_symlinks=True):\n                message = (\n                    \"The tar file ({}) has a file ({}) trying to install \"\n                    \"outside target directory ({})\"\n                )\n                raise InstallationError(\n                    message.format(filename, member.name, member.linkname)\n                )\n            if not is_symlink_target_in_tar(tar, member):\n                message = (\n                    \"The tar file ({}) has a file ({}) trying to install \"\n                    \"outside target directory ({})\"","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/utils/unpacking.py#L282-L318","documentation":"Path-traversal InstallationError from pip's legacy tar fallback (_untar_without_filter) used on Python versions without tarfile.data_filter. Raised when a member's resolved path is not inside the destination directory, either via textual `..` segments or via a symlink that later redirects a member outside. This is the Zip-Slip equivalent for tar on older Pythons.","triggerScenarios":"Untarring a tar archive on Python <3.12 (no data_filter) where a member name contains `..` or where the resolved real path escapes the destination (is_within_directory with resolve_symlinks=True returns False).","commonSituations":"Installing an sdist with a malicious or buggy member path on Python 3.8–3.11; symlink-redirect escape crafted to defeat a plain textual check.","solutions":["Treat as a security event: inspect the tar members and reject the artifact.","Upgrade Python to 3.12+ so pip can use the stricter data_filter path and give clearer messages.","Re-download and verify the hash from a trusted index.","Rebuild the archive without traversal/symlink escapes."],"exampleFix":"// before\n# python 3.10, archive has member 'sub/../../escape'\nuntar_file('pkg.tar.gz', '/target')\n\n// after\n# rebuild archive with normalized paths:\ntar -czf pkg.tar.gz --transform 's,^,pkg/,' -C src .","handlingStrategy":"validation","validationCode":"import tarfile, os\ndef members_within(path: str, dest: str) -> bool:\n    dest = os.path.realpath(dest)\n    with tarfile.open(path) as t:\n        for m in t.getmembers():\n            target = os.path.realpath(os.path.join(dest, m.name))\n            if os.path.commonpath([dest, target]) != dest:\n                return False\n    return True","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Upgrade to Python 3.12+ to use data_filter.","Use --require-hashes for sdist installs.","Audit package sources.","Avoid pinning to ancient Python/Pythons lacking PEP 706."],"tags":["pip","tar","security","path-traversal","symlink","legacy-python"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}