pypa/pip · critical · InstallationError
The tar file ({}) has a file ({}) trying to install outside
Error message
The tar file ({}) has a file ({}) trying to install outside target directory ({}) What it means
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.
Source
Thrown at src/pip/_internal/utils/unpacking.py:300
# PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure.
# This feature is fully supported from CPython 3.12 onward.
for member in tar.getmembers():
fn = member.name
if leading:
fn = split_leading_dir(fn)[1]
path = os.path.join(location, fn)
# The plain check rejects textual ".." escapes; resolving symlinks also
# catches a later member redirected outside by an earlier member's
# symlink (e.g. "link/../file").
if not is_within_directory(location, path) or not is_within_directory(
location, path, resolve_symlinks=True
):
message = (
"The tar file ({}) has a file ({}) trying to install "
"outside target directory ({})"
)
raise InstallationError(message.format(filename, path, location))
if member.isdir():
ensure_dir(path)
elif member.issym():
# Reject symlinks resolving outside the destination, so a later
# member cannot be written through them.
target = os.path.join(os.path.dirname(path), member.linkname)
if not is_within_directory(location, target, resolve_symlinks=True):
message = (
"The tar file ({}) has a file ({}) trying to install "
"outside target directory ({})"
)
raise InstallationError(
message.format(filename, member.name, member.linkname)
)
if not is_symlink_target_in_tar(tar, member):
message = (
"The tar file ({}) has a file ({}) trying to install "
"outside target directory ({})"View on GitHub (pinned to d7d0d0a394)
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.
Example fix
// before
# python 3.10, archive has member 'sub/../../escape'
untar_file('pkg.tar.gz', '/target')
// after
# rebuild archive with normalized paths:
tar -czf pkg.tar.gz --transform 's,^,pkg/,' -C src . Defensive patterns
Strategy: validation
Validate before calling
import tarfile, os
def members_within(path: str, dest: str) -> bool:
dest = os.path.realpath(dest)
with tarfile.open(path) as t:
for m in t.getmembers():
target = os.path.realpath(os.path.join(dest, m.name))
if os.path.commonpath([dest, target]) != dest:
return False
return True Type guard
null
Try / catch
null
Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Invalid member in the tar file {}: {}
- {path} is a symlink; Will not return uid for symlinks
- Path {path!r} in pylock file obtained from a URL resolves ou
- The zip file ({}) has a file ({}) trying to install outside
- path outside destination: %r
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/c728b71955f496f3.json.
Report an issue: GitHub.