Skip to main content

A self-hostable, Kubernetes-native sandbox for agent code execution — pod-per-session isolation plus a complete bash/python/file tool surface.

Project description

boxkite

boxkite

License: FSL-1.1-Apache-2.0 CI

The missing batteries-included, self-hostable sandbox for agent code execution.

Most "agent sandbox" projects give you raw isolation — a pod, a VM, a container — and leave you to build the actual tool surface an LLM agent needs on top of it. boxkite is the other half: a complete bash/python/file/search/process tool surface (14 ready-to-wire, framework-agnostic tools — LangChain, LangGraph, CrewAI, AutoGen, or plain OpenAI-style function calling — plus an opt-in git tool set of 8 more) running inside real Kubernetes pod isolation, built on industry-standard hardening practices — non-root execution, all Linux capabilities dropped, a read-only root filesystem, network egress denied by default, and output scrubbed for leaked API keys and cloud credentials before it ever reaches your agent. boxkite itself is early-stage (v0.1.0) — the isolation approach is proven, the project's production track record is not, yet.

Self-host it, point your agent framework at it, and you have a real sandbox in minutes instead of weeks.

Who this is for: boxkite is a self-hostable sandbox for teams building their own agent products that need isolated, multi-tenant code execution at scale — one Kubernetes pod per session, many sessions, many tenants. It is not a single-user local dev-session sandbox like the built-in bash tool in an IDE or CLI coding agent — if you just want your own coding assistant to run shell commands on your own machine, boxkite is the wrong layer; it's for when you are the one operating the sandbox infrastructure behind an agent product other people use.

Contents

Why this exists

Raw pod-per-session isolation is no longer whitespace on its own — kubernetes-sigs/agent-sandbox (a Kubernetes SIG-Apps project) already does that well, and GKE's managed version of it is faster to cold-start than this project's warm-pool claim. boxkite is not a competitor to that primitive — it's designed to sit compatibly alongside it, and a boxkite-style tool layer running on top of agent-sandbox's pod lifecycle is a reasonable future direction.

What boxkite fills in is the layer above raw isolation that, as of this writing, nothing else ships as a complete, self-hostable package:

  • Daytona was, in our assessment, the most credible open self-hosted alternative here, with a large GitHub star count. As of this writing (mid-2026) we believe it has privatized its core and that its OSS repo is no longer actively maintained — verify current licensing and repo activity directly on Daytona's GitHub page and website before relying on this, as we have not linked a snapshot/citation here and project status can change.
  • LangChain's own native sandbox integration appears, as of this writing, to have been archived, with users redirected to hosted-only services — check LangChain's repo/changelog directly for the current status rather than treating this as a permanent fact.
  • E2B, Modal, Riza and similar are excellent, but hosted-first — self-hosting isn't really the point of their product.

boxkite's pitch is narrow and specific: if you want the whole bash/python/file tool surface, hardened and wired to a real K8s pod, running entirely on infrastructure you control — this is the reference implementation to start from, not a novel isolation primitive.

boxkite Daytona E2B Modal
Self-hostable Yes No — core reportedly privatized mid-2026, OSS repo believed unmaintained (verify current status) No — hosted-first No — hosted-first
Batteries-included, framework-agnostic agent tool surface (bash/python/file tools, no LangChain dependency required) Yes Not the product focus Not the product focus Not the product focus
Isolation primitive Kubernetes pod (one per session) Unmaintained as OSS Own hosted sandbox runtime1 Own hosted sandbox runtime1
Open source Source-available (FSL-1.1, converts to Apache-2.0 after 2 years — see License) No longer — core is privatized No No

What's actually in here

This is one repo with several independently-versioned pieces in it, kept together deliberately (see CONTRIBUTING.md for why) rather than split across repos. If you only want to self-host the sandbox, the Core section below is the entire surface you need — everything else is optional, and mostly exists to talk to a hosted control-plane instead of embedding the library directly.

Core — embed the sandbox directly against your own Kubernetes cluster:

  • src/boxkite/ — the Python package (pip install boxkite-sandbox): SandboxManager (K8s pod lifecycle + HTTP routing to the sidecar), WarmPoolManager (a pre-warmed pod pool so session start is a claim, not a cold boot), LazySandboxRuntime (defers pod creation until a tool call actually needs one), and boxkite.tools — fifteen framework-agnostic tools (LangChain BaseTool, LlamaIndex FunctionTool, OpenAI-style function-calling schema, or plain callables for CrewAI/AutoGen/anything else — see boxkite.tools.adapters): bash_tool, python_interpreter, file_create, view, str_replace, present_files, ls, glob, grep, start_process, get_process_output, send_process_input, stop_process, list_processes, watch_directory (a long-poll filesystem watcher, see docs/FILE-WATCHER-DESIGN.md). Tested by the root tests/. An optional, opt-in sixteenth-through-twenty-third set (create_sandbox_tools(enable_git_tools=True)) adds git_clone/ git_status/git_add/git_commit/git_push/git_pull/git_branch/ git_checkout — see docs/GIT-OPERATIONS-DESIGN.md and src/boxkite/tools/git_tools.py for the credential-handling and network-egress caveats before enabling it.
  • sidecar/main.py — the FastAPI service that runs alongside the sandbox container in every pod. It owns the actual filesystem I/O, runs agent commands via nsenter into the sandbox's namespace (dropping to a non-root UID before exec), and syncs files to S3 or Azure Blob storage.
  • deploy/ — Kubernetes manifests (pod-template.yaml, rbac.yaml, network-policy.yaml), the two container images (sandbox.Dockerfile, sidecar.Dockerfile), a docker-compose.yml for local dev without a cluster, and deploy/local-kind/ for a real (if small) Kubernetes dev loop on your laptop.

Optional — run or talk to a hosted control-plane instead of embedding SandboxManager yourself:

  • control-plane/ — a separate FastAPI service (own pyproject.toml, own src/control_plane, own tests/) that puts signup/API-key auth, multi-tenant accounts, and fair-use rate limits in front of SandboxManager. Only needed if you want a hosted-API surface rather than embedding the library directly. See docs/API.md.
  • sdk-python/ (boxkite-client on PyPI) and sdk-js/ (boxkite-client on npm) — thin HTTP clients for someone's running control-plane (yours or a hosted one), not for src/boxkite itself.
  • mcp-server/ (boxkite-mcp) — wraps sdk-python as an MCP tool source, for pointing Claude Code, Claude Desktop, Cursor, or any other MCP client at a control-plane directly.

Everything else:

  • site/ — the marketing site and /developers docs (Next.js). Not part of the published packages; deployed separately.
  • docs/ — API reference, configuration reference, benchmarks, and the extension-points guide (linked from the Docs map below).
  • examples/ — the runnable cookbook (LangGraph, LangChain, raw HTTP, hosted control-plane).
  • scripts/ — one-off maintenance/benchmark scripts, not part of any published package.
  • tests/ — tests for the root src/boxkite package specifically; each of control-plane/, sdk-python/, sdk-js/, and mcp-server/ has its own tests/ alongside its own source, run independently (see CONTRIBUTING.md).

The isolation model

Every session is one Kubernetes pod with two containers sharing volumes:

sandbox container sidecar container
Runs Agent-generated code The HTTP API + storage sync
User Non-root (UID 1001), runAsNonRoot: true Root (needed for nsenter)
Capabilities All dropped (capabilities.drop: [ALL]) Only SYS_PTRACE/SYS_ADMIN, needed for nsenter — never inherited by agent code
Filesystem Read-only root filesystem Read-write (owns the shared volumes)
Network Denied by default (NetworkPolicy + a fresh network namespace per exec) Only what storage sync needs
Privilege escalation allowPrivilegeEscalation: false Same

The sidecar drops to the sandbox's UID/GID via nsenter --setuid --setgid before executing any agent command — the elevated capabilities it needs for nsenter itself are never available to the code it runs. Command output is scrubbed for AWS/Azure keys, JWTs, and generic secret-shaped strings before it's returned (src/boxkite/tools/bash_tool.py). Sensitive host paths (~/.ssh, ~/.aws, ~/.kube, the Docker socket) are outside every mount the sandbox container can see.

See deploy/network-policy.yaml and deploy/rbac.yaml for the actual manifests — they're written as real starting points with inline "change this for your cluster" comments, not illustrative snippets.

Security

boxkite executes arbitrary, agent-generated code, so its security posture is layered defense in depth — no single control below is sufficient on its own.

  • Sidecar HTTP API authentication. The sidecar (sidecar/main.py) has no authentication of its own beyond a per-pod shared secret (SIDECAR_AUTH_TOKEN, sent as the X-Sidecar-Auth-Token header on every route except /health). SandboxManager/WarmPoolManager (src/boxkite/sidecar_auth.py) generate this fresh per pod at pod-creation time — never a static, repo-wide value — and store it in a dedicated per-pod Kubernetes Secret (referenced by the pod's env var via secretKeyRef, never a literal value) so any manager process can recover it later via a separate secrets: get RBAC grant, not merely the pods: get/list the manager already needs for routine lifecycle management. The sidecar fails closed: if SIDECAR_AUTH_TOKEN isn't set, every protected route returns 503 rather than silently running unauthenticated. This exists in addition to NetworkPolicy, not instead of it — NetworkPolicy enforcement is CNI-dependent (GKE Autopilot without Dataplane V2, EKS's default VPC CNI, and deploy/local-kind/ all ship without an enforced NetworkPolicy), and even where enforced, a broad egress rule on the pod also governs what can reach the sidecar's own ingress, since both containers in a pod share one network namespace.
  • Per-exec network isolation. Every /exec call in K8s runtime mode runs inside a freshly created, empty network namespace (unshare -n before nsenter — see build_k8s_exec_command in sidecar/main.py), controlled by SANDBOX_EXEC_NETWORK_ISOLATION_ENABLED (default true). This is the actual network isolation boundary for agent-executed commands — not deploy/network-policy.yaml alone. If you set that flag to false, the NetworkPolicy becomes the only backstop against exec'd commands reaching the network, so make sure it's genuinely restrictive (see the next point) before doing so.
  • NetworkPolicy egress must be scoped to your actual storage endpoint, not left as a blanket allow. deploy/network-policy.yaml ships with a placeholder you must fill in (in-cluster storage via pod/namespace selector, or your cloud provider's storage CIDR/FQDN) — read the comments in that file before applying it. A separate, explicitly-named sandbox-network-policy-permissive policy — kept in its own file, deploy/network-policy-permissive-optin.yaml, specifically so it can't be applied as a side effect of kubectl apply -f deploy/network-policy.yaml — is available as an opt-in for self-hosters who want fully open egress instead; it is never applied by default and must not be layered on top of the scoped policy (overlapping NetworkPolicy selectors are additive, so applying both silently reintroduces the wide-open behavior).
  • The sidecar holds CAP_SYS_ADMIN for the pod's entire lifetime. The nsenter-based isolation design requires it (setns() needs CAP_SYS_ADMIN, not just CAP_SYS_PTRACE) — see the security comment on the sidecar container in deploy/pod-template.yaml and src/boxkite/manager.py. This is a near-root capability: any future RCE-class bug in the sidecar's own FastAPI request handling would be equivalent to full CAP_SYS_ADMIN root within the pod. This is inherent to the current design, not a misconfiguration, which is exactly why the two layers above (sidecar auth, NetworkPolicy) matter — they're what stands between "reachable" and "exploitable."
  • Command output redaction is a cosmetic backstop, not a security boundary. src/boxkite/tools/bash_tool.py's regex-based scrubbing catches accidentally-echoed labeled secrets (api_key=..., password: ..., etc.) in command output before it reaches the calling agent. It cannot catch a deliberately evasive agent (unlabeled values, encoding, splitting a secret across multiple tool calls). Do not rely on it to protect a secret you've decided to expose to agent-visible command execution — see the next point instead.
  • No credential injection into agent-visible /exec calls. The sidecar's /exec endpoint does not accept caller-supplied environment variables. If your integration needs a credentialed operation performed on behalf of a sandbox session, do it as a server-side broker call the manager makes on the sandbox's behalf — never by handing real secret values to an environment that LLM-generated shell commands can read.

See SECURITY.md for the full reporting scope and, importantly, its "Known follow-ups" section — a list of specific gaps that exist today and aren't fixed yet (no checksum verification on two Dockerfile downloads, compose-mode's weaker per-exec network isolation compared to K8s, and the inherent limits of command-name allowlisting). We'd rather you read that list before deploying than discover one of them yourself. Manager-to-sidecar traffic is TLS by default now (a fresh, short-lived, self-signed cert per pod, pinned by the manager — see docs/SIDECAR-TRANSPORT-TLS-DESIGN.md and SIDECAR_TLS_DISABLED in docs/CONFIGURATION.md for the escape hatch).

Self-hosting

boxkite is self-host-only today. The control-plane/ hosted multi-tenant API (accounts, API keys, authenticated sandbox exec) lives in this repo and has its own test suite, but there is no publicly running boxkite-hosted service to sign up for — boxkite signup and "Hosted mode" below assume you've deployed control-plane/ yourself. Everything in this section runs entirely on infrastructure you control, whether that's docker-compose on your laptop or a real Kubernetes cluster.

Quickstart: docker-compose, no Kubernetes needed

Before you run this: docker-compose mode is single-developer local dev only, never production or multi-tenant, and the reason is a real, currently-unmitigated CRITICAL finding, not boilerplate caution. deploy/docker-compose.yml bind-mounts the host's /var/run/docker.sock into the sidecar container so it can docker exec into the sandbox container. Anyone with a live connection to that socket can trivially escalate to full host-root compromise (e.g. docker run --privileged -v /:/host ...) — this was verified directly, including that the standard docker-socket-proxy mitigation does not close it. The only thing standing between sandboxed code and this socket is SIDECAR_AUTH_TOKEN gating the sidecar's own HTTP API; any future auth-bypass, path-traversal, or command-injection bug escalates straight to host compromise, not just container compromise. The sidecar itself logs a loud startup warning when it detects the socket mounted, as a last-resort reminder. See SECURITY.md's "Known follow-ups" section for the full writeup. This does not exist in the Kubernetes runtime at all (no docker socket, no docker-in-docker) — if you want the real isolation model from the start, or anything beyond a single local developer, skip ahead to Quickstart: real Kubernetes, via kind instead.

The fastest path for pure local iteration is the boxkite CLI (pip install -e . in this repo, or pip install boxkite-sandbox once published — see "The boxkite CLI" below for the full command reference).

Note: the PyPI package name is boxkite-sandbox, not boxkite — the plain boxkite name was already taken on PyPI. This only affects the pip install command; the Python import path is unchanged (import boxkite), and so is the boxkite CLI command. Same split as beautifulsoup4 (PyPI name) vs. bs4 (import name) — pip install boxkite will either fail or install an unrelated package, so make sure to install boxkite-sandbox instead.

git clone https://github.com/HarshitKmr10/boxkite.git boxkite && cd boxkite
# NOT "pip install boxkite" — that's a different, unrelated package on PyPI.
pip install -e .
boxkite up
boxkite exec "python3 -c 'print(1 + 1)'"
boxkite files create hello.txt --content "hello from boxkite\n"
boxkite files view hello.txt

First-run build time: until pre-built images are published to GHCR, boxkite up builds the sandbox image from scratch, which includes downloading a pinned Chrome-for-Testing build (~300MB across the browser and headless-shell zips) plus FFmpeg. On a fast connection with a warm Docker cache this typically takes several minutes, not seconds — docker ps will show no running containers until the build finishes. Subsequent runs reuse the cached image layers and start in seconds. The "minutes instead of weeks" framing above is about the infrastructure work you skip (writing your own isolation, hardening, and tool surface), not about this first-run image build.

boxkite up builds and starts the sandbox container, the sidecar HTTP API, and a local MinIO for S3-compatible storage — generating a fresh SIDECAR_AUTH_TOKEN and storing it in ~/.boxkite/local.env so boxkite exec/boxkite files pick it up automatically. No more manually exporting SIDECAR_AUTH_TOKEN yourself.

Or do it manually, without the CLI (raw HTTP contract)
git clone https://github.com/HarshitKmr10/boxkite.git boxkite && cd boxkite
cp .env.example .env
# Generate a secret for the sidecar's HTTP API and put it in .env — see the
# "Security" section above for why this is required, not optional.
echo "SIDECAR_AUTH_TOKEN=$(openssl rand -hex 32)" >> .env
docker compose -f deploy/docker-compose.yml up -d --build

This builds and starts the sandbox container, the sidecar HTTP API, and a local MinIO for S3-compatible storage. Check it's healthy:

curl http://localhost:8080/health

Exercise it directly, before wiring up an agent framework (every route except /health requires the X-Sidecar-Auth-Token header):

export SIDECAR_AUTH_TOKEN=$(grep ^SIDECAR_AUTH_TOKEN= .env | cut -d= -f2)

curl -X POST http://localhost:8080/exec \
  -H "Content-Type: application/json" -H "X-Sidecar-Auth-Token: $SIDECAR_AUTH_TOKEN" \
  -d '{"command": "python3 -c \"print(1 + 1)\""}'

curl -X POST http://localhost:8080/file-create \
  -H "Content-Type: application/json" -H "X-Sidecar-Auth-Token: $SIDECAR_AUTH_TOKEN" \
  -d '{"path": "hello.txt", "content": "hello from boxkite\n"}'

curl -X POST http://localhost:8080/view \
  -H "Content-Type: application/json" -H "X-Sidecar-Auth-Token: $SIDECAR_AUTH_TOKEN" \
  -d '{"path": "hello.txt"}'

Then, from Python, wire the sandbox tools into an agent. SandboxManager picks up compose vs. Kubernetes mode from the environment, so point it at the sidecar you just started (with the same token boxkite up generated, or the one you put in .env manually):

export RUNTIME_MODE=compose SIDECAR_URL=http://localhost:8080
# If you used `boxkite up`, the token is in ~/.boxkite/local.env:
export SIDECAR_AUTH_TOKEN=$(grep ^SIDECAR_AUTH_TOKEN= ~/.boxkite/local.env | cut -d= -f2)
# If you started compose manually, it's in .env instead:
#   export SIDECAR_AUTH_TOKEN=$(grep ^SIDECAR_AUTH_TOKEN= .env | cut -d= -f2)

boxkite's tool surface (bash_tool, python_interpreter, file_create, view, str_replace, present_files, ls, glob, grep, start_process, get_process_output, send_process_input, stop_process, list_processes, plus an opt-in git tool set) is framework-agnostic by default — create_sandbox_tool_specs() returns plain ToolSpecs (a name, description, JSON-schema parameters, and a normal async callable) with no LangChain/LangGraph dependency at all:

from uuid import uuid4
from boxkite import SandboxManager
from boxkite.tools import create_sandbox_tool_specs

manager = SandboxManager()
session_id = str(uuid4())
await manager.create_session(organization_id=uuid4(), session_id=session_id)

specs = create_sandbox_tool_specs(sandbox_manager=manager, session_id=session_id)
# specs = [ToolSpec(name="bash_tool", ...), ToolSpec(name="file_create", ...), ...]

bash_tool = next(s for s in specs if s.name == "bash_tool")
result = await bash_tool.handler(command="echo hello from boxkite")
# Call handler(**kwargs) directly, wire specs into CrewAI/AutoGen/a hand-rolled
# tool-calling loop, or convert the whole list with an adapter:
#   boxkite.tools.adapters.to_openai_functions(specs) -- OpenAI-style
#   function-calling schema, stdlib only, no extra dependency.

Prefer LangChain or LangGraph? boxkite.tools.adapters.to_langchain_tools converts the same specs into LangChain BaseTool objects (requires the langchain extra: pip install boxkite-sandbox[langchain]) — or use the backward-compatible create_sandbox_tools(), which does exactly that internally:

from boxkite.tools import create_sandbox_tools

tools = create_sandbox_tools(sandbox_manager=manager, session_id=session_id)
# tools = [bash_tool, python_interpreter, file_create, view, str_replace,
#          present_files, ls, glob, grep, start_process, get_process_output,
#          send_process_input, stop_process, list_processes]
# Hand these to any LangChain/LangGraph agent. Pass enable_git_tools=True
# to additionally include the opt-in git tool set.

Prefer LlamaIndex? boxkite.tools.adapters.to_llamaindex_tools converts the same specs into LlamaIndex FunctionTool objects (requires the llamaindex extra: pip install boxkite-sandbox[llamaindex]), ready to hand to a ReActAgent/FunctionAgent — see examples/llamaindex_agent/.

The full integration surface, in one place (this used to be scattered across the codebase with no single pointer — see docs/E2B-COMPARISON.md §4 for the competitive context):

Framework/provider How
LangChain / LangGraph to_langchain_tools() (langchain extra)
LlamaIndex to_llamaindex_tools() (llamaindex extra) — see examples/llamaindex_agent/
OpenAI Agents SDK to_openai_agents_tools() (openai-agents extra) — see examples/openai_agents_sdk/
CrewAI, AutoGen/AG2, hand-rolled loops Plain ToolSpec.handler(**kwargs) — no adapter needed
OpenAI function calling to_openai_functions() — stdlib, no openai dependency — see examples/openai_function_calling/
Anthropic tool use Same ToolSpec.parameters/handler shape as OpenAI above
Google Gemini Same shape, unwrapped into Gemini's FunctionDeclaration — see examples/gemini_function_calling/
Mistral Same shape, Mistral's own tool-calling API — see examples/mistral_function_calling/
Groq Same shape, genuinely OpenAI-compatible — see examples/groq_function_calling/
Vercel AI SDK (JS) sdk-js's createSandboxTools() from boxkite-client/vercel-ai (ai v5 extra)
MCP (Claude Desktop, Claude Code, Cursor) mcp-server/ (boxkite-mcp) — a standalone MCP server over the hosted control-plane, zero custom integration code
Claude Code CLI, headless, inside a sandbox docs/CLAUDE-CODE-SANDBOX-QUICKSTART.md + examples/claude_code_sandbox/, or examples/claude_code_declarative_builder/ for the hosted-control-plane API path instead of a hand-maintained Dockerfile

See examples/ for a runnable version of every row above.

Quickstart: real Kubernetes, via kind

./deploy/local-kind/setup.sh
kubectl proxy --context kind-boxkite-dev --reject-paths='' &
export RUNTIME_MODE=k8s SANDBOX_USE_K8S_PROXY=true \
       SANDBOX_IMAGE=boxkite-sandbox:local SIDECAR_IMAGE=boxkite-sidecar:local

See deploy/local-kind/README.md for the full walkthrough, verification steps, and teardown.

Known limitation — Apple Silicon / arm64: setup.sh builds the boxkite-sandbox image locally, and that build intentionally fails on arm64 hosts (including Apple Silicon Macs). This is a deliberate security control, not a bug: the sandbox's bundled Chromium is replaced with a pinned Chrome-for-Testing build to clear known-vulnerability-scanner findings, and Chrome for Testing does not publish a Linux arm64 build for any version — so there's no pin that would fix this, and silently falling back to the older bundled Chromium would reintroduce the vulnerability the pin exists to close. See deploy/local-kind/README.md's "Known limitations" for the full explanation and workarounds (build on an amd64 CI runner/cloud host and push to a registry, or develop on an actual amd64 machine).

For a real (non-kind) cluster, apply deploy/rbac.yaml, deploy/network-policy.yaml, and deploy/pod-security-policy.yaml (edit the namespace and service account references first), build and push the two images from deploy/, and point SANDBOX_IMAGE/SIDECAR_IMAGE at your registry. deploy/pod-security-policy.yaml is the cluster-level backstop for deploy/rbac.yaml's own disclosed limitation (RBAC can't scope pod/secret verbs to sandbox-labeled resources only) -- apply it even if you also follow the dedicated-namespace mitigation deploy/rbac.yaml recommends, since that mitigation is a deployment convention, not something Kubernetes enforces on its own.

Pre-built images: once the first GitHub Release is cut, this repo's release automation (.github/workflows/publish-images.yml) publishes container images to GHCR:

  • ghcr.io/harshitkmr10/boxkite-sandbox
  • ghcr.io/harshitkmr10/boxkite-sidecar
  • ghcr.io/harshitkmr10/boxkite-control-plane

As of now, no release has been cut yet, so none of these images exist in the registry yet — until then, build them yourself from deploy/ (and control-plane/Dockerfile for the control-plane image) as described above.

The boxkite CLI

boxkite up/boxkite exec above are two of the CLI's commands (also: config, session, files, keys, whoami, log, watch, allowlist, signup). The CLI works in two modes, auto-detected from what's configured:

  • Local mode — nothing configured beyond boxkite up. Every command talks directly to the single docker-compose sidecar that boxkite up started, using the token it wrote to ~/.boxkite/local.env.
  • Hosted mode — once boxkite config set-url/set-key (or boxkite signup) has stored a control-plane URL and API key in ~/.boxkite/config.toml, every command switches to calling that control-plane/ API instead, authenticated as Authorization: Bearer <api-key>. As covered in "Self-hosting" above, that control-plane is something you deploy yourself — boxkite signup talks to whatever URL you've pointed it at, not a boxkite-run service.
# Local mode
boxkite up                                  # start docker-compose (sandbox + sidecar + MinIO)
boxkite exec "ls /workspace"                # runs against the local sidecar
boxkite files view hello.txt
boxkite files create notes.txt --content "hi"
boxkite files edit notes.txt --old "hi" --new "hello"

# Hosted mode (against a control-plane you've deployed)
boxkite signup                              # signup -> login token -> create API key, in one step
# or: boxkite config set-url https://your-control-plane.example.com
#     boxkite config set-key bxk_live_...
boxkite session create --label "demo"                      # defaults to size=small, one sandbox
boxkite session create --label "build" --size medium \
  --storage-gb 20 --lifetime-minutes 120 --count 3          # optional overrides, all fair-use bounded
boxkite session ls
boxkite exec "python3 -c 'print(1 + 1)'"    # auto-picks the session if exactly one is active
boxkite exec "ls /workspace" --session <id> # or pick one explicitly
boxkite session rm <id>

boxkite log                                 # paginated exec/file-op audit history for the active session
boxkite log --limit 20 --offset 0 --session <id>
boxkite watch                               # live feed of new exec/file-op entries; blocks until Ctrl-C

boxkite allowlist get                       # unrestricted by default for every account
boxkite allowlist set ./allowed-commands.json
boxkite allowlist clear                     # back to unrestricted

--size is a CPU/memory preset (small by default, or medium/large); --storage-gb and --lifetime-minutes override the sandbox's volume size and how long it stays alive before the reaper tears it down; --count creates a batch of sandboxes in one call (default 1). All four are optional and capped by fair-use ceilings on your account, never a paid upgrade.

boxkite allowlist (hosted mode only) lets an account persist an opt-in command allowlist enforced on every future boxkite exec call: get shows the current rules (empty means unrestricted, the default for every account), set <path-to-json-file> replaces them wholesale from a JSON array of plain command-name strings or {command, args_allow?, args_deny?} objects, and clear restores the unrestricted default. This is a guardrail against accidental/unexpected commands, not a sandbox-escape boundary — allowing a general-purpose interpreter (python3, bash, node) through the allowlist still permits arbitrary code to run once it starts; the sandbox pod's own isolation (see "The isolation model" above) is what actually constrains what that code can do. See docs/API.md for the underlying /v1/account/allowed-commands endpoints.

boxkite log/boxkite watch (hosted mode only) read the same GET .../log/GET .../watch audit-trail routes the SDKs' get_log/watch methods call — see docs/SANDBOX-OBSERVABILITY-DESIGN.md and the "Audit log & takeover" developer guide for what's actually recorded. Like exec/files, both auto-pick the active session if exactly one exists, or take an explicit --session.

Hosted mode is session-scoped because the control-plane manages multiple concurrent sandboxes per account; local mode is intentionally not — a single boxkite up stack has no session concept of its own, so boxkite session */boxkite log/boxkite watch refuse to run against it with an explanation rather than inventing session management that doesn't exist locally. Run boxkite --help (or --help on any subcommand) for the full reference.

Cookbook / examples

examples/ has runnable, verified examples: a full LangGraph agent wired to all 5 sandbox tools, a minimal single-tool LangChain agent, native OpenAI function-calling with no agent framework at all, a LlamaIndex ReActAgent, Claude Code running headlessly inside a sandbox, plain curl/Python-requests scripts against the sidecar's raw HTTP API, and a walkthrough of the hosted control-plane API (signup -> sandbox -> exec -> teardown). Start there if you want working code to copy from rather than piecing it together from this README.

Docs map

This README covers the self-hosted boxkite package end to end. For everything else, start here:

  • docs/API.md — the control-plane's REST API (hosted mode): signup/login, API-key management, and sandbox-session lifecycle.
  • docs/EXTENDING.md — the two optional extension points (AuditSink, SessionMetadataStore) in more depth than the summary below.
  • docs/CONFIGURATION.md — every environment variable and config option across the sidecar, manager, and control-plane.
  • docs/BENCHMARKS.md — cold-start/warm-pool latency and throughput numbers, and how to reproduce them.
  • mcp-server/README.md — the MCP server that exposes a hosted control-plane as a native tool source for Claude Code, Claude Desktop, Cursor, and other MCP clients.
  • sdk-python/README.md — the Python client for a hosted control-plane (boxkite-client): sessions, exec, files, LangChain tools, over HTTP.
  • sdk-js/README.md — the TypeScript/JavaScript client for a hosted control-plane, mirroring the Python SDK for Node and browser, plus a Vercel AI SDK tool factory.
  • docs/CLAUDE-CODE-SANDBOX-QUICKSTART.md — running the Claude Code CLI headlessly inside a boxkite sandbox.
  • docs/E2B-COMPARISON.md — a research/scoping gap analysis against E2B, including the model-provider/agent-framework/MCP integration landscape and where boxkite already competes on it.
  • examples/README.md — the runnable cookbook: a full LangGraph agent, a minimal LangChain agent, native OpenAI/Gemini/Mistral/Groq function calling, a LlamaIndex agent, an OpenAI Agents SDK agent, Claude Code in a sandbox (both a hand-maintained image and the declarative-builder API path), raw HTTP calls, and a hosted control-plane walkthrough.

Extending it: AuditSink and SessionMetadataStore

boxkite works with zero external dependencies — every tool operates against the sidecar's own S3/Azure storage. Two optional hooks exist for callers who want more:

  • boxkite.audit.AuditSink — mirror file writes into your own system of record (a database-backed file browser, an audit log, a webhook). Every method is best-effort; a broken sink can never fail a sandbox operation.
  • boxkite.session_store.SessionMetadataStore — reconstruct session ownership (org/work-item IDs, storage prefix) if a pod is lost before its K8s labels/annotations can be read during error recovery. Most callers don't need this — K8s pod metadata already covers the common recovery path.

Both default to no-ops. Implement only what you need; see the docstrings in src/boxkite/audit.py and src/boxkite/session_store.py.

Ready-to-use, SQLite-backed reference implementations of both ship in the same modules — SQLiteAuditSink and SQLiteSessionMetadataStore — for callers who want a working durable store without writing one. See Extending boxkite for usage.

What's not included

This is a clean extraction, not a repackaging of a larger internal system. Deliberately left out:

  • Any specific agent-orchestration framework. The tools are plain LangChain @tool-decorated functions — wire them into LangGraph, a custom agent loop, or anything else that accepts LangChain tools.
  • A "skills injection" system. The sidecar's /ensure-skills endpoint (materializing read-only skill files under /mnt/skills) is included because it's genuinely part of the sandbox's own file model, but the policy of which skills to inject for which agent is your application's concern, not this package's.
  • Any database or auth layer. Session ownership lives on Kubernetes pod labels/annotations, which is enough to run this standalone. If you want cross-restart recovery after a pod is already gone, see SessionMetadataStore above.

License

FSL-1.1-Apache-2.0 — the Sentry-style Functional Source License. You can use, modify, and self-host boxkite for effectively any purpose except building a competing hosted/managed version of it. Every version converts automatically to the fully permissive Apache License 2.0 two years after its release. See LICENSE for the exact terms.

Contributing

See CONTRIBUTING.md — we use the Developer Certificate of Origin (git commit -s), not a CLA. Found a security issue? Please see SECURITY.md and report it privately rather than filing a public issue — this project executes arbitrary code, and a sandbox-escape report deserves a fast, private path.


  1. E2B and Modal don't publish their exact isolation primitive as part of this project's own docs — see each vendor's site for details. The comparison here is about self-hosting and tool-surface completeness, not the isolation technology itself. 2

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

boxkite_sandbox-0.1.0.tar.gz (270.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

boxkite_sandbox-0.1.0-py3-none-any.whl (183.2 kB view details)

Uploaded Python 3

File details

Details for the file boxkite_sandbox-0.1.0.tar.gz.

File metadata

  • Download URL: boxkite_sandbox-0.1.0.tar.gz
  • Upload date:
  • Size: 270.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for boxkite_sandbox-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4debabcdcb205a2399d5a9527327c89f8bdea9f217e715814bc083f1ff17dbb8
MD5 b13ba1547fecb2cd5225d05b8fcc2baa
BLAKE2b-256 6f49eb428b5f29b28e32176f486698aeef6293bc37870696fc633122b48db3bc

See more details on using hashes here.

File details

Details for the file boxkite_sandbox-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for boxkite_sandbox-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d1c77b7277fb19cbe60562cf1c5af07e48cb50084190799b3e9460b7d765239a
MD5 931376d83f43b7fda66d1b2cff9032a9
BLAKE2b-256 58a2d77b21623a01b869f45e3160028a9e2bd91286718d8382b59c24fb5e4d82

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page