Sandboxed installs · No network at run time · Read-only environment · Receipts

Sandboxed Python Environments

senv keeps the uv workflow and puts an OS-level security boundary underneath it. Dependencies install without reaching your source tree or your credentials. Your code runs with no network by default and an environment it cannot rewrite.

curl -fsSL https://senv.h5i.dev/install.sh | sh
Apache 2.0 · one Rust binary · same pyproject.toml, same uv.lock · read the manual · source
uvthe workflow you already use 2policies: install and run 0files added to your repo Apache 2.0open source, no lock-in

A boundary at both moments that carry risk.

Installing a dependency and running your code are different threats, so senv enforces different policies for each. Neither one is a mode you have to remember to turn on.

Install with registry-only network
Resolution and installation happen in a staging area holding your manifests and none of your source. Egress is pinned to package registries at the network layer, so a build backend that runs during pip install has nowhere to send anything.
senv sync · senv add · senv lock
Run with no network and no credentials
Your code gets its project read-write and almost nothing else. Network access is denied by default, and environment variables carrying credentials are absent unless you declare them as secrets.
senv run · senv shell
Keep the environment read-only
While your code runs, installed packages cannot rewrite the environment to persist into the next run. senv compiles bytecode at install time and denies writable bytecode caches at run time, so no package can leave a .pyc Python would prefer over the protected source.
venv outside the project · no writable parent
Review what was actually enforced
Every command appends a receipt outside the sandbox, with declared secrets redacted and a digest of the policy that ran. What was blocked is a question with an answer, not something you infer from a stack trace.
senv status · senv report · senv doctor
Resource Install · sync / add / lock Run · run / shell
Network PyPI and configured indexes only denied by default
Project source read-only — a staging copy, not your tree read-write — your code edits your files
Python environment writable, because installing means writing read-only
Credentials unavailable — never exposed to build code only the secrets you declared in senv.toml
Resource limits CPU, file size, wall clock; memory and processes on Linux CPU and file size; memory and processes on Linux

Two policies, compiled from one optional senv.toml and applied without a flag to remember. The asymmetry is the design: installation writes the environment and must not touch your source; execution writes your source and must not touch the environment. How each row is enforced.

A virtual environment is a PATH convention, not a boundary.

venv and uv isolate dependencies from each other. They do not isolate code from you. Everything a package can do, it can still do — with your user's full permissions.

Installation is code execution
An sdist build backend runs during install, before anything is ever imported and before any review of what you added. It runs as you, with your files and your network.
Your credentials are ambient
A package inside a virtual environment can read ~/.ssh, your cloud config, and every environment variable in the process it was imported into. Nothing in the venv model says otherwise.
Any destination is reachable
Exfiltration needs one socket. The dependency that opens it does not have to be the one you installed — a transitive update days later is enough, and the lockfile hash only tells you the bytes did not change.
Persistence is free
The environment is writable while the code runs, so a package can patch an installed module or leave a bytecode cache behind and be back on the next run, whatever the lockfile says.

Four different letters, not a different tool.

Flags pass through to uv and exit codes pass back to the caller, so senv run pytest is as usable in CI as it is on your laptop.

What you do todayWith senvWhat changes
uv syncsenv syncinstalls inside the install sandbox
uv locksenv lockresolves in a staging copy of your manifests
uv add requestssenv add requestsresolve, write back, sync — all confined
uv run pytestsenv run pytestno network, read-only environment
source .venv/bin/activatesenv shellthe same boundary, interactively
any other uv commandsenv uv -- <args>runs inside the install boundary

A senv project is a uv project. Same pyproject.toml, same uv.lock, and the only new file is an optional senv.toml. Teammates without senv keep running plain uv against the same repository, so one person can adopt it without a team-wide decision — and opting out is deleting nothing.

01

Adopt the project →

In an existing project, init takes pyproject.toml, uv.lock, and .python-version as they are. An existing .venv is left alone, because those packages never passed through the boundary.

$ senv init --python 3.13
adopts, does not scaffold · --replace-venv when you are ready
02

Install behind the registry allowlist →

uv resolves and installs in a staging area containing your manifests and none of your source, with egress pinned to package registries at the network layer.

$ senv add requests
no source · no credentials · registries only
03

Run with the network closed →

Your project is writable and the environment is not. Network access is denied until you name a host, and the grant is a line in a file you check in.

$ senv run pytest
read-only venv · net deny · exit code passes through
04

Read the boundary, not the docs →

See the policy that is actually enforced on this host, what was denied across recent runs, and which limits your platform cannot apply. senv marks those rather than printing them as ceilings.

$ senv status
resolved policy · isolation tier · grants · digest

Frequently asked questions

The short answers. The manual has the long ones.

What is senv?
senv is a security boundary for Python environments. It runs uv and your code inside an OS-level sandbox with a different policy for each moment that carries risk: installs reach package registries and nothing else and cannot write to your source tree, and your code runs with no network by default, an environment it cannot modify, and no access to your credentials. One Rust binary, Apache 2.0, and it adds no files to your project beyond an optional senv.toml.
How is this different from venv or uv?
venv and uv isolate dependencies, not code. A virtual environment is a PATH convention: a package inside one can still read ~/.ssh, take your environment variables, open a socket to any destination, and execute build code during installation with your full user permissions. senv keeps the uv workflow and puts an OS-level sandbox underneath it. A senv project is still a uv project, so teammates without senv keep running plain uv against the same repository.
Does senv detect malicious packages?
No, and it does not try. senv contains what a package can do rather than deciding what a package is. Lockfile hashes, pinned versions, and dependency review remain exactly as important as before. What changes is the blast radius: a malicious sdist build backend runs with no reach into your source tree, your credentials, or any network destination that is not a package registry.
What does senv block at run time?
Network access is denied by default, the installed environment is read-only, credentials are unavailable unless declared as secrets in senv.toml, and CPU and file-size limits apply everywhere with memory and process limits on Linux. Your project directory stays read-write, because that is the point of running the code. When something is blocked, senv prints what was blocked and the narrowest command that would permit it.
My program needs to call an API. Do I have to give up the boundary?
No. You widen one host at a time. senv allow api.stripe.com records the grant in senv.toml; senv run --allow-net api.stripe.com applies it to a single command. senv report --suggest aggregates the denials recorded across runs and prints a policy stanza to review — it never writes the policy for you. Some denials are guarantees rather than defaults, and senv does not offer to bypass those: runtime code cannot make the environment writable or read ~/.ssh.
Can a compromised dependency widen the policy?
It can edit senv.toml, because that file lives in your project and your project is writable while your code runs. It cannot make the edit take effect. senv keeps the last accepted policy snapshot outside the sandbox: an unchanged or narrower policy runs normally, and a wider one is refused with a diff until you inspect it and run senv trust. The same check covers the pyproject.toml sections that control install behavior, [build-system] and [tool.uv]. This is tamper-evidence, not tamper-prevention, and we do not claim more.
What are the limits?
The default tiers share the host kernel: Landlock, seccomp, and Seatbelt are OS-level isolation, not a hypervisor boundary, so isolation = "microvm" is the rung for when a separate kernel is required. Installation must write packages, so the install sandbox protects your source, credentials, and non-registry network but cannot make the environment read-only during an install. Kernel-tier denial reports infer some denials from program output, which untrusted code can influence, so review every suggested grant. And running .venv/bin/python directly from an editor bypasses the run boundary entirely; use senv run or senv shell. The manual keeps the full list.
What does senv put in my repository?
An optional senv.toml, and nothing else. The environment, the wheel cache, the receipts, and the trusted policy baseline all live outside the project tree, because the run phase grants the project read-write and receipts a compromised package can rewrite are not evidence. .venv stays as a symlink so editors and language servers still resolve. senv refuses to run if SENV_STATE_DIR or SENV_CACHE_DIR points inside the project.
Which platforms does senv support?
Linux and macOS. On Linux, senv uses Landlock, seccomp-bpf, namespaces, and rlimits or cgroups, and registry allowlisting during installs additionally needs slirp4netns and nftables. On macOS it uses the built-in Seatbelt sandbox with a DNS-pinned loopback proxy for allowed egress, and no extra runtime is required; macOS has no seccomp and cannot enforce cgroup memory or process limits, which senv reports rather than silently ignoring. On Windows, use WSL2. Run senv doctor to see what your host can actually enforce.
What happens if my host cannot enforce the policy?
senv refuses to run and explains what is missing. It never silently downgrades to a weaker boundary, and there is no flag that runs your code on the host unconfined. An explicit downgrade such as install.net = "host" is available for hosts that cannot allowlist registries, but it is written in the config, warned about in senv status, and stamped into every receipt.
How does senv relate to h5i?
senv uses h5i-sandbox, the confinement engine behind h5i, as a linked Rust crate, and compiles the Python-shaped senv.toml into an h5i policy. h5i gives a coding agent a whole disposable development environment; senv confines one Python project's installs and runs. They share the enforcement layer, the receipt model, and the rule that a boundary fails closed.

The uv workflow, with a boundary underneath.

Installs that cannot reach your source or your credentials. Code that runs with no network and an environment it cannot rewrite. Apache 2.0, no SaaS, no lock-in.

curl -fsSL https://senv.h5i.dev/install.sh | sh