ZIP archives show up constantly in professional Python work ā bundled data exports, .docx/.xlsx files (which are ZIPs internally), deployment packages. The standard library's zipfile module handles all of it, plus a specific security risk worth knowing about before extracting a ZIP from an untrusted source.
1Inspecting Before Extracting: namelist() and getinfo()
zipfile.ZipFile("export.zip") opens an archive for reading without extracting anything at all ā archive.namelist() returns every file's path within the archive as a list of strings, letting you see exactly what an archive contains before deciding what (if anything) to actually extract or read. archive.getinfo(name) goes further, returning a ZipInfo object with metadata about that specific entry ā .file_size (the original, uncompressed size) and .compress_size (the size within the archive) among other fields.
This inspect-first capability matters for both efficiency and safety: for a large archive containing many files, checking namelist() to confirm the specific file you actually need is present ā and checking its size via getinfo() before deciding to extract it ā avoids unnecessarily extracting gigabytes of unrelated content just to reach one small file you actually wanted.
The comparison between .file_size and .compress_size also directly foreshadows the zip bomb concern covered later in this lesson: a dramatic disparity between the two (a tiny compress_size inflating to an enormous file_size) is precisely the signature of a maliciously crafted archive designed to exhaust disk space or memory on extraction.
import zipfile
with zipfile.ZipFile("export.zip") as archive:
print(archive.namelist()) # ['data/users.csv', 'data/orders.csv', 'README.txt']
info = archive.getinfo("data/users.csv")
print(info.file_size, info.compress_size) # original vs compressed sizeSee what's inside before extracting anything
2Reading Without Extracting: archive.open()
archive.open("data/users.csv") returns a file-like object that reads directly from the compressed data within the archive, decompressing on the fly as you read ā no file is ever written to disk, and no separate extraction step happens first. This is functionally similar to opening a regular file with open(), and supports .read() for the full contents (returning bytes, which you decode explicitly if you need text, exactly as regular binary file reading works) or iterating line by line for text-mode-style access.
This matters specifically when you only need to inspect or process *some* of an archive's contents, or when you don't want extracted files left behind on disk at all ā a script that peeks at a CSV inside a ZIP export to validate its structure before deciding whether to fully process the archive doesn't need extractall()'s side effect of writing every file to disk first, most of which might never actually get used.
This pattern generalizes well beyond ZIP-specific use cases: it's the same 'process data through a stream rather than materializing it fully on disk first' philosophy that underlies the Streaming Large Files lesson closing out this section, applied here specifically to data that happens to live inside a compressed archive rather than as a standalone file.
with zipfile.ZipFile("export.zip") as archive:
with archive.open("data/users.csv") as f:
content = f.read().decode("utf-8")
print(content[:200]) # peek at the file without extracting anything to diskReads directly from the archive ā no disk extraction step
3Zip Bombs and Path Traversal: Extraction From Untrusted Sources
Extracting a ZIP archive whose contents you don't fully trust (a user upload, a file from an external partner) carries two specific, well-documented risk categories worth designing against deliberately. A zip bomb is an archive engineered to compress an enormous amount of highly-repetitive data into a tiny file ā a few kilobytes on disk that expand to gigabytes or more when extracted, capable of exhausting disk space or memory on a system that extracts it naively without checking sizes first; comparing getinfo(name).file_size against a sane maximum before extraction is a direct mitigation.
Path traversal exploits a different weakness: a ZIP entry's stored filename is, in principle, an arbitrary string, and a maliciously crafted archive can include an entry named something like "../../../etc/passwd" or an absolute path ā if extraction naively joins the destination directory with that stored name without validation, the resulting file can be written *outside* the intended extraction directory entirely, potentially overwriting sensitive files elsewhere on the filesystem. safe_extract()'s check for ".." and a leading "/" in every member name before calling extractall() is a direct, deliberate defense against exactly this attack.
The professional default this establishes: treat extractall() on a ZIP archive from any source you don't fully and permanently trust as something requiring validation first ā checking member paths for traversal attempts and checking sizes for zip-bomb-style disparities ā the same disciplined 'don't trust external input by construction' posture this curriculum has applied consistently to JSON parsing, YAML loading, and now archive extraction.
import zipfile
def safe_extract(zip_path: str, dest_dir: str):
with zipfile.ZipFile(zip_path) as archive:
for member in archive.namelist():
if member.startswith("/") or ".." in member:
raise ValueError(f"Unsafe path in archive: {member}")
archive.extractall(dest_dir) # safe now that paths are validatedPrevents path traversal and zip-bomb-style resource exhaustion
4Step-by-Step Breakdown
.docx and .xlsx files are secretly ZIP archives full of XML ā rename one to .zip and open it. Let's use the module that handles this format professionally.
zipfile.ZipFile lets you inspect an archive's contents WITHOUT extracting anything -- namelist() and getinfo() give you metadata first.
You can read a SPECIFIC file's contents directly from the archive, without extracting the whole ZIP to disk first.
Checkpoint: What does archive.open("data/users.csv") let you do that extractall() does not?
- āRead a specific file's contents directly from the archive, without writing anything to disk
- āIt reads faster than extractall() for every use case
Extracting from an UNTRUSTED zip needs a safety check first -- a 'zip bomb' or path traversal attack can exploit naive extraction.
Checkpoint: Why does safe_extract() check for ".." and a leading "/" in each archive member's name before extracting?
- āTo prevent a maliciously crafted archive from writing files OUTSIDE the intended destination directory (a path traversal attack)
- āTo check whether the file is compressed or stored uncompressed
ZIP files cover compressed archives; Binary Files goes one level lower, to raw byte-level file handling.
Read a Real File from a Zip. Finish read_from_zip(): pull a single file's bytes out of a zip without extracting everything.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Best Practices
Inspect an archive with namelist()/getinfo() before extracting, especially from an untrusted source
This lets you validate what's actually in an archive ā checking for path traversal attempts and unreasonable file sizes ā before committing to extraction.
Never call extractall() on an untrusted ZIP archive without validating member paths first
A maliciously crafted archive can contain entries designed for path traversal, writing files outside the intended destination directory ā validate every member name before extraction from any source you don't fully trust.
Frequent Bugs
Calling archive.extractall(dest_dir) directly on a ZIP file from an untrusted source (user upload, external partner) without validating member paths first, creating a path traversal vulnerability.
Validate every entry's name (checking for '..' sequences and absolute paths) before calling extractall(), or extract members individually with explicit, sanitized destination paths.
Real-World Examples
Safely Processing a User-Uploaded ZIP Export
A web application accepts user-uploaded ZIP files containing CSV data for bulk import, and needs to process them without risking path traversal or excessive resource consumption from a malicious upload.
import zipfile
MAX_FILE_SIZE = 50 * 1024 * 1024 # 50MB
def process_upload(zip_path: str):
with zipfile.ZipFile(zip_path) as archive:
for name in archive.namelist():
if ".." in name or name.startswith("/"):
raise ValueError(f"Unsafe path: {name}")
info = archive.getinfo(name)
if info.file_size > MAX_FILE_SIZE:
raise ValueError(f"File too large: {name}")
for name in archive.namelist():
if name.endswith(".csv"):
with archive.open(name) as f:
process_csv_data(f.read().decode("utf-8"))