hyperbox

An execution sandbox for coding agents.

HyperBox is an MCP server that hands an agent a disposable, network-sealed container. Generated code runs there and the result is observed β€” real stdout, real stderr, a real exit code β€” rather than assumed by the model that wrote it.

$ pip install hyperbox-mcp

The problem

An agent is not a credible witness to its own work

An agent writes code, then reports whether it works. Those are the same party. Nothing independent sits in between, which is why generated diffs so reliably look correct and aren't.

Running that code somewhere is the obvious fix, and where it runs matters: on the developer's machine it has their filesystem, their credentials and their network. HyperBox is the somewhere β€” a container the agent can use freely and destroy, whose output it cannot author.

Scope, stated up front. This is developer containment β€” protection against a generated script doing something careless. It is not a VM, not kernel-level isolation, and not a boundary for running genuinely hostile third-party code. What that means exactly β†’

Architecture

Four tools at the front, one swappable runtime at the back

The MCP layer never speaks to a container engine. Everything above the Runtime protocol deals in sandbox ids and results; everything below it deals in containers. Today one implementation satisfies that protocol.

MCP client β€” Claude Code, Cursor… stdio Β· JSON-RPC server.py β€” MCP surface create_sandbox run get_process_logs destroy_sandbox policy.py every limit, one place validate.py checked at the boundary registry.py β€” SQLite ownership, survives restart Runtime protocol β€” the seam NativeRuntime β†’ engine REST API docker Β· podman Β· auto β€” no execution SDK beneath
Note the dashed seam. Above it nothing knows what a container is; below it nothing knows what MCP is. NativeRuntime is the only implementation that exists today β€” the protocol is an architectural boundary, not a plugin ecosystem.

Execution

One sandbox per task, many runs inside it

A sandbox is created once, reused across runs, and destroyed when the task ends. The filesystem and installed packages persist between runs; in-memory state does not, because each run is a fresh process.

create_sandbox state: creating provision install declared packages, then seal + verify ready recorded in registry run(code) stdout Β· stderr exit_code Β· success timed_out Β· language exit_code β‰  0 β†’ read the real error, fix, run again destroy_sandbox confirmed against engine …or idle past its TTL β†’ garbage collected
The loop on the right is the point: a non-zero exit returns the real error, not a summary of it, so the next attempt is informed. Cleanup has two paths β€” an explicit destroy_sandbox, and a sweep that reclaims anything left idle, so an abandoned sandbox is not a leak.

Network model

Dependencies install in a window that closes before your code runs

Packages declared at creation install while the sandbox can still reach a package index. The network is then detached β€” and the container's real configuration is read back from the engine and compared against policy. If it cannot be proven sealed, the sandbox is destroyed rather than handed back.

network OPEN β€” provisioning only install packages declared at create_sandbox() no agent-submitted code has run yet detach network configuration change verify read config back off the engine and compare mismatch β†’ destroy, raise network SEALED β€” everything the agent submits runs here no route off the machine Β· loopback still works, so a server started in the sandbox can be called from it
The distinction that matters: provisioning access is not execution access. And sealing is verified rather than assumed β€” an accepted configuration is not proof of an applied one, so the state is read back from the engine before the sandbox is considered ready.

Security

What is enforced, and what is not claimed

container boundary agent-submitted code memory 1 GB cpu 1 core processes 128 pids per run 60 s max host filesystem unreachable engine socket not mounted outbound network detached after provisioning shared host kernel β€” this is a container, not a VM
Every control shown here is asserted by the test suite against a real container, including a fork bomb stopping at the process ceiling and an allocation loop being OOM-killed with a legible reason.

What HyperBox provides

  • No access to the host filesystem β€” nothing is mounted in
  • No access to the Docker or Podman socket
  • No outbound network once provisioning is done, verified by reading the container's real configuration back
  • Memory, CPU and process-count ceilings applied by the engine, and read back after creation
  • A per-run timeout that kills the process group, with the kill verified
  • Cleanup that survives a server restart, because ownership lives in a registry on disk
  • Credentials excluded from sync by name β€” .env, id_rsa, .netrc, .aws/ and others, with every exclusion reported

What it does not

  • No VM, microVM or gVisor boundary β€” containers share the host kernel
  • Not a multi-tenant boundary; don't run untrusted third-party code as a service
  • Code in the default images runs as root inside the container
  • Packages come from public indexes and are not vetted
  • No protection against a kernel exploit or a container escape
If you need a boundary against genuinely adversarial code, you want a VM or microVM sandbox β€” not a local container, from HyperBox or anyone else. Full detail in docs/security.md.

Real projects

Verify the actual repository, not a retyped snippet

create_sandbox(sync_from=…) copies a directory from the developer's machine into the sandbox, so an agent can run a real test suite against real code. It is off until a human enables it by running hyperbox init in a directory they choose β€” an agent cannot widen its own boundary.

BehaviourDetail
Never copied.env, .envrc, id_rsa, id_ed25519, .netrc, .npmrc, .pypirc, credentials, .git-credentials
Never descended.aws/, .ssh/, .gnupg/, .docker/
Always skipped.git, node_modules, .venv, venv, __pycache__
Your rules.hyperboxignore β€” deliberately not .dockerignore, which excludes exactly the tests you want to run
ReportedEvery skip appears in the result with a reason; nothing is dropped silently
Ceiling2,000 files / 64 MB
SemanticsA copy taken once, at creation β€” not a live mount. Editing the file afterwards does not reach the sandbox

Quick start

Install, point a client at it, run something

Requires Python 3.11+ and either Docker or Podman running.

# 1. install
pip install hyperbox-mcp          # or: uv tool install hyperbox-mcp

# 2. check the machine and pre-pull the base image, so a
#    multi-gigabyte download never happens inside a tool call
hyperbox doctor --pull

# 3. print a ready-to-paste MCP client config
hyperbox config --format json      # also: --format cursor | antigravity | yaml

Merge that into your client's config and restart it β€” MCP servers are read at launch. Then ask the agent to use HyperBox. A typical exchange:

create_sandbox(language="python", packages=["requests"])
  β†’ {"sandbox_id": "6f5eaaefb938", "network": "sealed …", …}

run(sandbox_id="6f5eaaefb938", code="print(2 + 2)")
  β†’ {"stdout": "4\n", "exit_code": 0, "success": true, "language": "python"}

destroy_sandbox(sandbox_id="6f5eaaefb938")
  β†’ {"status": "destroyed"}

To run a real project instead, enable syncing once with hyperbox init in that directory, then pass sync_from at creation. Full setup, including per-client instructions, is in docs/setup.md.

MCP surface

Four tools, one resource, one prompt

Deliberately small. Adding to this list requires an argument, not just a use case.

NameKindPurpose
create_sandboxtool Create a sealed container. Takes language, backend, environment, packages, sync_from. Returns a sandbox_id.
runtool Execute code. Returns stdout, stderr, exit_code, success, timed_out, language. With background=True, returns a process_id instead.
get_process_logstool Read what a background run has printed so far.
destroy_sandboxtool Tear it down. Idempotent, and confirmed against the engine before reporting success.
hyperbox://capabilitiesresource The real limits and supported surface, so an agent can read them instead of discovering them by failing.
run_safelyprompt The verification workflow, worded so an agent doesn't claim success it hasn't seen.

Five languages, each on an official tagged image: python (python:3.12-slim), javascript (node:22-slim), bash (debian:bookworm-slim), go (golang:1.23-bookworm) and java (eclipse-temurin:21-jdk).

Direction

From running code to producing evidence

Today HyperBox produces an observable result. The direction is to make that result richer and cheaper to obtain β€” without widening the tool surface for its own sake.

Today

  • Sealed, disposable sandboxes across five languages
  • Real stdout, stderr and exit codes from a real process
  • Declared packages installed before sealing, version-pinnable in each language's own syntax
  • A whole project synced in and its real test suite run
  • Background processes, with loopback between runs
  • Ownership that survives a restart, and two-way garbage collection

Planned not built

  • Git-based sync β€” send only what changed, and get a minimal diff back
  • Server-authored inspection tools, so a repo can be examined without running agent code
  • A create β†’ provision β†’ seal lifecycle split, once inspection makes it coherent
  • Structured check results rather than raw output
  • Execution artifacts and evidence
Nothing in the right-hand column exists yet, and nothing there has a date. The changelog is the record of what actually shipped.