Skip to content

Sandbox

Concept

Letting a model write code and then running that code is useful and dangerous in proportion. The danger is not usually malice; it is a data-analysis snippet that reads a file it should not, or loops forever, or pip-installs something at runtime.

Sandbox is the boundary. The default provider runs code in a subprocess with a timeout, no network, a controlled environment and an optional memory cap. Execution emits SANDBOX_EXECUTED with the exit code, output and duration, so what ran is in the run log.

The provider is an interface. The subprocess backend is the local default; a container or microVM backend implements the same protocol.

Minimal example

from rewyn.runtime import create

result = create().execute("print(sum(range(10)))")
print(result.stdout, result.exit_code)

Production example

from rewyn.runtime import create, sandbox_tool
from rewyn.tools import MaxRiskLevel, RiskLevel

box = create(
    timeout=15.0,
    network="none",
    memory_limit_mb=512,
    env={"DATA_DIR": "/tmp/analysis"},
    inherit_env=[],  # inherit nothing from the parent process
)

result = await box.aexecute(generated_code)
if result.exit_code != 0 or result.timed_out:
    log.warning("sandbox failed", extra={"stderr": result.stderr[:2000]})

# Or give the agent code execution as a tool, under the same policy as any other.
agent = Agent(
    model=...,
    tools=[sandbox_tool(box)],
    permission_policy=MaxRiskLevel(RiskLevel.MEDIUM),
)

inherit_env=[] is the important line. By default a subprocess inherits the parent environment, which is where your API keys live.

Containers

For code you did not write, a subprocess is not enough. The container provider runs the same protocol with real isolation: every capability dropped, a read-only root, a kernel-enforced memory cap including swap, a non-root user, and only the scratch directory mounted.

from rewyn.runtime import ContainerProvider, sandbox

sandbox.set_default_provider(ContainerProvider(image="python:3.13-slim"))

It shells out to the runtime rather than taking a client dependency, so Docker, Podman and nerdctl all work. For genuinely hostile code, point runtime at a microVM runtime such as gVisor or Kata; nothing else changes.

API reference

rewyn/runtime/sandbox.py for Sandbox, SandboxSpec, SandboxResult, SandboxProvider, SubprocessSandbox, create, acreate and sandbox_tool. rewyn/runtime/container.py for ContainerProvider and ContainerSandbox.

Failure modes

The code times out. SandboxResult reports it rather than raising. Read exit_code and timed_out instead of assuming success.

Network calls fail. network="none" is the default, deliberately. Set network="allow" only when the task genuinely needs it, and understand that you have removed the main containment.

A subprocess is not a security boundary. It bounds accidents well and determined escapes poorly. Use ContainerProvider for untrusted code.

The container runtime is installed but nothing runs. available checks that the binary is on PATH, not that the daemon is up. A stopped daemon surfaces as a non-zero exit code with the runtime's message in stderr.

Secrets leak into the sandbox. Set inherit_env explicitly. The default subprocess environment is not empty.

Output is truncated. Large stdout is capped to keep events reasonable. Have the code write to a file and return a path.