self-hosted sandbox platform

$ sandfort

Run the code your AI agents write, on hosts you control.

REST API · Python & TypeScript SDK · CLI · MCP server

Request beta access → API reference

Hosted 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.

untrustedwritten by a model, not a colleague
statefulfiles, repositories, long-running processes
constantmany runs per task, every task
1 · model writes code 2 · code runs somewhere untrusted 3 · output stdout, stderr, exit code 4 · model fixes, retries turn n

the problem

Where does that code run today?

✗ option a

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.

✗ option b

on a shared CI or prod box

Untrusted code lands next to secrets, deploy tokens and every other workload on that machine.

✗ option c

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.

YOUR INFRASTRUCTURE INTERNET your agent LLM + orchestrator sandfort api REST · SDK · CLI · MCP auth · quotas · audit worker · jobs · reconciler database sqlite · postgres · audit chain docker host sbx · run tests isolated container sbx · build isolated container sbx · live shell isolated container egress proxy allowlisted host any other host ✗
one Python serviceover the Docker hosts you already run
state stays with yousandboxes, keys and the audit chain in your SQLite or Postgres
outbound, per sandboxno network, an allowlist through the proxy, or open: you choose

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.

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(command="echo build; exit 7")
# r.exit_code == 7  (returned, not raised)

run = sbx.process.code_run("python", "print(21 * 2)")
# run.stdout == "42"

sbx.fs.upload_file("/work/main.py", source)
client.delete(sbx.id)
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: 72 operations in an exported OpenAPI spec
  • Python SDK: sync and async clients, sessions, fs, git, PTY, image builder
  • TypeScript SDK: the same agent workflow, no runtime dependencies
  • MCP server: sandboxes as tools for any MCP client, no SDK integration
  • 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.

inside a sandbox
$ :(){ :|:& };:                      # 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
processespids_limit=512, 4096 open files
networkmetadata and private ranges rejected by the host firewall script; no container-to-container traffic
secretsSANDFORT_* names refused at create; server settings never passed in
dockerthe socket is never mounted into a sandbox
privilegecap_drop=ALL (7 added back), no-new-privileges, CPU and memory caps, no swap
optionalgVisor (runsc) runtime, seccomp profile; prod refuses to start on a weak host

Sandboxes are containers that share the host kernel. These controls reduce the risk of an escape; they do not remove it.

network

Reach the API it needs. Nothing else.

POST /v1/sandboxes
{
  "snapshot": "python:3.12-slim",
  "egress_allowlist": ["example.com"]
}
inside that sandbox
$ 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
  • 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.

107 mscreate a sandbox · p50
p95 145
18 msrun a command · p50
p95 25
050100150200 ms
warm poolstandby sandboxes per org, snapshot and size; a matching create takes one (opt-in)
snapshotsstart from a prepared environment instead of installing again
reproduce itpython examples/benchmark.py on your own hardware

One local measurement on one host (2026-11-01): 20 creates waiting for started and 50 short commands in one sandbox; macOS arm64, Docker 29.8.2, alpine:3.20 pre-pulled. Not an SLO and not a comparison with any other product.

operations

Runs where you already run.

day one

a laptop

  • One Python process and a SQLite file
  • A local Docker daemon
  • Your own instance in under an hour with the install guide
production

your cluster

  • Kubernetes kit with separate api and worker roles, Postgres
  • Multi-node placement and cross-node routing
  • Prometheus /metrics, OTel tracing, JSON logs with request ids
  • Read-only web console at /console
  • 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.

identity providerOkta or Entra ID assigns a person to the app
SCIM →
member createdSCIM 2.0 Users with a per-org token; default role viewer
→
signs inOIDC identity token exchanged for a short-lived session
role checkedevery session request re-checks membership and the current role
→
worksadmin · developer · viewer, mapped to API scopes
offboard →
deactivatedactive: false removes the membership; audit history stays
scoped API keys, hashed, shown once no one grants scopes they do not hold the last admin cannot be removed SCIM: Users only, no groups

engineering

Measured, not promised.

2267tests in the suite
10acceptance gates passing, end to end on real Docker
11delivery phases shipped
72REST API operations
CI greenlint mypy --strictimport-layer contracts unit testsdocker tests load and chaos suites

Tests: pytest --collect-only, 2026-11-02. 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 you can put it in cron.

honest limits

What it does not do (yet).

Container isolation. Sandboxes share the host kernel; there is no microVM option. A gVisor runtime is optional.
Docker socket on sandbox nodes. Server processes mount it and it grants node control: use dedicated nodes.
Unencrypted node-to-node hop. Requires a private network or an encrypted overlay.
No built-in TLS, and /metrics has no authentication: terminate TLS in front and keep metrics internal.
A sandbox lives on one node. Placement spreads sandboxes; it does not move them.
SSO maps into one org. SCIM covers Users, not groups.
Version 0.1.0. No tagged release, built in late 2026.
No production references and no certifications yet. Source-available under FSL-1.1, not open source (Apache-2.0 after two years).

next step

Looking for 2–3 design partners.

primary

teams building AI coding agents

Your agent needs exec, files, git and a terminal. Your customers need the code to stay on your side.

secondary

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
hosted beta→your instance→pilot

Request beta access →

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.