The python zipp package is a pathlib-compatible wrapper around zip files, and it lands in your virtual environment because importlib-metadata, one of the most depended-upon packages on PyPI, requires it, not because you asked for it. Run pip list in almost any nontrivial Python project and zipp is there, quietly, alongside a handful of other jaraco/* utility packages. Developers meet it in three ways: an SBOM audit asks what it is, a vulnerability scanner flags an old version, or a broken environment produces an ImportError naming it. This post explains all three.
What zipp actually does
zipp provides zipp.Path, an object that lets you navigate the contents of a zip archive with the same ergonomics as pathlib.Path on a real filesystem:
import zipp
import zipfile
zf = zipfile.ZipFile('bundle.whl')
root = zipp.Path(zf)
for entry in root.iterdir():
print(entry.name, entry.is_dir())
readme = root / 'my_package-1.0.dist-info' / 'METADATA'
print(readme.read_text())
iterdir(), joinpath(), the / operator, read_text(), exists(), is_file(): the familiar pathlib surface, but the "filesystem" is an archive. That matters more than it looks, because Python packages themselves are zip archives (wheels), and the machinery that reads package metadata at import time needs exactly this capability.
Here is the part that explains its ubiquity: zipp is the upstream of the standard library's own zipfile.Path. Jason "jaraco" Coombs maintains zipp as the place where this functionality is developed first and then merged into CPython; zipfile.Path appeared in Python 3.8 based on it, and later zipp releases continue to backport newer fixes and behaviors to older interpreters. The zipp python package is, in effect, the standard library running ahead of the standard library.
How it gets into your tree
Nobody types pip install zipp. The chain is almost always:
your-app
└── some-framework
└── importlib-metadata
└── zipp
importlib-metadata reads installed-package metadata (versions, entry points, dependencies) and needs to look inside wheels and zipped eggs to do it, hence zipp. Because importlib.metadata only joined the standard library in Python 3.8, and its API kept evolving through 3.10 and 3.12, a very large share of the ecosystem, historically including pytest, flake8, and countless CLI tools that resolve plugin entry points, depended on the PyPI backport. Every one of those pulled zipp along.
On modern interpreters (3.10+), more packages drop the backport and use the standard library directly, so zipp is slowly becoming less universal. But "slowly" is doing heavy lifting: any project supporting older Pythons, and any dependency of such a project, keeps it around. Check your own chain in one command:
pipdeptree --reverse --packages zipp
If you generate SBOMs, zipp is also a good canary for whether your tooling resolves transitive dependencies honestly. An SBOM of a Python service that lists no jaraco utilities is usually an SBOM built from a requirements file rather than the actual environment, which is worth knowing before an auditor notices, and it is the kind of gap SCA tooling that scans lockfiles and environments, such as Safeguard, exists to close.
The security record: one real CVE
For a package installed this widely, zipp's advisory history is short. The one entry that matters is CVE-2024-5569: versions before 3.19.1 could be sent into an infinite loop by a specially crafted zip file. The loop is reachable through the Path API's ordinary operations, joinpath, the / operator, iterdir, so any code that opens an untrusted archive through zipp (or through the identical code in CPython's zipfile.Path, which shared the flaw) could be hung by a malicious file. It is a denial of service, not code execution; the loop does not even exhaust memory, it just never returns. The fix landed in zipp 3.19.1 on May 31, 2024.
Two practical notes:
- The attack requires untrusted input. If zipp only ever touches wheels installed from your own lockfile, exploitability is negligible. If your service accepts uploaded zip or wheel files and inspects them, the bug was real for you, and you should also review the equivalent CPython patch level for the interpreter itself.
- Pinned old versions linger. Because zipp is transitive, nobody feels ownership of it, and constraint files written in 2022 happily hold it at 3.8.x forever.
pip install --upgrade zippcosts nothing; the package has no dependencies of its own.
As of mid-2025 the current release line is 3.23.0 (June 2025), which requires Python 3.9+; upgrading past 3.19.1 is the only security-relevant threshold.
Should you remove it?
You generally cannot, and you do not need to. But three legitimate housekeeping moves exist:
- Raise your Python floor. If everything you run is on 3.10+, audit which of your direct dependencies still require
importlib-metadatafrom PyPI and whether newer releases of them dropped it. Fewer backports means a smaller tree. - Stop pinning what you do not own. Transitive pins of zipp in application constraint files cause more breakage than they prevent. Let your resolver float it within the lockfile update cadence you already have.
- Watch the
jaracocluster as a unit. zipp travels withjaraco.functools,jaraco.context,more-itertools, and friends, all maintained by the same person, all deep in the setuptools/importlib supply chain. That concentration is a single-maintainer risk profile worth acknowledging in your dependency review, not a reason to panic. The maintenance record spanning well over a decade is excellent.
That last point deserves a beat. The Python packaging stack rests on a small number of prolific individual maintainers, and zipp is a textbook example: microscopic package, enormous blast radius if a malicious release ever shipped. This is exactly the scenario that makes teams add provenance checks and registry monitoring to their pipelines, and it is a better argument for continuous dependency scanning than any individual CVE.
Debugging the classic zipp ImportErrors
Because zipp sits under the machinery that imports everything else, a broken zipp breaks loudly and confusingly:
ModuleNotFoundError: No module named 'zipp'
usually means a half-upgraded environment where importlib-metadata survived a cleanup that removed zipp. Fix it with a reinstall of the metadata stack:
pip install --force-reinstall importlib-metadata zipp
The rarer failure is a version conflict where an old zipp lacks an API a new importlib-metadata expects (AttributeError: 'Path' object has no attribute ...). The resolution is the same: upgrade both together, and prefer regenerating the lockfile over hand-editing pins.
FAQ
What is the zipp package in Python?
zipp provides zipp.Path, a pathlib-style interface for reading files inside zip archives. It is the upstream project from which CPython's zipfile.Path was developed, maintained by Jason Coombs (jaraco), and it reaches most environments as a dependency of importlib-metadata.
Is zipp safe? Does it have vulnerabilities?
One notable CVE exists: CVE-2024-5569, an infinite-loop denial of service triggered by crafted zip files, fixed in zipp 3.19.1 (May 2024). Anything at or above that version has no known advisories. The risk only applies where zipp processes untrusted archives.
Can I uninstall zipp?
Not if anything in your environment depends on importlib-metadata, which is almost always the case. Removing it will break imports across the environment. The realistic path to dropping it is raising your minimum Python version so dependencies stop needing the backport.
Why does my project have zipp when I never installed it?
It is transitive: importlib-metadata (and historically importlib_resources) requires zipp to read metadata inside wheel archives, and hundreds of popular packages required those backports on Python versions before 3.10. Run pipdeptree --reverse --packages zipp to see your exact chain.