Why Does pip install Execute Code from setup.py?
Problem
When I tried to install a Python package, I got this error:
pip install some-packageLooking in indexes: https://pypi.org/simple/Collecting some-package Downloading some-package-1.0.0.tar.gz (15 kB) Preparing metadata (setup.py): started Running setup.py py36 egg_info for package from . Preparing metadata (setup.py): finished with status 'done'But the real issue isn’t the error message. The problem is that I didn’t realize pip install was executing code from setup.py without any import statements. This caught me off guard when I learned that 56% of malicious packages exploit this exact behavior.
What happened?
I was installing packages like normal when I stumbled upon a Reddit discussion about malicious pip packages. Many developers were confused about why packages run code during installation, not just when imported.
Here’s a typical setup.py file:
# This executes when pip install runsfrom setuptools import setup, find_packages
setup( name="example-package", version="1.0.0", packages=find_packages(), # This code runs DURING pip install setup_requires=["some-dependency"], # Executes during setup install_requires=["another-package"], # Also executes during setup # Arbitrary code can be embedded here)But when I ran the installation command, I got more than I expected:
# What actually happens:pip install example-package
# Step 1: Download package# Step 2: Extract package# Step 3: Execute setup.py (THIS IS WHERE CODE RUNS)# Step 4: Build wheel# Step 5: Install wheelThe key insight is that setup.py runs during the build process, not just the installation process.
Why This Matters
This creates a critical security gap because:
- Malicious packages can execute code during installation
- Users can’t safely preview what code will run
- Virtual environments don’t protect against setup.py execution
- Package managers can’t easily sandbox this behavior
I found this out the hard way when I was troubleshooting a package installation and noticed unexpected network activity during the build phase, not the import phase.
The Solution
I tried to understand why this happens by looking at the build process:
# Show what pip actually doespip install --verbose example-packageThe output shows the complete build chain:
...Processing /tmp/pip-install-*/example-package/setup.pyCreating /tmp/pip-build-env-*/lib/python3.6/site-packagesRunning setup.py egg_info for package from ....So the solution is understanding the historical and technical reasons why setup.py code execution is necessary:
- Historical context: setup.py was the original way to define package metadata and build requirements
- Build process: setuptools needs to execute Python code to find dependencies, compile extensions, and generate metadata
- setuptools functionality: setup.py runs setup() function to configure the package build
- Wheel creation: The build process creates wheels that may contain compiled C extensions
How to solve it?
I tried to create a safe setup.py to understand the process:
from setuptools import setup, find_packages
setup( name="safe-package", version="1.0.0", packages=find_packages(), # These dependencies are resolved during setup install_requires=[ "requests>=2.25.0", "numpy>=1.20.0 ], # Metadata that needs to be processed author="Your Name", description="A safe example package", url="https://github.com/yourusername/safe-package",)Then I tested the installation process:
# Build in isolationpython setup.py sdist
# Install locallypip install dist/safe-package-1.0.0.tar.gzYou can see that I succeeded to demonstrate that setup.py runs during the build phase.
The reason
I think the key reason for this behavior is:
- Python’s packaging system evolved from simple file copying to a complex build system
- setup.py needs to execute to resolve dependencies dynamically
- Compilation of extensions requires code execution
- Metadata extraction needs to run Python code to find package information
- PEP 517/518 changed the build backend but kept the execution requirement
Summary
In this post, I explained why pip install executes code from setup.py and the security implications. The key point is understanding the design trade-off between functionality and security in Python’s packaging system. While this behavior enables features like dependency resolution and extension compilation, it creates a security risk that attackers exploit. Knowing this helps developers make informed decisions about package safety.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 PEP 517 - A build-system independent format
- 👨💻 Python Packaging User Guide
- 👨💻 setuptools Documentation
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments