Safe TAR Extraction with Python tarfile

Published on: October 26, 2026
Reading time: 4 minutes
Secure document folder representing safe TAR extraction in Python

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.

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.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Dual monitors running Python code in parallel
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    concurrent.interpreters: Isolated Parallel Workers

    Learn Python concurrent.interpreters for isolated workers, explicit data transfer, and multicore parallelism.

    Ler mais

    Tempo de leitura: 6 minutos
    25/10/2026
    Python code with type annotations on a laptop
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: Read Annotations Safely

    Learn Python annotationlib to read annotations as values, strings, or forward references safely.

    Ler mais

    Tempo de leitura: 5 minutos
    25/10/2026
    ZIP file compression icon
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    ZipInfo.compress_level: Control ZIP Compression

    Learn Python ZipInfo.compress_level to choose compression per ZIP member and balance size, CPU, latency, and compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026
    Binary code representing Python bytearray.resize
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    bytearray.resize: Resize Buffers Safely

    Learn Python bytearray.resize for safe buffer growth and shrinking, limits, memoryview handling, and compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026
    Python code on a laptop for filtering None values
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    operator.is_none: Filter None Without Losing Falsy

    Learn operator.is_none and is_not_none to filter None values without removing valid falsy data in Python.

    Ler mais

    Tempo de leitura: 4 minutos
    23/10/2026
    Python code on a laptop for creating dynamic dataclasses
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    make_dataclass decorator: Transform Dataclasses

    Learn how the make_dataclass decorator parameter validates, registers, and transforms dynamic dataclasses safely in Python.

    Ler mais

    Tempo de leitura: 5 minutos
    23/10/2026