TAR archives are common in backups, software packages, data pipelines, and deployment workflows. Python’s built-in tarfile module can create, inspect, and extract .tar, .tar.gz, .tar.bz2, and .tar.xz files without third-party dependencies. Extraction, however, deserves careful handling. A malicious archive may contain paths that escape the selected directory, links that point outside it, unusual file types, or a huge amount of uncompressed data. This guide shows how to use extraction filters, inspect members, enforce limits, and build a reusable safe extraction function.
Why TAR extraction can be risky
A TAR member contains more than file bytes. It can also store a name, permissions, ownership information, symbolic links, hard links, device entries, and other metadata. If an application trusts every field, a member named ../../settings.py may attempt to write outside the extraction root. Absolute paths can cause a similar problem. Symbolic links may redirect later writes to unexpected locations.
The safest assumption is that every externally supplied archive is untrusted. That includes uploads from authenticated users, files downloaded from a vendor, email attachments, and archives copied from another internal service. Validation should happen before extraction rather than after damage has already occurred.
Use extraction filters
Modern Python versions support a filter argument in extract() and extractall(). The data filter is usually the best choice for ordinary application data because it limits dangerous metadata and rejects unsafe situations. The tar filter keeps more archive semantics. The permissive fully_trusted behavior should be reserved for archives that you created and completely control.
from pathlib import Path
import tarfile
archive = Path("dataset.tar.gz")
destination = Path("extracted")
destination.mkdir(parents=True, exist_ok=True)
with tarfile.open(archive, "r:*") as tar:
tar.extractall(destination, filter="data")
The r:* mode automatically detects the compression format. The data filter works well for CSV, JSON, text files, images, and similar assets that do not need to preserve advanced ownership or link metadata.
Inspect members before writing files
getmembers() returns TarInfo objects. Each object exposes the member name, size, type, mode, and link information. Inspection lets you reject an archive before any content reaches disk. Useful policies include a maximum number of members, a maximum total uncompressed size, an allowlist of extensions, and a ban on special file types.
MAX_FILES = 500
MAX_SIZE = 100 * 1024 * 1024
ALLOWED = {".csv", ".json", ".txt", ".png", ".jpg"}
with tarfile.open("dataset.tar.gz", "r:*") as tar:
members = tar.getmembers()
if len(members) > MAX_FILES:
raise ValueError("Too many archive members")
total = sum(member.size for member in members if member.isfile())
if total > MAX_SIZE:
raise ValueError("Uncompressed content exceeds the limit")
for member in members:
if member.isfile() and Path(member.name).suffix.lower() not in ALLOWED:
raise ValueError(f"Blocked extension: {member.name}")
tar.extractall("extracted", filter="data")
Always enforce limits on the uncompressed size, not only on the uploaded archive size. Compression can hide a very large output and exhaust storage, processing time, or quotas.
Verify the final path
If you need additional rules or compatibility with older environments, resolve every candidate path against the destination root and verify that it remains inside that root.
def is_safe_path(root: Path, member_name: str) -> bool:
root = root.resolve()
candidate = (root / member_name).resolve()
try:
candidate.relative_to(root)
return True
except ValueError:
return False
A simple check for the text .. is not enough. Absolute paths, platform-specific separators, link behavior, and path normalization can bypass superficial string checks. Comparing resolved paths makes the policy easier to understand and test.
Reject links and special entries when unnecessary
Most upload workflows need only directories and regular files. TarInfo provides helpers such as isfile(), isdir(), issym(), and islnk(). A strict application can reject symbolic links, hard links, devices, FIFOs, and other special entries.
for member in tar.getmembers():
if not (member.isfile() or member.isdir()):
raise ValueError(f"Unsupported member type: {member.name}")
This policy reduces complexity and makes the extracted directory predictable. If your application truly needs links, validate both the link itself and its target instead of enabling every archive feature globally.
Build a reusable extraction function
from pathlib import Path
import tarfile
def extract_tar_safely(source, destination, size_limit=200_000_000):
source = Path(source)
destination = Path(destination)
destination.mkdir(parents=True, exist_ok=True)
with tarfile.open(source, "r:*") as tar:
members = tar.getmembers()
total = sum(m.size for m in members if m.isfile())
if total > size_limit:
raise ValueError("Extraction size limit exceeded")
for member in members:
if not is_safe_path(destination, member.name):
raise ValueError(f"Unsafe path: {member.name}")
if not (member.isfile() or member.isdir()):
raise ValueError(f"Special member blocked: {member.name}")
tar.extractall(destination, members=members, filter="data")
In a web application, run this function in a dedicated temporary directory. The guide to temporary files with Python helps with isolated workspaces. For path handling, see pathlib in Python. You may also review using with for files and safe ZIP extraction.
Handle failures and partial output
Catch relevant exceptions such as tarfile.ReadError, tarfile.CompressionError, other TarError subclasses, and OSError. If extraction fails, delete the incomplete directory. For important workflows, extract into a staging directory, validate the final tree, and then move approved files into their permanent location.
try:
extract_tar_safely("upload.tar.gz", "staging")
except (tarfile.TarError, OSError, ValueError) as error:
print(f"Extraction failed: {error}")
Logging the archive identifier, blocked member name, and validation rule helps troubleshooting without exposing sensitive paths to end users.
Create TAR archives safely
When producing an archive, select w, w:gz, w:bz2, or w:xz. Use arcname to control the internal path and avoid recording absolute filesystem locations.
with tarfile.open("backup.tar.gz", "w:gz") as tar:
tar.add("reports", arcname="reports")
For a broader workflow, combine this approach with the automatic backup guide.
Security checklist
Use filter="data", validate resolved paths, limit member count and uncompressed size, reject unnecessary types, extract into an isolated directory, clean up partial output, log validation failures, and keep Python updated. Consult the official tarfile documentation and the OWASP path traversal guidance. With these layers, tarfile remains a practical standard-library tool for imports, backups, and automated processing without blindly trusting archive contents.







