Protecting Your Python Projects Against Supply Chain Attacks

Protecting Your Python Projects Against Supply Chain Attacks

6 min de leitura

If you follow security news in Python, you’ve probably heard about the recent attacks that compromised several popular libraries on PyPI. Legitimate packages were hijacked, malicious versions were published, and developers around the world installed compromised code without even realizing it.

The scariest part? This can happen to any of us, at any time.

What’s Happening?

In recent months, we’ve seen an alarming increase in “supply chain” attacks in the Python ecosystem:

  • Typosquatting: Malicious packages with names similar to popular libraries (e.g., requets instead of requests)
  • Account Takeover: Maintainer accounts being compromised and malicious versions published
  • Dependency Confusion: Internal packages being replaced by malicious public versions
  • Malicious Updates: Legitimate updates that introduce malicious code

The impact of these attacks can be devastating: credential theft, data exfiltration, backdoors in production, and much more.

The First Line of Defense: Don’t Install “Bleeding Edge” Packages

Here’s an uncomfortable truth: most supply chain attacks are discovered within the first hours or days after publication. If you install a package as soon as it’s released, you’re putting yourself at unnecessary risk.

The solution? Wait a few days before installing new versions.

UV: The Modern Package Manager with Built-in Security

If you don’t know uv yet, it’s the fastest and most modern Python package manager available today. And it has a killer feature for security: --exclude-newer.

How It Works

uv lets you exclude packages published less than X time ago. For example, you can configure it to only install packages that have existed for at least a week:

1
uv lock --upgrade --exclude-newer "1 week"

This means any malicious package published recently will be automatically ignored.

User/System-Wide Configuration

You can set this globally in the uv config file. On Linux/Mac, add to ~/.config/uv/uv.toml:

1
2
# Don't install packages published less than 1 week ago
exclude-newer = "1 week"

On Windows: %APPDATA%\uv\uv.toml

Per-Project Configuration

Even better, add it to your project’s pyproject.toml so that everyone on the team uses the same policy:

1
2
[tool.uv]
exclude-newer = "1 week"

What If I Need an Urgent Update?

Great question! Imagine a critical CVE is discovered in requests and a fix is released today. You can’t wait a week.

For these cases, you can define exceptions for specific packages:

1
2
3
4
5
6
[tool.uv]
exclude-newer = "1 week"

# Exception for requests - can install versions since 2026-03-25
[tool.uv.exclude-newer-package]
requests = "2026-03-25T16:00:00Z"

This allows you to install specific versions when needed while maintaining general protection.

Why This Works

Think about it: when an attacker compromises a package, they want immediate results. They won’t wait a week to reap the rewards. With exclude-newer:

  • ✅ You give the community time to identify malicious packages
  • ✅ Security scanners have time to process new releases
  • ✅ Maintainers have time to yank problematic versions
  • ✅ You drastically reduce your attack surface

It’s like letting wine age — but for dependencies.

Getting Started with UV

If you don’t have uv installed yet:

1
2
3
4
5
6
7
8
# Linux/Mac
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell)
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

# Or via pip
pip install uv

Create your project:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
uv init my-project
cd my-project

# Add the security configuration
echo '[tool.uv]
exclude-newer = "1 week"' >> pyproject.toml

# Add dependencies
uv add requests pandas

# Sync (respecting the 1-week window)
uv sync

Done! Your project is now automatically protected against bleeding edge packages.

Integrity Verification: Hashing

In addition to avoiding very new packages, another effective technique is using hash verification for your dependencies.

How It Works

When you install a Python package, pip downloads a .whl or .tar.gz file from a repository. Each file has a unique hash (SHA256) that serves as a “fingerprint” of that specific package.

The idea is simple: if you specify the exact hash of the package you want to install, pip will verify that the downloaded file matches that hash. If someone tries to replace the package with a malicious version, the hash won’t match, and the installation will fail.

In Practice

Let’s see how to implement this in your project.

1. Generate the hash file

First, create a requirements.txt file with your dependencies and pinned versions:

1
2
3
4
requests==2.31.0
pandas==2.2.1
numpy==1.26.4
pydantic==2.6.3

Now, use the pip hash command to generate the hashes:

1
2
pip install --dry-run --report - requests==2.31.0 | \
  python -c "import json, sys; print(json.load(sys.stdin)['install'][0]['download_info']['url'])"

Or, more simply, use pip-compile from pip-tools:

1
2
pip install pip-tools
pip-compile --generate-hashes requirements.in -o requirements.txt

This will generate a requirements.txt file with hashes:

1
2
3
4
5
6
7
requests==2.31.0 \
    --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
    --hash=sha256:942c5a758f98d788eaceeaa8c0fb4a82c5e0c5f626c3f5f9d5e5a16b1a1e8c88
pandas==2.2.1 \
    --hash=sha256:234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef \
    --hash=sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
# ... and so on

2. Install with hash verification

Now, when you install your dependencies:

1
pip install --require-hashes -r requirements.txt

pip will only install if the hashes match. If someone tries to send you a malicious package, the installation will fail immediately.

Other Layers of Protection

Hash verification is powerful, but it’s not the only defense. Here are other techniques you should use:

1. Pip Audit

Use the pip-audit tool to check for known vulnerabilities:

1
2
pip install pip-audit
pip-audit

This will scan all your dependencies against the PyPI vulnerability database.

2. Dependabot / Renovate

Set up automatic dependency update tools in your repository. They monitor for vulnerabilities and automatically create PRs:

1
2
3
4
5
6
7
8
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

3. Poetry with Lock File

If you use Poetry, poetry.lock already includes hashes by default:

1
poetry install --sync

This ensures exact versions (with verified hashes) are installed.

4. Isolated Environments

Always use virtual environments. If a malicious dependency is installed, it will be isolated to that specific project:

1
2
3
4
python -m venv venv
source venv/bin/activate  # Linux/Mac
# or
venv\Scripts\activate  # Windows

5. Private PyPI Index

For critical projects, consider using a private PyPI index (like Artifactory or DevPI):

1
pip install --index-url https://your-private-pypi.com/simple/ requests

This lets you control exactly which packages are available.

6. Dependency Code Review

Before adding a new dependency:

  • ✅ Check popularity (downloads, GitHub stars)
  • ✅ Check update frequency
  • ✅ Read the source code (yes, really)
  • ✅ Verify the maintainers are trustworthy
  • ✅ Look for open security issues

7. Minimize Dependencies

The best dependency is the one you don’t have.

Always ask yourself: “Do I really need this 500MB library to do something I can implement in 50 lines?”

Conclusion

Supply chain attacks are a real and growing threat in the Python ecosystem. This isn’t paranoia, it’s precaution.

The good news? The tools to protect yourself already exist and are free. The bad news? Most developers don’t use them.

Don’t be part of the statistic. Implement these practices today:

  1. Migrate to uv and configure exclude-newer — instant protection
  2. Add hash verification — use pip-tools or Poetry
  3. Set up pip-audit in CI/CD
  4. Enable Dependabot or Renovate
  5. Educate your team

Security is not optional. It’s responsibility.


Pro tip: Combine uv + hash verification + pip-audit. This trio creates an in-depth defense that is practically impenetrable against supply chain attacks.


References: