Python Virtual Environment
Python Virtual Environment venv is an isolated environment that allows you to install and manage Python packages independently from the system-wide Python installation.
This helps avoid version conflicts between projects and keeps dependencies organized.
By default, Python installs packages globally, but virtual environments ensure project-level dependency control.
Why Use Virtual Environment?
Virtual environments allow you to isolate dependencies for each individual project. This means every project can maintain its own set of packages and versions without interfering with others, which is especially important when different projects require conflicting versions of the same library.
By using virtual environments, you eliminate version conflicts and package collisions that often occur when managing global installations. Each environment is fully self-contained, ensuring that changes in one project don’t accidentally break another.
Another advantage is that virtual environments do not require root or administrator access. All packages are installed locally within the environment’s directory, avoiding any modifications to the system-wide Python installation.
Finally, virtual environments greatly simplify reproducibility. You can easily export all installed packages into a requirements.txt file and recreate identical environments across different systems or deployment stages, ensuring consistent behavior between development, testing, and production.
How it Works?
A virtual environment works by creating a self-contained directory that mimics a complete Python installation. Inside this directory, venv creates a local copy of the Python interpreter, along with directories to store installed packages.
When the virtual environment is activated, the system adjusts environment variables such as PATH and PYTHONPATH to prioritize the virtual environment’s interpreter and packages. Any package installed via pip while the environment is active will be placed into the environment’s isolated directory.
This redirection ensures that all Python code execution, package installation, and dependency management happen within the virtual environment scope, leaving the global Python environment unaffected.
For example:
# Create a new virtual environment in the directory 'venv'
python3 -m venv venv
# Activate the virtual environment (Linux/macOS)
source venv/bin/activate
# Install packages inside the virtual environment
pip install requests
# Export installed packages to requirements.txt
pip freeze > requirements.txt
# Deactivate the virtual environment
deactivate
In this example, all installed packages such as requests package are isolated within the virtual environment directory.
The requirements.txt file can later be used to recreate the same environment on another system using pip install -r requirements command.
Virtual environments are a fundamental tool for maintaining clean, stable, and reproducible Python projects. They allow developers to isolate dependencies, avoid version conflicts, and safely experiment without risking the global system environment.
A venv is not a security boundary
It is tempting to read “isolated environment” as “sandbox.” It is not. A virtual environment provides dependency isolation, not privilege or execution isolation. There is no separate user, no namespace, no syscall filtering, no restriction on what code inside the environment can do. The “isolation” is almost entirely a matter of path manipulation:
$ source venv/bin/activate
$ which python
/home/user/project/venv/bin/python
$ python -c "import sys; print(sys.prefix)"
/home/user/project/venv
Activation prepends the venv’s bin/ to PATH and points sys.prefix at the venv directory; the interpreter then resolves imports against the venv’s site-packages first. That is the whole mechanism. Code running in a venv has exactly the privileges of the user who launched it. A venv around a vulnerable app does nothing to contain an attacker who reaches code execution.
site-packages is an attacker-writable code directory
The part that matters offensively: a venv’s site-packages is a directory of code that the interpreter imports automatically, and it is owned by the unprivileged user, with no sudo required to write to it. Any attacker who can drop a file there, or influence what pip install pulls in, gets their code executed on the next import.
Two persistence-relevant surfaces live inside every venv:
.pthfiles. Python executes any line in asite-packages/*.pthfile that begins withimportat interpreter startup, before user code runs. Writing a single.pthinto a venv’ssite-packagesis a clean, low-noise persistence and auto-run primitive. This is covered in depth in Import System Abuse with .pth Files and sys.meta_path.- Module shadowing. Because the venv’s
site-packagesis searched ahead of the standard library for non-builtin modules, dropping a maliciousrequests.py(or shadowing any third-party package the app imports) hijacks that import for everything running in the environment.
pyvenv.cfg and the interpreter it trusts
Every venv has a pyvenv.cfg at its root that records which base interpreter it was created from:
home = /usr/bin
include-system-site-packages = false
version = 3.12.3
include-system-site-packages = true widens the import search to the system site-packages, enlarging the trusted code surface, which is worth noting when reviewing a deployment. And because the venv’s python is frequently just a symlink back to the base interpreter, the integrity of the environment is only ever as good as the integrity of the base install it points at.
The takeaway for defenders: treat site-packages and pyvenv.cfg as executable, security-sensitive paths. File-integrity monitoring on them catches .pth persistence and module shadowing that process-level monitoring will miss, because the malicious code runs inside the legitimate interpreter as a legitimate import.
Why virtual environments matter from an offensive security perspective
I never treat a venv as something that contains me. It is a dependency-management convenience, and from where I sit it is a writable, auto-loaded code directory running with full user privileges. When I land code execution near a venv, it is one of the first places I reach for persistence.
- Activation is just path manipulation. Activation prepends
bin/toPATHand repointssys.prefix, nothing more. There is no separate user, namespace, or syscall filtering, so a venv around a vulnerable app does nothing to slow me down once I am inside it. site-packagesis unprivileged, auto-imported code. Nosudois needed to write there, and the interpreter imports from it on every run. I look for write access to it as a direct path to execution..pthfiles auto-run at startup. A singleimport-prefixed line in asite-packages/*.pthexecutes before user code, which is clean, low-noise persistence (Import System Abuse with .pth Files and sys.meta_path).- Module shadowing hijacks imports. Because the venv’s
site-packagesis searched ahead of the standard library for non-builtin modules, dropping a trojanedrequests.pycaptures that import for everything in the environment. PYTHONPATHandpyvenv.cfgwiden the trust surface. A controllablePYTHONPATHinjects my directories ahead of legitimate ones, andinclude-system-site-packages = trueenlarges the code the interpreter trusts. I check both during review.- Hijacking the install step. Anything that influences what
pip installpulls into the venv (typosquats, dependency confusion, an unpinned mirror) runs at install time with the user’s privileges (see Python Packages).
For defenders: the isolation a venv gives you is about versions, not security, so treat site-packages and pyvenv.cfg as executable paths and watch them accordingly.