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

    Software dependency graph and Python task workflow
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python graphlib: Topological Sorting

    Learn Python graphlib to order dependencies, detect cycles, and coordinate independent tasks safely in parallel.

    Ler mais

    Tempo de leitura: 5 minutos
    27/07/2026
    Python code for safe context in asynchronous applications
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python contextvars for Safe Context

    Learn how Python contextvars isolates request data across asyncio tasks, logs, threads, and tests without unsafe global variables.

    Ler mais

    Tempo de leitura: 7 minutos
    26/07/2026
    Python code using cached_property to store expensive calculations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python cached_property: Cache Object Values

    Learn Python cached_property to store expensive calculations, invalidate values, and prevent stale object caches.

    Ler mais

    Tempo de leitura: 6 minutos
    25/07/2026
    Python code with type-based function dispatch
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python singledispatch: Type-Based Functions

    Learn Python singledispatch to build type-based functions, reduce isinstance chains, and organize extensible polymorphism with clear examples.

    Ler mais

    Tempo de leitura: 6 minutos
    25/07/2026
    Python code demonstrating descriptors and attributes
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python Descriptors: Practical Guide

    Learn Python descriptors with __get__, __set__, validation, property, instance storage, testing, inheritance and practical design tips.

    Ler mais

    Tempo de leitura: 6 minutos
    22/07/2026
    Caixas empilhadas
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python Modules and Packages: Complete Guide

    Learn how to create Python modules and packages, organize imports, use __init__.py, run modules, avoid circular imports, and structure real

    Ler mais

    Tempo de leitura: 5 minutos
    10/07/2026