Open Source Security

Python setuptools Security Considerations

setuptools is the default Python packaging backend and its security properties matter for anyone who builds, installs, or runs Python code. Here is what to watch.

Shadab Khan
Security Engineer
7 min read

setuptools is the oldest part of the Python packaging stack, the default build backend for projects that have not migrated to something else, and the thing that runs arbitrary code every time you install a source distribution. Its security properties affect everyone who uses pip, not just package maintainers. Most Python developers have no idea what setuptools is actually doing when they run pip install, which is fair because setuptools has spent two decades hiding behind setup.py ergonomics.

This is a measured look at what setuptools does, where it has gone wrong, and what you can do about it.

setup.py Runs Code, Always Has

The original Python packaging model was: a package ships a setup.py, pip downloads the source, and pip runs python setup.py install. That setup.py is a Python script and can contain arbitrary code. It can import any installed library, read environment variables, make network calls, execute shell commands. There is no sandbox.

This is not a bug. It is a design choice from a time when package installation was expected to sometimes need to compile C extensions, probe the system for dependencies, or conditionally configure. The result is that every source distribution you install from PyPI is, technically, untrusted code execution.

In practice, the ecosystem has relied on PyPI's curation and author reputation. The pyhttpslib incident (2017, typosquatting urllib3-adjacent names), the jeIlyfish/python3-dateutil incident (2019), and the ongoing drumbeat of typosquatting attacks show that this trust model has limits.

PEP 517 Build Isolation Helps

PEP 517 and PEP 518 (both from 2016-2017, widely adopted by around 2020) specify a build isolation model where pip creates a clean environment, installs the build system declared in pyproject.toml, and runs the build there. The build system could be setuptools, or it could be Poetry's builder, Hatchling, Flit, PDM's builder, or maturin.

For a setuptools-backed project, the isolation means the build system dependencies (setuptools itself, wheel, any plugins declared in [build-system] requires) are installed fresh rather than inherited from the user's environment. This does not prevent a malicious setup.py from executing — it still executes — but it prevents a lot of accidental environment confusion, and it is a foundation on which further sandboxing can be built.

You get PEP 517 isolation automatically with a recent pip (19.0 and later). If you are building with pip install --no-build-isolation to avoid downloading build dependencies every time, you are opting out of this. Have a reason.

Which setuptools CVEs Matter?

setuptools itself has had CVEs worth knowing about.

CVE-2022-40897 was a ReDoS in setuptools' package index parsing. A maliciously crafted HTML response from a package index could hang the parser indefinitely. Patched in setuptools 65.5.1. The impact was low — you had to be pointed at an attacker-controlled index — but it was a real issue.

CVE-2024-6345 (discovered 2024, but useful historical context for 2023 posture) was a remote code execution in setuptools.package_index.download when processing a crafted package index URL, via a shell injection path. Patched in setuptools 70.0.0. Affected anyone who used setuptools to fetch packages from a URL, which included some older pip workflows.

The lesson is that setuptools, despite being "just a build tool," has a network-facing component (the package index) and network-facing code is subject to normal web vulnerability classes. Keep setuptools current; it is not a package you can freeze at a 2020 version.

Are Editable Installs Safe?

pip install -e . and its setuptools implementation (setup.py develop) create an egg-link or a PEP 660 editable install that points at your working copy. This is enormously convenient for development and it has security implications.

The implementation involves adding the project's source directory to sys.path via a .pth file or an import hook. A .pth file is a list of paths that Python adds to sys.path at interpreter startup — but .pth files can also contain lines starting with import, which execute Python code at interpreter startup. Every time you start the interpreter.

The practical implication: if your development virtualenv has an editable install of a project whose source has been tampered with, every python command runs that tampered code. Normally this is not an issue because you trust your own working copy. In CI, where you might pip install -e from a cloned repository, it matters. Use non-editable installs in CI; use editable installs only in environments where you trust the source tree.

pyproject.toml and Declarative Configuration

setuptools has been moving toward declarative configuration in pyproject.toml for years. The [project] table and the [tool.setuptools] sections let you declare dependencies, package metadata, entry points, and more without ever writing a setup.py.

This is a real security improvement. A project whose packaging is fully declarative cannot execute arbitrary code at install time (for the source distribution install path). setuptools will still run — it is still the build backend — but the scope of what it does is bounded by the declared configuration rather than by a Python script.

Migrate your setup.py projects to declarative config. The setuptools documentation has the migration path. Keep setup.py only if you genuinely need logic that cannot be expressed declaratively — conditional extension modules, for example — and when you do, keep the script small and review it for anything that touches the network or the filesystem outside the package directory.

Entry Points Are a Subtle Attack Surface

setuptools' entry points mechanism is how console_scripts creates command-line tools and how many plugins register themselves. Any installed package can declare an entry point in a widely-used group — say, flask.commands or pytest11 — and the host application will pick it up on startup.

An attacker who publishes a typosquat of a common package, or a package that a victim installs for unrelated reasons, can register entry points in trusted groups and hook into application startup. This is not a setuptools bug; it is the design. But it means that a pip install of a seemingly unrelated utility can expand the attack surface of your Flask app, your pytest runs, or your Airflow DAGs.

Mitigation: know what plugins are registered in your critical entry point groups. importlib.metadata.entry_points(group="flask.commands") enumerates them at runtime. Compare against an expected list in a security check.

Version Pinning for setuptools Itself

A subtle thing: setuptools is a build-time dependency, not a runtime dependency. It is in your virtualenv, but it is not in your requirements.txt. It is, however, in your SBOM if your SBOM tool inspects the environment.

Pin it in your [build-system] requires. requires = ["setuptools>=68.0.0", "wheel"] is typical. This ensures your CI builds against a known setuptools version and that a setuptools CVE does not silently affect your builds via an ambient upgrade.

How Safeguard Helps

Safeguard inventories setuptools versions across your build environments and deployment images, treating build-time dependencies as first-class SBOM entries rather than ambient tooling. Reachability analysis evaluates whether a setuptools CVE affects your specific build patterns — for example, whether you use the network-facing package index parser in the way CVE-2022-40897 required. Griffin AI drafts migration PRs from setup.py to declarative pyproject.toml configuration, reducing the surface for arbitrary code in your own packages. Policy gates can block publishing a package that still uses a setup.py with network access at install time, or block builds that use pinned-old setuptools versions below a security baseline.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.