Auditing my local Python packages

surprisetalk1 pts0 comments

Auditing my local Python packages – alexwlchanSkip to main contentAuditing my local Python packages<br>Posted 10 April 2026<br>Is it just me, or are chain attacks on the rise? It feels like there are more and more incidents where a bad actor publishes a malicious version of a popular package, people install it on their machines, and they get compromised. In March alone, such attacks included Axios npm package, the Trivy vulnerability scanner, and the LiteLLM Python package.<br>So far I’ve been unaffected, because the attacks have only involved libraries or packages I don’t use – but it would be foolish to imagine that will always be the case. I have a lot of local Python projects, and I’ve been thinking about how I’d react if a Python package I use was compromised.<br>The first step is detection: once I know a package version is malicious, how do I know if I’ve installed it? Because I use virtual environments, this turns out to be a non-trivial question.<br>What are virtual environments?<br>Virtual environments (or “virtualenvs”) are a tool to create isolated Python environments, each with its own set of installed packages. They allow you to have different dependencies for different projects. For example, if two projects depend on different versions of the same package, you can create per-project virtualenvs, each with the appropriate version.<br>A virtualenv is stored in a folder that includes symlinks to the global Python interpreter and the packages you’ve installed in the virtualenv. When you “activate” the virtualenv, commands like pip install install packages in the virtualenv folder rather than your global Python.<br>Here’s an example:<br>$ # `python3` points to my global interpreter<br>$ which python3<br>/Library/Frameworks/Python.framework/Versions/3.13/bin/python3

$ # Create the virtualenv<br>$ python3 -m venv .venv

$ # Activate the virtualenv, so now `python3` and `pip` commands will<br>$ # run inside the virtualenv<br>$ source .venv/bin/activate

$ # `python3` now points to the symlink in the virtualenv<br>$ which python3<br>/private/tmp/example/.venv/bin/python3

$ # Pillow will be installed inside the `.venv` folder<br>$ pip install PillowI create a new virtualenv for every Python project, so I have a lot of different virtualenvs on my personal Mac.<br>To check if I’d installed version X of package Y, I’d have to check each of my virtualenvs. Python itself doesn’t keep a running list of virtualenvs I’ve created, so I have to manage that list myself.<br>Getting a list of my virtualenvs<br>I’m very consistent about naming my virtualenvs: the folder is always named .venv. (I actually have a shell function for creating virtualenvs, which enforces that convention.)<br>This means I can find all the virtualenvs in my home directory with a one-line command:<br>$ find ~ -type d -name .venv<br>/Users/alexwlchan/repos/snippets/.venv<br>/Users/alexwlchan/repos/alexwlchan.net/.venv<br>/Users/alexwlchan/repos/colour-scheme/.venv<br>…I can similarly search external drives and volumes where I have virtualenvs:<br>$ find /Volumes/Media/ -type d -name .venv<br>/Volumes/Media/Screenshots/.venv<br>/Volumes/Media/Social Media/.venv<br>/Volumes/Media/Bookmarks/.venv<br>…These commands take about 30 seconds to run – just long enough to be annoying – so I’ve saved the results to a text file:<br>$ find ~ -type d -name .venv >> ~/.venv_registry<br>$ find /Volumes/Media/ -type d -name .venv >> ~/.venv_registryI’ve also modified my shell function that creates virtualenvs to update this file whenever I create a new virtualenv. Now I have an up-to-date list of all my virtualenvs that I can use to search for vulnerable dependencies.<br>What about Python packages installed outside virtualenvs?<br>If you run pip install without activating a virtualenv, the packages will get installed in your global Python installation, and they wouldn’t be included in this list. This is generally a bad idea, because you’re back to the problem of different projects using incompatible dependencies.<br>You can tell pip that it should only use virtualenvs, either with an environment variable or a config file. Once you set up that config, pip will refuse to install packages outside a virtualenv.<br>Alternatively, if you use uv instead of pip, you can’t install packages outside a virtualenv unless you explicitly pass the --system flag to modify your system Python.<br>I set the PIP_REQUIRE_VIRTUALENV=true in my shell config file, and I use uv, so I don’t have any Python packages installed outside virtualenvs.<br>Searching my virtualenvs for package versions<br>Now I have a text file with a list of all my virtualenvs, I can write scripts that run commands in each of them.<br>For example, here’s a bash script that runs uv pip freeze in every virtualenv to print a list of installed dependencies:<br>#!/usr/bin/env bash

set -o errexit<br>set -o nounset

while read -r venv_dir; do<br>if ! test -d "$venv_dir"; then<br>echo "does not exist: $venv_dir" >&2<br>continue<br>fi

echo "== $venv_dir =="<br>uv pip freeze --python "$venv_dir/bin/python"<br>echo ""<br>done Within half a second, I have a...

python virtualenvs venv virtualenv packages installed

Related Articles