self-hosted sandbox platform
$ sandfort
Run the code your AI agents write, on hosts you control.
Request beta access → API referenceHosted evaluation beta is open: try the API with zero install on our shared instance. In production, Sandfort runs on your own infrastructure.
the shift
AI agents write code.
Every turn of the loop runs it.
Generated code is only useful once it executes: tests, builds, scripts, dev servers. A coding agent runs code on every turn, and nobody on your team wrote or reviewed it.
the problem
Where does that code run today?
on the agent's own host
One rm -rf or curl | sh away from a bad afternoon.
Full access to keys, files and the network. No limits.
on a shared CI or prod box
Untrusted code lands next to secrets, deploy tokens and every other workload on that machine.
in a hosted sandbox service
Runs isolated, but your code, repositories and data leave your network on every call.
When customers, a security review or a contract say “the code stays here”, none of these work.
the answer
$ The sandbox runs on your side of the line.
integrate
Four ways in. Same sandbox.
The whole agent loop — exec, files, git, PTY, sessions, snapshots, preview URLs — behind one API. Non-zero exits come back as values, so the error goes straight back to the model. The REST API and the Python SDK are part of every evaluation package; the TypeScript SDK, the MCP server and the CLI are delivered on request, according to your stack.
from sandfort_sdk import Client client = Client(base_url=URL, api_key=KEY) sbx = client.create_sandbox(snapshot="python:3.12-slim", wait=True) r = sbx.process_exec(argv=["sh", "-c", "echo build; exit 7"]) # r.exit_code == 7 (returned, not raised) run = sbx.code_run("python", "print(21 * 2)") # run.exit_code == 0, run.stdout.strip() == "42" sbx.fs.upload_file("/tmp/hello.txt", b"hello\n") sbx.delete()
import { SandfortClient } from "@sandfort/sdk"; const client = new SandfortClient({ baseUrl: URL, apiKey: KEY }); const sbx = await client.createSandbox({ snapshot: "python:3.12-slim" }); const r = await sbx.codeRun("python", "print(21 * 2)"); console.log(r.exitCode, r.stdout); // 0 42 // sessions, async commands, fs, git, snapshots, volumes
$ sandfort login $ sandfort sandbox create demo --snapshot python:3.12-slim --wait $ sandfort exec sbx_… python3 -c "print(21 * 2)" 42 $ sandfort cp ./app sbx_…:/work/app $ sandfort shell sbx_… # interactive PTY
{ "mcpServers": { "sandfort": {
"command": ".venv/bin/python",
"args": ["mcp_server/sandfort_mcp.py"],
"env": { "SANDFORT_BASE_URL": URL, "SANDFORT_API_KEY": KEY }
} } }
# tools any MCP client gets:
# create_sandbox · run_code · run_command · create_snapshot
# upload_text_file · download_text_file · list_sandboxes · delete_sandbox
- REST API: 77 HTTP operations on 59 paths in an exported OpenAPI spec
- Python SDK: sync and async clients, sessions, fs, git, PTY, image builder; in every evaluation package
- TypeScript SDK (on request, per stack): the same agent workflow, no runtime dependencies
- MCP server (on request, per stack): sandboxes as tools for any MCP client, no SDK integration
- CLI (on request, per stack):
sandfortfor exec, copy, snapshots and a shell - Signed webhooks for lifecycle events, with delivery history and redelivery
- Preview URLs and a web terminal, to show a human what the agent built
isolation
Assume the code is hostile.
$ :(){ :|:& };: # fork bomb
bash: fork: retry: Resource temporarily unavailable
$ curl -m 3 http://169.254.169.254/ # cloud credentials
curl: (7) Failed to connect to 169.254.169.254 port 80
$ env | grep SANDFORT_
(no output)
$ ls /var/run/docker.sock
ls: cannot access '/var/run/docker.sock': No such file
pids_limit=512, 4096 open filesSANDFORT_* names refused at create; server settings never passed incap_drop=ALL (7 added back), no-new-privileges, CPU and memory caps, no swapcontainer (runc), gvisor (a user-space kernel, the default on the hosted beta) or microvm (Kata Containers: its own guest kernel on KVM)prod refuses to start on a weak hostOn the container tier sandboxes share the host kernel. On the
gvisor tier a user-space kernel sits between the code and the host kernel; it is
not a hardware boundary. These controls reduce the risk of an escape; they do not remove it.
This output is from the container tier; the hosted beta default is
gvisor, and the output differs per tier (under gvisor, for example,
uname -r reports a gVisor kernel and exceeding the memory or process limit usually
stops the whole sandbox: exit code 137, sandbox_stopped true, files kept).
network
Reach the API it needs. Nothing else.
POST /v1/sandboxes { "snapshot": "python:3.12-slim", "egress_allowlist": ["example.com"] }
$ curl -sI https://example.com
HTTP/2 200 # CONNECT tunnel through the proxy
$ curl -sI https://other.example.net
curl: (56) CONNECT tunnel failed, response 403
# same proxy, forged credential
$ curl -sI --proxy-user sbx:wrong https://example.com
curl: (56) CONNECT tunnel failed, response 407
- Fail-closed: with no proxy configured, an allowlisted create is refused (409), never run with open egress.
network.block_all: no network at all, for code that needs none.- On Linux hosts the firewall chain admits only the proxy address.
- The cloud metadata address is blocked for every sandbox, always.
guardrails
Built for agents that misbehave.
- Per-org quotas kept in a transactional ledger; only a platform admin can raise them (self-hosted; no org quotas are set on the hosted beta)
- Per-org caps: 8 live terminals and 16 running async commands by default
- Rate limits per user and key; JSON request bodies capped at 16 MiB
- Idle sandboxes auto-stop; forgotten ones auto-delete
- Reconciler and GC remove orphaned containers, images and volumes
- Webhook delivery shared fairly between tenants: one slow endpoint cannot stall the rest
An agent stuck in a loop burns its own quota, not your host.
speed
Fast enough for the inner loop.
python examples/benchmark.py on your own hardwareTwo measurements, each one run on one host, same
script (20 creates waiting for started, 50 short commands in one sandbox,
alpine:3.20 pre-pulled). macOS laptop, 2026-11-01: arm64, Docker 29.8.2.
Linux beta VM, 2026-11-03: a KubeVirt VM with 6 vCPU on a shared Hetzner host (nested
virtualization, not bare metal), Ubuntu 24.04, Docker 29.9.2, XFS project-quota storage.
Neither is an SLO, and neither is a comparison with any other product. The hosted beta is
a shared evaluation instance, not a benchmark environment: measure on your own host.
operations
Runs where you already run.
a laptop
- One Python process and a SQLite file
- A local Docker daemon
- Your own instance in under an hour with the install guide
your cluster
- Kubernetes kit with separate
apiandworkerroles, Postgres - Multi-node placement and cross-node routing
- Prometheus
/metrics, OTel tracing, JSON logs with request ids - Read-only web console at
/console(self-hosted; not exposed on the hosted beta) - Runbook: backup, restore, upgrade, scaling
Same codebase and the same API from the first experiment to the security review. TLS terminates in front of the API.
identity and access
Your identity provider decides who gets in.
vieweradmin · developer · viewer, mapped to API scopesactive: false removes the membership; audit history staysSSO and SCIM are self-hosted configuration features; they are not enabled on the hosted evaluation beta, which uses API keys.
engineering
Measured, not promised.
Tests: pytest --collect-only,
2026-11-04. Operations: counted in the exported OpenAPI spec, 2026-11-04 (includes
/healthz and /readyz). Gates: scripts in examples/.
CI: green on GitHub as of 2026-11-03.
The audit log is a per-organization hash chain; audit-verify exits non-zero
on any break, so a self-hosted operator can put it in cron. On the hosted evaluation
beta that check runs operator-side; customers read their events through the audit API.
compared with e2b
If you use E2B today.
Both can be self-hosted. They differ in what the host needs, how a sandbox is isolated by default, and what ships with the platform.
gvisor, a
user-space kernel and not a hardware boundary; the hosted beta also offers a microvm
tier (Kata Containers on nested KVM). We do not claim stronger isolation than microVMs.container and gvisor
tiers run on any host with a Docker daemon, with no KVM; only the microvm tier needs KVM.E2B facts checked 2026-11-04 against github.com/e2b-dev/infra (README and license); they can change. This is not a feature-by-feature comparison, and it says nothing about E2B's hosted service.
honest limits
What it does not do (yet).
container shares the host kernel;
gvisor is a user-space kernel, not a hardware boundary, and runs spawn- and
small-file-heavy work several times slower; microvm (Kata Containers on the beta's
nested KVM) starts in about 4 s. Per-sandbox disk size (disk_gib) is enforced on all three tiers./metrics has no
authentication: terminate TLS in front and keep metrics internal.The hosted beta is a shared evaluation instance: no SLA, best-effort support, no production secrets, not for benchmarks. The governance details behind the connect guide (scopes per endpoint, rate-limit buckets, usage metering, what the audit log records and redacts, webhook delivery) come in a governance reference shipped with the evaluation package.
next step
Looking for 2–3 design partners.
teams building AI coding agents
Your agent needs exec, files, git and a terminal. Your customers need the code to stay on your side.
platform and DevEx teams
You answer to security and compliance, and need auditable, multi-tenant execution in your own cluster.
the pilot
- Free for a limited number of partners; the commercial model is decided after the pilots
- Start on our hosted evaluation beta — zero install, try the API in an afternoon
- Then your own instance, in under an hour with the install guide
- In return: feedback sessions and the right to name you as a reference
- Best-effort support, directly with the maintainer; no SLA
sandfort
$ run untrusted code.
Keep it in-house.
Self-hosted sandboxes for AI-generated code: exec, files, git, terminals and snapshots, with quotas, SSO and an audit chain.