---
title: "Introduction"
description: "Every agent deserves its own computer"
icon: "house"
---

AI agents run with whatever privileges you give them, and most of the time that's too many. They can see API keys sitting in the environment, reach the network freely (including cloud metadata at `169.254.169.254`), and nothing stops a prompt injection from running `rm -rf /` on the host filesystem. Containers help with some of this, but they share the host kernel, and namespace escapes are a well-documented class of vulnerability.

microsandbox takes a different approach: every sandbox is a real VM with its own Linux kernel. It provides security primitives for preventing exploits like secret exfiltration. And it **runs locally** on your machine.

<CodeGroup>

```rust Rust
use microsandbox::{Sandbox, NetworkPolicy};

let sb = Sandbox::builder("sandbox")
    .image("python")
    .memory(512)
    .cpus(2)
    .secret(|s| s
        .env("OPENAI_API_KEY")
        .value(std::env::var("OPENAI_API_KEY")?)
        .allow_host("api.openai.com")
    )
    .network(|n| n.policy(NetworkPolicy::public_only()))
    .create()
    .await?;
```

```typescript TypeScript
import { NetworkPolicy, Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("sandbox")
    .image("python")
    .memory(512)
    .cpus(2)
    .secret((s) =>
        s.env("OPENAI_API_KEY")
            .value(process.env.OPENAI_API_KEY!)
            .allowHost("api.openai.com"),
    )
    .network((n) => n.policy(NetworkPolicy.publicOnly()))
    .create();
```

```python Python
import os
from microsandbox import Network, Sandbox, Secret

sb = await Sandbox.create(
    "sandbox",
    image="python",
    memory=512,
    cpus=2,
    secrets=[
        Secret.env("OPENAI_API_KEY",
            value=os.environ["OPENAI_API_KEY"],
            allow_hosts=["api.openai.com"],
        ),
    ],
    network=Network.public_only(),
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "sandbox",
    m.WithImage("python"),
    m.WithMemory(512),
    m.WithCPUs(2),
    m.WithSecrets(
        m.Secret.Env("OPENAI_API_KEY", os.Getenv("OPENAI_API_KEY"),
            m.SecretEnvOptions{AllowHosts: []string{"api.openai.com"}},
        ),
    ),
    m.WithNetwork(m.NetworkPolicy.PublicOnly()),
)
```

</CodeGroup>

<Callout icon="bolt" color="#7C3AED">
  microsandbox spins up lightweight VMs in **under a second**, right from your code on your machine. **No servers, no daemons**, no infrastructure to manage.
</Callout>

## The basics

- **Under 100ms boot.** Sandboxes boot in under 100 milliseconds.
- **No daemon.** The runtime spawns directly as a child process of whatever application creates the sandbox. No root, no server, no background service.
- **Any OCI image.** Docker Hub, GHCR, ECR, GCR, or any OCI-compatible registry. Shared layers are deduplicated and only stored once.
- **Cross-platform.** Native on macOS (Apple Silicon) and Linux (KVM). Same CLI, same SDKs.
- **Multi-language SDKs.** Rust, TypeScript, Python, and Go, all with consistent APIs.
- **Programmable.** Network policies, filesystem hooks, secret bindings, and TLS interception are all configured from the host side through the SDK.

## What makes it different

### Secrets that can't leak

Most sandboxes protect secrets by restricting what the guest can do. microsandbox goes further: **the guest never has the secret at all**.

Instead of injecting real credentials into the sandbox, microsandbox generates random placeholders. The actual value never enters the VM. The only way it reaches the outside world is when a request goes to an allowed host, at which point microsandbox swaps the placeholder for the real value. Everywhere else, the placeholder is just a meaningless string.

So even with full code execution inside the sandbox, there's nothing to exfiltrate. The credential was never there. DNS rebinding protection and cloud metadata blocking kick in automatically when secrets are configured, closing the remaining side channels.

<CodeGroup>

```rust Rust
let sb = Sandbox::builder("sandbox")
    .image("python")
    .secret_env("OPENAI_API_KEY", api_key, "api.openai.com")
    .create()
    .await?;
```

```typescript TypeScript
await using sb = await Sandbox.builder("sandbox")
    .image("python")
    .secretEnv("OPENAI_API_KEY", process.env.OPENAI_API_KEY!, "api.openai.com")
    .create();
```

```python Python
sb = await Sandbox.create(
    "sandbox",
    image="python",
    secrets=[
        Secret.env("OPENAI_API_KEY",
            value=os.environ["OPENAI_API_KEY"],
            allow_hosts=["api.openai.com"],
        ),
    ],
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "sandbox",
    m.WithImage("python"),
    m.WithSecrets(
        m.Secret.Env("OPENAI_API_KEY", os.Getenv("OPENAI_API_KEY"),
            m.SecretEnvOptions{AllowHosts: []string{"api.openai.com"}},
        ),
    ),
)
```

</CodeGroup>

### Programmable network layer

Every packet leaving a sandbox passes through a networking stack controlled entirely from the host side, where policy is enforced at the IP, DNS, or HTTP level. From inside the sandbox it looks like a normal network interface, but on the host side you decide what gets through.

There's no underlying host network to bridge to, no rules to circumvent, and no way for the guest to reach around the filter.

<CodeGroup>

```rust Rust
let policy = NetworkPolicy::builder()
    .default_deny()
    .egress(|e| e.allow_domains(["api.openai.com", "pypi.org"]))
    .build()?;

let sb = Sandbox::builder("sandbox")
    .image("python")
    .network(|n| n.policy(policy))
    .create()
    .await?;
```

```typescript TypeScript
import { Destination, NetworkPolicy, Rule, Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("sandbox")
    .image("python")
    .network((n) => n.policy({
        defaultEgress: "deny",
        defaultIngress: "allow",
        rules: [
            Rule.allowEgress(Destination.domain("api.openai.com")),
            Rule.allowEgress(Destination.domain("pypi.org")),
        ],
    }))
    .create();
```

```python Python
from microsandbox import Network, NetworkPolicy, Rule, Sandbox

sb = await Sandbox.create(
    "sandbox",
    image="python",
    network=Network(
        policy=NetworkPolicy(rules=(
            Rule.allow(destination="api.openai.com"),
            Rule.allow(destination="pypi.org"),
        )),
    ),
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "sandbox",
    m.WithImage("python"),
    m.WithNetwork(&m.NetworkConfig{
        DefaultEgress: m.PolicyActionDeny,
        Rules: []m.PolicyRule{
            {Action: m.PolicyActionAllow, Direction: m.PolicyDirectionEgress, Destination: "api.openai.com"},
            {Action: m.PolicyActionAllow, Direction: m.PolicyDirectionEgress, Destination: "pypi.org"},
        },
    }),
)
```

</CodeGroup>

### Extensible filesystem backends <sup><sup>coming soon</sup></sup>

The host controls what the guest sees at the filesystem level. Mount host directories, use managed volumes, or attach custom filesystem backends entirely. Hooks let you intercept reads and writes to do things like encrypt everything going to disk or proxy to remote storage, and the guest never knows the difference.

<CodeGroup>

```rust Rust
let sb = Sandbox::builder("encrypted")
    .image("python")
    .volume("/secrets", |v| v
        .bind("/data/secrets")
        .on_read(move |_path, data| decrypt(data, &key))
        .on_write(move |_path, data| encrypt(data, &key))
    )
    .create().await?;
```

```typescript TypeScript
// Filesystem hooks (onRead / onWrite) are coming soon for Node. For now,
// you can mount the host directory directly:
await using sb = await Sandbox.builder("encrypted")
    .image("python")
    .volume("/secrets", (m) => m.bind("/data/secrets"))
    .create();
```

```python Python
from microsandbox import Sandbox, Volume

key = get_encryption_key()
sb = await Sandbox.create(
    "encrypted",
    image="python",
    volumes={
        "/secrets": Volume.bind("/data/secrets",
            on_read=lambda path, data: decrypt(data, key),
            on_write=lambda path, data: encrypt(data, key),
        ),
    },
)
```

```go Go
// Mount the host directory directly.
sb, err := m.CreateSandbox(ctx, "encrypted",
    m.WithImage("python"),
    m.WithMounts(map[string]m.MountConfig{
        "/secrets": m.Mount.Bind("/data/secrets", m.MountOptions{}),
    }),
)
```

</CodeGroup>

### Snapshot and reuse setup state

Capture a stopped sandbox's writable upper layer as a portable artifact, then boot fresh sandboxes from it. Install dependencies once, snapshot, and `msb run --snapshot ...` workers that skip the setup phase. The artifact is a directory you can `scp` between machines or bundle into a `.tar.zst`. Snapshots today are disk-only and stopped-only; live VM-state snapshots are tracked as future work.

<CodeGroup>

```rust Rust
let sb = Sandbox::builder("baseline")
    .image("python")
    .create()
    .await?;

sb.exec("pip", ["install", "-r", "requirements.txt"]).await?;
sb.stop_and_wait().await?;

// Snapshot the stopped sandbox under a bare name
let h = Sandbox::get("baseline").await?;
let _snap = h.snapshot("after-pip-install").await?;

// Boot 10 workers from the same snapshot (each gets its own upper layer)
let workers: Vec<Sandbox> = futures::future::try_join_all(
    (0..10).map(|i| async move {
        Sandbox::builder(format!("worker-{i}"))
            .from_snapshot("after-pip-install")
            .create()
            .await
    })
).await?;
```

```typescript TypeScript
import { Sandbox } from "microsandbox";

const sb = await Sandbox.builder("baseline").image("python").create();
await sb.exec("pip", ["install", "-r", "requirements.txt"]);
await sb.stopAndWait();

const h = await Sandbox.get("baseline");
await h.snapshot("after-pip-install");

// Boot 10 workers from the same snapshot
const workers = await Promise.all(
    Array.from({ length: 10 }, (_, i) =>
        Sandbox.builder(`worker-${i}`).fromSnapshot("after-pip-install").create(),
    ),
);
```

```python Python
import asyncio
from microsandbox import Sandbox

sb = await Sandbox.create("baseline", image="python")
await sb.exec("pip", ["install", "-r", "requirements.txt"])
await sb.stop_and_wait()

h = await Sandbox.get("baseline")
await h.snapshot("after-pip-install")

# Boot 10 workers from the same snapshot
workers = await asyncio.gather(
    *(Sandbox.create(f"worker-{i}", snapshot="after-pip-install") for i in range(10))
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "baseline", m.WithImage("python"))
_, err = sb.Exec(ctx, "pip", []string{"install", "-r", "requirements.txt"})
_, err = sb.StopAndWait(ctx)
_ = sb.Close()

h, err := m.GetSandbox(ctx, "baseline")
_, err = h.Snapshot(ctx, "after-pip-install")

// Boot 10 workers from the same snapshot
workers := make([]*m.Sandbox, 10)
for i := range workers {
    workers[i], err = m.CreateSandbox(ctx,
        fmt.Sprintf("worker-%d", i),
        m.WithSnapshot("after-pip-install"),
    )
}
```

```bash CLI
msb run --name baseline --detach python
msb exec baseline -- pip install -r requirements.txt
msb stop baseline

msb snapshot create after-pip-install --from baseline

# Boot 10 workers from the snapshot
for i in $(seq 1 10); do
    msb run --name worker-$i --detach --snapshot after-pip-install
done
```

</CodeGroup>

### Spawn sandboxes from sandboxes <sup><sup>coming soon</sup></sup>

Code running inside a sandbox can spawn peer sandboxes alongside itself. This is designed for multi-agent systems where agents need to create sub-environments. Peer sandboxes inherit nothing by default (their own network, filesystem, secrets), and the orchestrator coordinates via the SDK, not shared state. If the code isn't running inside a microsandbox, the spawn call fails safely.

### Composable plugin system <sup><sup>coming soon</sup></sup>

Extend microsandbox with in-process Rust plugins or out-of-process plugins in any language. Plugins hook into lifecycle events, exec calls, filesystem operations, or network traffic. They compose naturally, so you can stack an audit logger, a rate limiter, and a network monitor without them knowing about each other.

### Bidirectional events <sup><sup>coming soon</sup></sup>

Guest processes can emit typed events to the host, and the host can send events back. No special libraries required in the guest since any language can write JSON to a socket. This enables structured communication beyond stdin/stdout, like progress reporting, task completion signals, or custom RPC.

<CodeGroup>

```rust Rust
// Receive events from guest
let _sub = sb.on_event("task.progress", |event: EventData| {
    if let Ok(p) = event.data.unwrap().deserialized::<Progress>() {
        println!("[{}%] {}", p.percent, p.message);
    }
});

// Send events to guest
sb.emit("task.start", TaskConfig {
    input_file: "/data/input.txt".into(),
    output_dir: "/data/output".into(),
}).await?;
```

```typescript TypeScript
// Receive events from guest
const sub = sb.onEvent("task.progress", (event: EventData) => {
    const { percent, message } = event.data as { percent: number; message: string }
    console.log(`[${percent}%] ${message}`)
})

// Send events to guest
await sb.emit("task.start", { inputFile: "/data/input.txt", options: ["--verbose"] })
```

```python Python
# Receive events from guest
sub = sb.on_event("task.progress", lambda event: (
    print(f"[{event.data['percent']}%] {event.data['message']}")
))

# Send events to guest
await sb.emit("task.start", {
    "input_file": "/data/input.txt",
    "output_dir": "/data/output",
})
```

```go Go
// Bidirectional events are not available in the current SDKs yet.
```

</CodeGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="bolt" href="/getting-started/quickstart">
    Get a sandbox running in under 5 minutes
  </Card>

  <Card title="Sandbox overview" icon="box" href="/sandboxes/overview">
    Configuration, rootfs sources, and lifecycle
  </Card>

  <Card title="CLI reference" icon="square-terminal" href="/cli/overview">
    Manage sandboxes from the terminal
  </Card>

  <Card title="SDK reference" icon="code" href="/sdk/rust">
    Full API documentation for all SDKs
  </Card>
</CardGroup>
