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

    Developer configuring a Python HTTPS server and TLS certificate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: Build a Local HTTPS Server in Python

    Learn Python HTTPSServer for local HTTPS services, TLS certificates, threaded handling, testing, and practical security limits.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Python code showing warnings controlled with catch_warnings
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: Capture Python Warnings in Tests

    Learn Python catch_warnings to capture, test, and control warnings with focused filters and safe temporary scopes.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Python code representing persistent pickle references
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: Serialize External References

    Learn Python pickle persistent_id for stable external references, validation, security, performance, and long-term compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Binary code representing Python buffers and memory views
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: Count Values Without Buffer Copies

    Learn Python memoryview.count to count bytes and values in buffers without copies, with formats, limits, and practical safety.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026
    Python code and type annotations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: Avoid Circular Imports in Annotations

    Learn Python annotationlib to inspect deferred annotations, avoid circular imports, and build safer runtime tooling.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026