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
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.
pip install has nowhere to send anything..pyc Python would prefer over the
protected source.| 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.
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.
install, before anything is
ever imported and before any review of what you added. It runs as you, with your files and your
network.~/.ssh, your
cloud config, and every environment variable in the process it was imported into. Nothing in the
venv model says otherwise.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 today | With senv | What changes |
|---|---|---|
| uv sync | senv sync | installs inside the install sandbox |
| uv lock | senv lock | resolves in a staging copy of your manifests |
| uv add requests | senv add requests | resolve, write back, sync — all confined |
| uv run pytest | senv run pytest | no network, read-only environment |
| source .venv/bin/activate | senv shell | the same boundary, interactively |
| any other uv command | senv 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.
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.
--replace-venv when you are readyuv 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.
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.
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.toml.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.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.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.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.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.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.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.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.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.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