Run untrusted Python safely

eryx is an open-source WebAssembly sandbox running full CPython 3.14. Embed it from Python, JavaScript or Rust, and let sandboxed code await exactly the host functions you choose to expose.

app.py
import eryx

def get_user(args):
    return {"id": args["id"], "name": "Alice"}

sandbox = eryx.Sandbox(callbacks=[
    {"name": "get_user", "fn": get_user},
])

# Untrusted code: no filesystem, no network,
# only the callbacks you hand it
result = sandbox.execute('''
user = await get_user(id=123)
print(f"Hello {user['name']}")
''')
print(result.stdout)  # Hello Alice

e·ryx noun

  1. A genus of sand boas: small, non-venomous snakes that spend most of their lives buried in sand, watching the world through eyes set high on their heads.
  2. A sandbox where Python does the same. Buried in WebAssembly, it only sees what you choose to put above the surface.

What's in the sandbox

Real CPython 3.14

Not a subset. The full standard library and SQLite work, and you can add packages from wheels. Native extensions like numpy are experimental.

Async host callbacks

Expose host functions that sandboxed code can await, including several at once with asyncio.gather. Tools from MCP servers can be exposed the same way.

Locked down by default

No host filesystem and no network unless you enable them. Every run has limits on time, memory, instructions executed and callback count.

Opt-in networking

Allow only the hosts you list, with wildcards. Localhost and private ranges stay blocked unless you opt in. requests, httpx and urllib just work.

Secrets that don't leak

Sandboxed code only ever sees a placeholder. The real value is swapped in for requests to allowed hosts, and placeholders are redacted from output and files.

Sessions & filesystem

Variables persist across runs for REPL-style use, and state can be snapshotted and restored. Files written to the virtual filesystem stay for the session, and you can mount host directories read-only or read-write.

Fast startup

A precompiled, pre-initialized runtime creates new Python sandboxes in about 1–5ms. A sandbox pool keeps warm instances ready for heavy throughput.

Visibility & control

Stream stdout and stderr live, trace execution line by line, cancel long runs, and get a structured result back as JSON.

How callbacks work

1

You expose functions

Pick which host functions the sandbox may call

2

Code hits await

The sandbox suspends and calls into your host

3

Your function runs

Query a database, call an API, ask a human

4

Execution resumes

The result comes back as an ordinary Python value

Install

Pick your host language. The Python, Rust and JavaScript packages all ship with the runtime built in.

Python

PyPI
pip install pyeryx

# or try it with nothing installed
uvx --with pyeryx eryx -c 'print("hi")'

Rust

crates.io
cargo add eryx --features embedded

let sandbox = Sandbox::embedded().build()?;
let result = sandbox.execute("print(1 + 1)").await?;

JavaScript

npm
npm install @bsull/eryx

# Browser and Node; needs JSPI
# (Chrome/Edge 133+, Node --experimental-wasm-jspi)

gRPC server

Docker
docker run -p 50051:50051 sd2k/eryx-server

# Shared warm pool, callback replay and
# suspension, TLS/mTLS, Prometheus metrics

Built for agents

Give an agent a Python sandbox over MCP, or let sandboxed code call your existing MCP tools.

eryx as an MCP server

// Claude Desktop, Cursor, VS Code, ...
{
  "mcpServers": {
    "eryx": {
      "command": "uvx",
      "args": ["--with", "pyeryx[serve]",
               "eryx", "serve"]
    }
  }
}

Exposes a run_python tool backed by a persistent session.

MCP tools inside the sandbox

# Servers are discovered from your Claude Code,
# Cursor, VS Code, Zed, Gemini or Codex config
repos = await mcp["github"].search_repos(
    query="eryx sandbox"
)
for r in repos["items"][:5]:
    print(r["full_name"])

An agent can chain many tool calls in one run instead of a model round trip for each.

Hosted eryx

We're exploring a hosted eryx API so you don't have to run sandboxes yourself. It isn't available yet. Leave your email if you'd like to hear when it is.

One email when it's ready. Nothing else.