Python tarfile: Create Safe TARs

Published on: August 17, 2026
Reading time: 4 minutes
Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.

The Python tarfile module reads, writes, and extracts TAR archives, including gzip, bzip2, XZ, and, in Python 3.14, Zstandard compression when available. Unlike GZIP, which represents one stream, TAR is a container that preserves paths, directories, permissions, timestamps, links, and other filesystem metadata.

Those features also create risk. A hostile archive may attempt to write outside the destination, create dangerous links or device files, or exhaust storage with many members. Since Python 3.14, the default extraction filter is data, which is safer than the previous fully trusted behavior. It still does not prevent every denial-of-service scenario.

When to use tarfile

Use TAR to package directories, backups, source trees, and Unix artifacts. ZIP is often easier for desktop users; see the Python ZIP guide. For a single compressed stream, use Python gzip.

Create an uncompressed TAR

import tarfile

with tarfile.open("project.tar", "x") as tar:
    tar.add("src", arcname="src")
    tar.add("README.md", arcname="README.md")

Exclusive mode fails if the destination exists. arcname controls the stored path and prevents absolute local directory names from leaking into the archive.

Create compressed TAR files

import tarfile

with tarfile.open("project.tar.gz", "x:gz", compresslevel=6) as tar:
    tar.add("src", arcname="src")

with tarfile.open("data.tar.xz", "x:xz", preset=6) as tar:
    tar.add("data", arcname="data")

Use modes such as w:gz, w:bz2, w:xz, and w:zst. For reading, r:* detects compression automatically. Compressed archives cannot be appended with the ordinary append mode; create a replacement archive instead.

Inspect members first

import tarfile

with tarfile.open("project.tar.gz", "r:*") as tar:
    for member in tar:
        print(member.name, member.size, member.type)

Each entry is a TarInfo. Before extraction, inspect names, types, sizes, link targets, and duplicates. Helpers such as isfile(), isdir(), issym(), islnk(), and isdev() simplify classification.

Safe extraction in Python 3.14

The default is now the data filter, but specifying it explicitly also protects code running on versions where the old default may still apply:

import tarfile
from pathlib import Path

source = Path("upload.tar.gz")
destination = Path("extracted").resolve()
destination.mkdir(parents=True, exist_ok=False)

with tarfile.open(source, "r:*") as tar:
    tar.extractall(destination, filter="data")

The filter rejects absolute paths, paths escaping the destination, external links, and special files. It also reduces permissions and ignores stored owner information.

The data filter is not a complete sandbox

An archive may still contain millions of members, huge files, long names, duplicates, case-insensitive collisions, or payloads that consume disk and CPU. Extract into a new temporary directory, enforce OS-level quotas, and delete the entire directory after a failure.

Limit count and declared size

import tarfile

MAX_FILES = 5_000
MAX_TOTAL = 2 * 1024**3

def accepted_members(tar):
    total = 0
    for index, member in enumerate(tar, start=1):
        if index > MAX_FILES:
            raise ValueError("too many members")
        if member.size < 0:
            raise ValueError("invalid size")
        total += member.size
        if total > MAX_TOTAL:
            raise ValueError("expanded size exceeded the limit")
        if member.isdev() or member.isfifo():
            continue
        yield member

with tarfile.open("upload.tar", "r:*") as tar:
    tar.extractall("destination", members=accepted_members(tar), filter="data")

Header sizes can also be malicious. Combine this check with filesystem quotas and process isolation.

import tarfile

def regular_data(member, path):
    member = tarfile.data_filter(member, path)
    if member is None:
        return None
    if member.issym() or member.islnk():
        return None
    return member

with tarfile.open("upload.tar.gz", "r:*") as tar:
    tar.extractall("destination", filter=regular_data)

A custom filter may return a modified TarInfo, return None, or raise an exception.

Read one member without extraction

import tarfile
import json

with tarfile.open("package.tar.gz", "r:*") as tar:
    member = tar.getmember("manifest.json")
    if not member.isfile() or member.size > 1_000_000:
        raise ValueError("invalid manifest")
    with tar.extractfile(member) as file:
        manifest = json.load(file)

This is preferable when only a manifest, configuration, or signature is needed.

Control names while archiving

tar.add() otherwise stores the supplied local path. Always use arcname to produce a portable layout and avoid disclosing server directories.

Filter and normalize during creation

import tarfile

def prepare(info):
    if info.name.endswith((".env", ".key")):
        return None
    return info.replace(
        uid=0, gid=0, uname="root", gname="root", mtime=0
    )

with tarfile.open("source.tar.gz", "x:gz", compresslevel=6) as tar:
    tar.add("project", arcname="project", filter=prepare)

Normalizing ownership and timestamps helps reproducible builds. Exclude secrets, caches, virtual environments, and temporary files.

Archive formats

  • USTAR_FORMAT: old and widely supported, with name and size limits.
  • GNU_FORMAT: GNU extensions for long names and large files.
  • PAX_FORMAT: current default, flexible, and UTF-8 friendly.

PAX is usually best for new archives.

Streaming modes

Modes such as r|gz and w|gz process blocks sequentially without random access, which works with stdin, stdout, sockets, and pipes:

import sys
import tarfile

with tarfile.open(fileobj=sys.stdout.buffer, mode="w|gz", compresslevel=6) as tar:
    tar.add("result", arcname="result")

In stream mode, process each member when it arrives because earlier entries cannot be revisited.

Partial extraction after errors

extractall() does not roll back files already written. Extract to an isolated temporary directory, validate the completed tree, and move it into place only after success.

Important exceptions

Handle ReadError, CompressionError, StreamError, and FilterError subclasses. Avoid setting errorlevel=0 for untrusted archives because refused members may be logged and skipped while extraction continues.

Testing

Include absolute paths, ../, links outside the destination, devices, FIFOs, duplicates, excessive member counts, huge files, Unicode names, truncated archives, and every supported compression. Test on Windows and Linux when portability matters.

Best practices

  • Read with r:*.
  • Create with exclusive mode.
  • Set arcname.
  • Extract with filter="data".
  • Reject links unless required.
  • Limit members, size, names, disk, and CPU.
  • Use a new temporary destination.
  • Normalize metadata when creating.
  • Never assume a TAR is trusted.

Conclusion

Python tarfile is a complete tool for packaging filesystem trees and combining TAR with gzip, bzip2, XZ, or Zstandard. Python 3.14 improved the default, but applications still need explicit limits and isolation.

Read the official tarfile documentation and PEP 706. To monitor large extractions, see Python tracemalloc and Python trace.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Row of colorful office binders neatly arranged on a shelf, ideal for organization concepts.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python gzip: Compress .gz Files

    Learn Python gzip to read and write .gz files, produce reproducible streams, process large data, and limit external expansion.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Neatly arranged blue office binders labeled with dates and names for organized storage.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python lzma: Compress XZ Files

    Learn Python lzma to create XZ files, process streams, select checks and filters, and enforce memory limits on external data.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Detailed close-up texture of a snake's patterned skin showcasing natural patterns and scales.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python bz2: Compress with bzip2

    Learn Python bz2 to compress files and bytes, process data incrementally, handle concatenated streams, and limit external expansion.

    Ler mais

    Tempo de leitura: 4 minutos
    16/08/2026
    Close-up of a computer screen displaying colorful programming code with depth of field.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zlib: Compress Data Safely

    Learn Python zlib to compress and decompress bytes, process streams, use checksums and dictionaries, and limit external data safely.

    Ler mais

    Tempo de leitura: 5 minutos
    16/08/2026
    File and system diagram representing INI configuration with Python configparser
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python configparser: Read INI Files

    Learn Python configparser to read and write INI files, layer defaults, use interpolation and converters, and update files safely.

    Ler mais

    Tempo de leitura: 5 minutos
    16/08/2026
    RAM module representing object memory management with Python gc
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python gc: Control Garbage Collection

    Learn Python gc to control cyclic collection, inspect tracked objects, diagnose memory growth, and observe collection pauses.

    Ler mais

    Tempo de leitura: 6 minutos
    16/08/2026