- Introduce a new Chrome image for the sandbox, enabling VNC access with real Chrome and CDP support (amd64 only). - Update README to reflect the addition of the Chrome image and its build instructions. - Modify build script to include a dedicated build process for the Chrome image. - Enhance .gitignore to exclude the new Chrome plan file. Co-authored-by: Cursor <cursoragent@cursor.com>
221 lines
9.9 KiB
Markdown
221 lines
9.9 KiB
Markdown
# 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
|