Skip to content

Why Does pip install Execute Code from setup.py?

Problem

When I tried to install a Python package, I got this error:

Terminal window
pip install some-package
Looking 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:

setup.py
# This executes when pip install runs
from 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:

Terminal window
# 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 wheel

The 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:

Terminal window
# Show what pip actually does
pip install --verbose example-package

The output shows the complete build chain:

...
Processing /tmp/pip-install-*/example-package/setup.py
Creating /tmp/pip-build-env-*/lib/python3.6/site-packages
Running 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:

safe_setup.py
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",
author_email="[email protected]",
description="A safe example package",
url="https://github.com/yourusername/safe-package",
)

Then I tested the installation process:

Terminal window
# Build in isolation
python setup.py sdist
# Install locally
pip install dist/safe-package-1.0.0.tar.gz

You 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:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments