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

    Locked folder representing file types and permissions with Python stat
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python stat: File Types and Permissions

    Learn Python stat to interpret file types, permissions, links, timestamps, Windows attributes, and Unix flags safely and portably.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Laptop with code representing automatic documentation with Python pydoc
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pydoc: Automatic Documentation

    Learn Python pydoc to generate terminal help, HTML, search, and a local documentation server safely from docstrings.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    International keyboard representing numbers, currency, and dates with Python locale
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python locale: Numbers, Currency, and Dates

    Learn Python locale to format and parse numbers, currency, dates, encodings, and cultural sorting without concurrency mistakes.

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Monitor and network representing system information with Python platform
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python platform: System Information

    Learn Python platform to identify operating systems, architecture, Linux distributions, Python versions, and runtime environments safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Source code and compiler representing build paths and variables with Python sysconfig
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python sysconfig: Paths and Build Info

    Learn Python sysconfig to discover installation paths, build variables, headers, virtual environments, and platform tags safely.

    Ler mais

    Tempo de leitura: 7 minutos
    09/08/2026
    Hard drive representing memory-mapped files with Python mmap
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python mmap: Memory-Mapped Files

    Learn Python mmap to map files into memory, search bytes, share data, and choose read, write, or copy-on-write access safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026