# Yao Sandbox

Sandbox provides persistent Docker containers as isolated execution environments for external CLI agents like Claude Code.

## Overview

The sandbox module enables Yao to safely run external AI coding agents (like Claude CLI) in isolated Docker containers. Each user+chat session gets its own container with:

- Persistent workspace for code and dependencies
- IPC communication via Unix sockets
- Resource limits (CPU, memory)
- Security isolation
- **VNC remote desktop** for visual transparency (optional)

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                        Yao Server                            │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                   Sandbox Manager                       │  │
│  │                                                         │  │
│  │   - GetOrCreate(userID, chatID) → container             │  │
│  │   - Exec/Stream commands in container                   │  │
│  │   - Filesystem operations (read, write, copy)           │  │
│  │                                                         │  │
│  └────────────────────────┬────────────────────────────────┘  │
│                           │                                   │
│  ┌────────────────────────┴────────────────────────────────┐  │
│  │                    VNC Proxy Service                     │  │
│  │                                                          │  │
│  │   - GET /v1/sandbox/{id}/vnc        → VNC status        │  │
│  │   - GET /v1/sandbox/{id}/vnc/client → noVNC page        │  │
│  │   - GET /v1/sandbox/{id}/vnc/ws     → WebSocket proxy   │  │
│  └──────────────────────────────────────────────────────────┘  │
│                           │                                   │
│           ┌───────────────┼───────────────┐                   │
│           ▼               ▼               ▼                   │
│    ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐       │
│    │ sandbox- │ │ sandbox- │ │ sandbox- │ │ sandbox- │       │
│    │ claude   │ │ browser  │ │ desktop  │ │ chrome   │       │
│    │ (No VNC) │ │ (VNC)    │ │ (VNC)    │ │ (VNC)    │       │
│    └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘       │
│         │            │            │            │              │
│    ─────┴────────────┴────────────┴────────────┴────         │
│                   Unix Socket IPC                             │
│             (one socket per container)                        │
└───────────────────────────────────────────────────────────────┘
```

## Quick Start

### Build Docker Images

```bash
cd sandbox/docker

# Build base image
./build.sh claude

# Build VNC-enabled images
./build.sh browser      # Browser (Playwright) + Fluxbox + VNC
./build.sh desktop      # XFCE Desktop + VNC
./build.sh chrome       # Real Chrome + CDP + VNC (amd64 only)

# Build all images
./build.sh all
```

### Usage

```go
import "github.com/yaoapp/yao/sandbox"

// Create manager
config := sandbox.DefaultConfig()
config.Init("/path/to/yao/data")

manager, err := sandbox.NewManager(config)
if err != nil {
    log.Fatal(err)
}
defer manager.Close()

// Get or create container
container, err := manager.GetOrCreate(ctx, "user123", "chat456")
if err != nil {
    log.Fatal(err)
}

// Execute command
result, err := manager.Exec(ctx, container.Name, []string{"echo", "hello"}, nil)
fmt.Println(result.Stdout) // "hello\n"

// Write file
err = manager.WriteFile(ctx, container.Name, "/workspace/test.txt", []byte("content"))

// Read file
data, err := manager.ReadFile(ctx, container.Name, "/workspace/test.txt")
```

## Configuration

### Environment Variables

| Variable                       | Default                             | Description                                    |
| ------------------------------ | ----------------------------------- | ---------------------------------------------- |
| `YAO_SANDBOX_IMAGE`            | `yao/sandbox-claude:latest`         | Docker image                                   |
| `YAO_SANDBOX_WORKSPACE`        | `{YAO_DATA_ROOT}/sandbox/workspace` | Workspace directory                            |
| `YAO_SANDBOX_IPC`              | `{YAO_DATA_ROOT}/sandbox/ipc`       | IPC socket directory                           |
| `YAO_SANDBOX_MAX`              | `100`                               | Max concurrent containers                      |
| `YAO_SANDBOX_IDLE_TIMEOUT`     | `30m`                               | Idle timeout                                   |
| `YAO_SANDBOX_MEMORY`           | `2g`                                | Memory limit                                   |
| `YAO_SANDBOX_CPU`              | `1.0`                               | CPU limit                                      |
| `YAO_SANDBOX_VNC_PORT_MAPPING` | `false`                             | Enable VNC port mapping (for Docker Desktop)   |

### Docker Desktop (macOS/Windows)

Docker Desktop runs containers in a LinuxKit VM, so container IPs are not directly accessible from the host. Enable VNC port mapping for local development:

```bash
export YAO_SANDBOX_VNC_PORT_MAPPING=true
export YAO_SANDBOX_IMAGE="yaoapp/sandbox-claude-browser:latest"
```

When enabled, VNC ports (6080, 5900) are automatically mapped to random available host ports on `127.0.0.1`.

## Docker Images

| Image                                      | VNC | Description                           |
| ------------------------------------------ | --- | ------------------------------------- |
| `yaoapp/sandbox-base:latest`               | ❌  | Base image with git, curl, yao-bridge |
| `yaoapp/sandbox-claude:latest`             | ❌  | + Claude CLI, Node.js 20, Python 3.11 |
| `yaoapp/sandbox-claude:full`               | ❌  | + Go 1.23                             |
| `yaoapp/sandbox-claude-browser:latest`     | ✅  | + Playwright, Fluxbox, VNC (~3.4GB)   |
| `yaoapp/sandbox-claude-desktop:latest`     | ✅  | + XFCE Desktop, VNC (~3.1GB)          |
| `yaoapp/sandbox-claude-chrome:latest`      | ✅  | + Real Chrome, CDP, PyAutoGUI, VNC (~3.4GB, amd64 only) |

## IPC Communication

Sandbox containers communicate with Yao via Unix sockets using the MCP (Model Context Protocol) JSON-RPC format. The `yao-bridge` binary inside containers bridges stdio ↔ socket.

Supported methods:

- `initialize` - Handshake
- `tools/list` - List available tools
- `tools/call` - Execute a tool

## VNC Remote Desktop

VNC-enabled images (playwright, desktop) provide real-time visibility into Claude's operations.

### API Endpoints

| Endpoint                        | Description                        |
| ------------------------------- | ---------------------------------- |
| `GET /v1/sandbox/{id}/vnc`      | VNC status (ready/starting/unavailable) |
| `GET /v1/sandbox/{id}/vnc/client` | noVNC HTML client page            |
| `GET /v1/sandbox/{id}/vnc/ws`   | WebSocket proxy to container VNC   |

### View Modes

- **Interactive** (default): User can use keyboard and mouse
- **View-only** (`?viewonly=true`): User can only watch

For detailed design, see [DESIGN-PLAYWRIGHT-VNC.md](./DESIGN-PLAYWRIGHT-VNC.md).

## Directory Structure

```
sandbox/
├── bridge/          # yao-bridge source
├── docker/          # Dockerfiles and build script
│   ├── base/
│   ├── claude/
│   ├── browser/     # Browser (Playwright) + VNC image
│   ├── desktop/     # XFCE Desktop + VNC image
│   ├── chrome/      # Real Chrome + CDP + VNC image (amd64 only)
│   │   ├── config/  # Chrome preferences, stealth scripts
│   │   └── tests/   # LLM-driven browser automation demos
│   ├── vnc/         # Shared VNC scripts
│   └── build.sh
├── ipc/             # IPC system
│   ├── manager.go
│   ├── session.go
│   └── types.go
├── vncproxy/        # VNC proxy service
│   ├── proxy.go
│   ├── config.go
│   └── proxy_test.go
├── config.go        # Configuration
├── errors.go        # Error types
├── helpers.go       # Helper functions
├── manager.go       # Main manager
└── types.go         # Type definitions
```

## Testing

```bash
# Load environment variables first
source env.local.sh

# Unit tests (no Docker required)
go test -v ./sandbox/... -run "^Test.*Validation|^Test.*Generation|^Test.*Parsing"

# All tests (requires Docker)
go test -v ./sandbox/...

# VNC proxy tests only
go test -v ./sandbox/vncproxy/...
```

## Security

- Containers run as non-root user
- `--cap-drop ALL` removes all capabilities
- `no-new-privileges` prevents privilege escalation
- Only workspace directory is mounted
- Per-session IPC sockets with authorized tools only
