Kern Sandbox
Your model writes the code. This runs it where it can't touch your machine.
rootless · no daemon · no socket · no VM · no cloud · no account
Works with MCP clients · LangChain · pi · Python and Node
An agent's tool-call, a generated snippet, a notebook cell, a CI step: it arrives, you run it, and nobody has read it first.
# the runtime: one static binary, checksum-verified by the script
curl -fsSL https://raw.githubusercontent.com/getkern/kern/main/install.sh | sh
# if the venv line fails, your distribution ships it separately:
# sudo apt install python3-venv
python3 -m venv .venv && . .venv/bin/activate
pip install kern-sandbox
import kern_sandbox as kern
r = kern.run_code("print(sum(range(100)))")
print(r.stdout, r.fault) # 4950 None
That call started a container from an OCI image, ran the code with no network, memory and PID caps and a deadline applied from outside, and threw the container away before returning. The next call gets a new one. Two things, not one: the isolation is the binary's, this package is the API in front of it.
The result says who stopped the run
docker run gives you exit 137 and leaves you to guess whether that was your timeout, the OOM killer
or something else. This tells you. Every row was run:
| the code | fault.type |
exit_code |
|---|---|---|
print(sum(range(100))) |
None |
0 |
while True: pass, timeout_s=3 |
timeout |
137 |
bytearray(400<<20), memory_mb=128 |
oom |
137 |
urlopen(...), network off |
None |
1 |
The last row is the one a loop gets wrong: the network was off, so the code raised and the
sandbox did nothing. fault is read from a pipe kern writes rather than from stdout, so code that
prints [exit 0] can't fake it. Also killed, escape_blocked, exec_failed, startup_failed.
Works with
| Any MCP client | Cursor, Claude Code, Claude Desktop, LM Studio, Zed, Windsurf. The package ships kern-mcp, a dependency-free stdio server: the model writes code, kern runs it here, charts come back as images it can see. Per-client config in docs/MCP.md |
| LangChain | kern_code_tool() is a StructuredTool your agent can call, and a sandbox fault comes back labelled for the model. There is a shell execution policy too: LANGCHAIN-SHELL.md |
| pi | kern-pi routes its bash, read, write, edit, ls, grep and find into a box, your working directory at /workspace: integrations/pi |
| Python and Node | the same API on both registries: pip install kern-sandbox and npm i kern-sandbox |
{ "mcpServers": { "kern": { "command": "uvx", "args": ["--from", "kern-sandbox", "kern-mcp"] } } }
A client spawns the server from its own PATH, so a venv is invisible to it: uvx above installs
nothing, pipx install kern-sandbox is the other way. From macOS or Windows the client is one hop
away and it is still one line ("command": "wsl", or "command": "ssh" to a VM or a board).
Safe by default
A bare Sandbox() has no network, no host mounts, seccomp on, capabilities dropped and a
mandatory timeout. Every relaxation is a named argument (image, setup, memory_mb, cpus,
timeout_s, network, mounts, workspace, prewarm, and a dozen more).
Two that have surprised people, both measured:
- Mounts over sensitive sources are refused even if you ask: the host's own directories, anything
with
.ssh/.aws/.kubein its path, and kern's own state. No opt-out. Mount a copy. network=Trueincludes the host's loopback, where unauthenticated services live. A test read the host's SSH banner off127.0.0.1:22.egress_allowis the middle setting and is route-level.
How fast
The number to quote is 14.5 ms, the default path, one tool-call end to end on an
i7-14700KF. The box itself is 4.9 ms; most of the rest is CPython starting inside, which is a Python
cost. The 0.7 ms bar is a prewarm burst and falls back to 14.5 when the pool cannot keep up. And
print(1) flatters every runtime here: import json,re measures 47.3 ms against 320.8, 7x rather
than 20x. Measure your own machine and take the p50.
Compared to what you are probably doing
| a venv | isolates imports, not the process: the code still has your files, your keys and your network |
docker run per call |
the same idea with a daemon and a socket in front of it, at 292.8 ms against 14.5 ms on the same machine. That socket is root-equivalent |
| bubblewrap, nsjail | the building blocks kern uses. They don't resolve images, don't apply cgroup caps, and give you no verdict: you get an exit code and work out the rest |
| a microVM (Firecracker, Kata) or gVisor | a stronger boundary than this one, and the right answer when the code is actively hostile. It costs what a machine costs: about half a second per command |
| E2B, Modal, Daytona | the same job in someone else's cloud, with an account and your code leaving the machine |
Current limitations
- Not a boundary against deliberately hostile code. This is namespaces, cgroups and seccomp: a kernel boundary, for your own or semi-trusted code. If the code is actively hostile, or belongs to someone else, use a microVM (Firecracker, Kata) or gVisor. That is a different job and costs what a machine costs: about half a second per command there against 14.5 ms here. Full statement in SECURITY.md.
- Linux only. Windows works through WSL2; on a Mac the package installs and refuses to run, because macOS has no namespaces and no cgroups. Use a Linux VM.
- The caps bind only where your host delegates a cgroup.
kern doctorsays whether yours does, andrequire_limits=Trueturns a silent no into a refusal to start. - Nothing bounds the workspace. It is a host directory, so a job can fill your disk. Point
workspace=at a filesystem you have sized. - No
--user, so an image that refuses to run as root (postgres, some databases) has no answer here yet. pip install kern-sandboxdoes not install the sandbox. It drives akernbinary onPATHor in$KERN_BIN, a second thing to keep current. If a verdict looks wrong, printkern --version.
More
Charts and mime-typed results without a Jupyter kernel, the full API, kernel() for a warm
interpreter, snapshots, and the measured sharp edges:
SANDBOX-NOTES.md.
Requires unprivileged user namespaces and cgroup v2, and Python 3.9+: install notes. Apache-2.0.
Release files for kern-sandbox 0.2.34
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kern_sandbox-0.2.34.tar.gz | 252.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kern_sandbox-0.2.34-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 380.8 kB
Release files / kern_sandbox-0.2.34.tar.gz
| Download URL | kern_sandbox-0.2.34.tar.gz |
|---|---|
| Size | 252.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bdf03bfbc23a72b69fb2c6ceb50e5a06f2079d7da3f518abc32e3e79774f8f5c
|
|
BLAKE2b-256 checksum How to use checksums |
f2aef8904047772f25c0fdec983700d7c52961b2526ef4144ac02968a3b3f18a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / kern_sandbox-0.2.34-py3-none-any.whl
| Download URL | kern_sandbox-0.2.34-py3-none-any.whl |
|---|---|
| Size | 128.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
315058e7926953885255fb33b14697b8c6bd93e5cde9860e17f9d4ba9066e1cd
|
|
BLAKE2b-256 checksum How to use checksums |
89886437d4582b24b1ab3ee7357f81dedff734e92a39acda11165e967f8601bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|