Python zipfile: Safe ZIP Archives

Published on: July 27, 2026
Reading time: 4 minutes
ZIP archive icon for a Python zipfile article

The Python zipfile module is part of the standard library and provides tools to create, read, inspect, and extract ZIP archives without third-party packages. It is useful for backups, report exports, document delivery, upload processing, dataset packaging, and automation that needs to bundle many files into one portable container.

The API is compact, but a dependable production workflow must still handle internal names, total size, duplicate entries, integrity, and extraction paths. This guide explains how to use ZipFile, choose opening modes, write in-memory content, read files without extracting everything, inspect metadata, and apply practical validation. The subject also complements other standard-library topics such as graphlib for dependencies, contextvars for safe context, heapq priority queues, and singledispatch for extensible APIs.

Opening a ZIP archive

The main class is zipfile.ZipFile. Mode r opens an existing archive for reading, w creates or replaces one, a appends content, and x creates a new archive while failing if the destination already exists.

from zipfile import ZipFile

with ZipFile("data.zip", "r") as archive:
    print(archive.namelist())

Always prefer a context manager. ZIP files maintain a central directory containing member metadata, and an archive that is not closed correctly may remain incomplete.

Creating an archive

Use write() to add a file from disk. The arcname argument determines the internal name stored in the archive.

from zipfile import ZIP_DEFLATED, ZipFile

with ZipFile("backup.zip", "w", compression=ZIP_DEFLATED) as archive:
    archive.write("reports/sales.csv", arcname="sales.csv")
    archive.write("config/app.json", arcname="config/app.json")

Choose concise, portable internal names. Avoid absolute paths and unnecessary server-specific directories because they make archives harder to move and may reveal local structure.

Writing in-memory content

When data already exists as text or bytes, writestr() avoids creating a temporary file.

from zipfile import ZIP_DEFLATED, ZipFile

content = "id,name\n1,Ana\n2,Caio\n"
with ZipFile("export.zip", "w", ZIP_DEFLATED) as archive:
    archive.writestr("customers.csv", content)

This works well for small reports generated on demand. For very large outputs, use temporary files or chunked processing so that the entire result does not remain in memory.

Reading without extraction

The read() method returns a member as bytes. The open() method returns a file-like object and supports incremental processing.

from zipfile import ZipFile

with ZipFile("data.zip") as archive:
    with archive.open("config.json") as source:
        text = source.read().decode("utf-8")
        print(text)

This is useful when passing content directly to a JSON, CSV, XML, or text parser without writing another copy to disk.

Inspecting archive members

infolist() returns ZipInfo objects containing the member name, original size, compressed size, timestamp, and compression method.

with ZipFile("upload.zip") as archive:
    for item in archive.infolist():
        print(item.filename, item.file_size, item.compress_size)

Inspect metadata before extraction. Applications can reject unsupported extensions, too many entries, oversized members, or suspicious compression ratios.

Validating extraction paths

A member name should be treated as external input. Resolve the final destination and confirm that it remains under the approved root directory.

from pathlib import Path
from zipfile import ZipFile

root = Path("received").resolve()
with ZipFile("upload.zip") as archive:
    for item in archive.infolist():
        destination = (root / item.filename).resolve()
        if root not in destination.parents and destination != root:
            raise ValueError(f"Invalid archive path: {item.filename}")
        archive.extract(item, root)

Also reject absolute names, unnecessary parent components, and file types that your application does not need.

Applying resource limits

A small compressed file can expand into a much larger amount of data. Define a maximum per member and a maximum for the complete archive.

MAX_FILE = 50 * 1024 * 1024
MAX_TOTAL = 200 * 1024 * 1024

total = 0
for item in archive.infolist():
    if item.file_size > MAX_FILE:
        raise ValueError("Archive member is too large")
    total += item.file_size
    if total > MAX_TOTAL:
        raise ValueError("Archive exceeds the total limit")

Count the bytes actually copied as well. This allows the application to stop if the declared metadata does not match the stream being processed.

Checking integrity

testzip() reads archive members and checks their CRC. It returns the first problematic filename or None when no issue is found.

from zipfile import BadZipFile, ZipFile

try:
    with ZipFile("archive.zip") as archive:
        damaged = archive.testzip()
        if damaged:
            raise ValueError(f"Corrupted member: {damaged}")
except BadZipFile:
    print("The file is not a valid ZIP archive")

In an API, log technical details internally while returning a simple error message to the client.

Choosing a compression method

ZIP_DEFLATED is broadly compatible and suitable for most text and structured data. BZIP2 and LZMA may compress some content more effectively, but older tools may not support them. JPEG images, videos, and already compressed files usually show little improvement.

The compresslevel parameter controls the trade-off between speed and size. Measure actual data before increasing the level in high-volume jobs.

Handling duplicate names

An archive can contain multiple entries with the same name. Different tools may choose different entries, so normalize and track names before extraction.

seen = set()
for item in archive.infolist():
    name = item.filename.replace("\\", "/").casefold()
    if name in seen:
        raise ValueError(f"Duplicate archive name: {item.filename}")
    seen.add(name)

Consider case differences, slash styles, and equivalent Unicode representations when archives move between operating systems.

Controlled extraction

For external archives, inspect and copy one member at a time. This makes it possible to enforce limits, report progress, and remove partial files after a failure.

import shutil

for item in archive.infolist():
    destination = root / item.filename
    if item.is_dir():
        destination.mkdir(parents=True, exist_ok=True)
        continue
    destination.parent.mkdir(parents=True, exist_ok=True)
    with archive.open(item) as source, destination.open("wb") as target:
        shutil.copyfileobj(source, target, length=1024 * 1024)

Password-protected archives

The standard library can read some traditionally encrypted ZIP files, but it is not the right choice for modern confidentiality requirements. Sensitive information should use current encryption tools, sound key management, and an appropriate threat model.

Testing the workflow

Include tests for empty archives, missing files, invalid ZIP data, duplicate names, directories, Unicode filenames, large members, total-size limits, and invalid destination paths. Verify that partial outputs are removed when an exception occurs.

  • Use with for every archive operation.
  • Set deliberate internal names with arcname.
  • Validate final paths before writing files.
  • Limit member count, individual size, and total size.
  • Reject duplicate names and unsupported extensions.
  • Check integrity when correctness matters.
  • Extract into an isolated temporary directory.
  • Never execute extracted files automatically.
  • Log errors and remove incomplete results.
  • Test both normal and malformed archives.

Conclusion

Python zipfile covers the most common requirements for building and consuming ZIP archives. A production implementation should combine the basic API with metadata inspection, resource limits, path validation, and controlled extraction.

By treating every archive member as external input and verifying the destination before writing, applications become more predictable and resilient. Consult the official zipfile documentation and the OWASP guidance on safe extraction for additional details.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A vibrant array of colored thread spools neatly organized in rows, perfect for sewing enthusiasts.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python queue: Coordinate Threads

    Learn Python queue to coordinate threads with FIFO, priority, backpressure, task tracking, retries, and graceful shutdown.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Extreme close-up of computer code displaying various programming terms and elements.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python struct: Pack Binary Data

    Learn Python struct to pack binary values, define endianness, reuse buffers, parse records, and validate external protocols safely.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tarfile: Create Safe TARs

    Learn Python tarfile to create compressed TARs, inspect members, and extract archives with filters, limits, and path traversal protection.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    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