{"record":{"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":"error","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/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_internal/utils/unpacking.py#L282-L318","documentation":"Raised by pip's tar extraction fallback path (_untar_without_filter, used on Python < 3.12 without tarfile.data_filter) when a tar member's resolved path escapes the destination directory. This is a security guard against path-traversal attacks where a malicious archive contains filenames with '..' sequences that would write outside the target install directory.","triggerScenarios":"A tar archive (sdist or source distribution) being unpacked contains a member whose name, after os.path.join with the location, resolves outside that location — e.g. a member named '../../etc/cron.d/evil'. The check is performed by is_within_directory() at line 293-300, both with and without symlink resolution.","commonSituations":"Installing a malicious or corrupted source distribution from an untrusted index. A typo-squatted or compromised package on PyPI containing a crafted tarball. Rarely, a legitimately misconfigured build tool that packs absolute paths into the tar.","solutions":["Inspect the tar file with 'tar -tf <file>' to identify the offending member path","Do not install the package if it contains path-traversal entries — report it to the index/maintainer","Reinstall from the official PyPI source or a trusted mirror to get a clean archive","Upgrade to Python 3.12+ so pip uses tarfile.data_filter for stronger, upstream-maintained extraction filtering"],"exampleFix":"// before\npip install suspicious-package==1.0\n// after\n# verify the archive is clean first\ntar -tf suspicious-package-1.0.tar.gz | grep '\\.\\.'\n# if clean, reinstall from official source\npip install --index-url https://pypi.org/simple/ suspicious-package==1.0","handlingStrategy":"validation","validationCode":"import tarfile, os\n\ndef tar_is_safe_to_extract(tar_path: str, dest: str) -> bool:\n    dest = os.path.abspath(dest)\n    with tarfile.open(tar_path) as tar:\n        for member in tar.getmembers():\n            target = os.path.join(dest, member.name)\n            if not os.path.abspath(target).startswith(dest + os.sep):\n                return False\n            if member.issym() or member.islnk():\n                link_target = os.path.join(os.path.dirname(target), member.linkname)\n                if not os.path.realpath(link_target).startswith(dest + os.sep):\n                    return False\n    return True\n\n# before calling untar_file / unpack_file\nif not tar_is_safe_to_extract('pkg.tar.gz', '/tmp/extract'):\n    raise SecurityError('tar contains path-traversal entries')","typeGuard":"import os\n\ndef is_safe_member_path(member_name: str, dest: str) -> bool:\n    target = os.path.abspath(os.path.join(dest, member_name))\n    return target == os.path.abspath(dest) or target.startswith(os.path.abspath(dest) + os.sep)","tryCatchPattern":"from pip._internal.exceptions import InstallationError\n\ntry:\n    unpack_file(filename, location)\nexcept InstallationError as e:\n    if 'trying to install outside target directory' in str(e):\n        # treat as malicious archive; do not retry\n        raise SecurityError(f'rejecting unsafe archive: {e}')\n    raise","preventionTips":["Only install packages from trusted indexes (official PyPI)","Use Python 3.12+ so pip uses tarfile.data_filter for robust extraction","Audit sdists from untrusted sources with 'tar -tf' before installing","Run pip install in a container or sandbox to limit blast radius"],"tags":["security","tarfile","path-traversal","extraction","installation"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}