# Sandbox Quick Start

Kernel-level process isolation in a few lines of Go.

## Self-Sandboxing (Linux)

On Linux, a process can restrict itself using Landlock. Restrictions are irreversible once applied.

```go
package main

import (
    "fmt"
    "log"
    "os"

    "github.com/openparallax/openparallax/sandbox"
)

func main() {
    sb := sandbox.New()

    // Check if the sandbox mechanism is available.
    if !sb.Available() {
        fmt.Println("Sandbox not available on this platform/kernel")
        os.Exit(1)
    }
    fmt.Printf("Sandbox mechanism: %s\n", sb.Mode())

    // Define what this process is allowed to do.
    cfg := sandbox.Config{
        AllowedReadPaths:  []string{"/workspace", "/usr/lib", "/etc/hosts"},
        AllowedWritePaths: []string{"/workspace/output"},
        AllowedTCPConnect: []string{"api.anthropic.com:443"},
        AllowProcessSpawn: false,
    }

    // Apply restrictions. This is irreversible.
    if err := sb.ApplySelf(cfg); err != nil {
        log.Fatalf("Failed to apply sandbox: %s", err)
    }
    fmt.Println("Sandbox applied. Restrictions are now in effect.")

    // Verify with canary probes.
    result := sandbox.VerifyCanary()
    fmt.Printf("Status: %s (%d/%d probes blocked)\n",
        result.Status, result.Blocked(), result.Blocked()+result.Failed())

    // From here on, the process cannot:
    // - Read files outside /workspace, /usr/lib, /etc/hosts
    // - Write files outside /workspace/output
    // - Connect to any host except api.anthropic.com:443
    // - Spawn child processes

    // This will succeed:
    data, err := os.ReadFile("/workspace/config.yaml")
    if err != nil {
        fmt.Printf("Read allowed file: error (unexpected): %s\n", err)
    } else {
        fmt.Printf("Read allowed file: OK (%d bytes)\n", len(data))
    }

    // This will fail with a permission error:
    _, err = os.ReadFile("/etc/shadow")
    if err != nil {
        fmt.Printf("Read blocked file: blocked (expected): %s\n", err)
    }
}
```

## Sandboxing a Child Process (macOS)

On macOS, the parent process wraps the child spawn with `sandbox-exec`:

```go
package main

import (
    "fmt"
    "log"
    "os/exec"

    "github.com/openparallax/openparallax/sandbox"
)

func main() {
    sb := sandbox.New()
    if !sb.Available() {
        log.Fatal("Sandbox not available")
    }

    // Prepare the child command.
    cmd := exec.Command("./my-agent", "--workspace", "/workspace")

    // Define restrictions.
    cfg := sandbox.Config{
        AllowedReadPaths:  []string{"/workspace", "/usr/lib"},
        AllowedWritePaths: []string{"/workspace/.openparallax"},
        AllowedTCPConnect: []string{"127.0.0.1:50051"},
    }

    // Wrap the command to run inside the sandbox.
    // On macOS, this rewrites cmd.Path to sandbox-exec and prepends
    // the profile file argument.
    if err := sb.WrapCommand(cmd, cfg); err != nil {
        log.Fatalf("Failed to wrap command: %s", err)
    }

    // Start the sandboxed child.
    if err := cmd.Start(); err != nil {
        log.Fatalf("Failed to start: %s", err)
    }

    fmt.Printf("Child process started in sandbox (PID: %d)\n", cmd.Process.Pid)
    _ = cmd.Wait()
}
```

## Checking Platform Capabilities

```go
package main

import (
    "encoding/json"
    "fmt"

    "github.com/openparallax/openparallax/sandbox"
)

func main() {
    status := sandbox.Probe()
    data, _ := json.MarshalIndent(status, "", "  ")
    fmt.Println(string(data))
}
```

Output on Linux with kernel 6.8:

```json
{
  "active": true,
  "mode": "landlock",
  "version": 4,
  "filesystem": true,
  "network": true
}
```

Output on macOS:

```json
{
  "active": true,
  "mode": "sandbox-exec",
  "filesystem": true,
  "network": true
}
```

Output on Windows:

```json
{
  "active": true,
  "mode": "job-object",
  "filesystem": false,
  "network": false
}
```
