boxy

module
v0.1.58 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: AGPL-3.0

README

Boxy

Boxy is a resource pooling and sandbox orchestration tool. It pre-provisions pools of VMs, containers, and other resources, then assembles them into on-demand sandboxes for labs, training, pentesting, and development environments.

Install

Release installers are available for Windows PowerShell, Linux, and macOS. They download the newest published GitHub release, verify it against the published checksums.txt, and install it into a user-local bin directory.

Windows PowerShell:

irm https://raw.githubusercontent.com/Geogboe/boxy/main/scripts/install.ps1 | iex

Linux / macOS:

curl -fsSL https://raw.githubusercontent.com/Geogboe/boxy/main/scripts/install.sh | sh

See docs/install.md for supported platforms, version pinning, environment overrides, PATH behavior, and verification details.

Developer Quickstart

task test             # Run the full Go test suite
task lint             # Run CI-equivalent Go linting
task generate         # Regenerate schemas, installers, and API documentation

Use gopls format -w <files> and gopls check <file> while editing Go. User-facing CLI changes must update docs/cli-wireframe.md and the bundled skill under internal/skills/assets/boxy-cli/. The REST reference lives in docs/api.md.

How It Works

Boxy keeps pools of generic, ready-to-use resources warm ahead of time. When a user requests a sandbox, resources are pulled from pools and personalized via hooks — credentials are set, networking is configured, and connection info is returned. The user connects with their native client (SSH, RDP, SMB, etc.). Boxy is not a proxy.

flowchart TB
    client(["boxy CLI / REST client"]) -- "REST + mTLS" --> api

    subgraph daemon["boxy serve"]
        api["REST API + web dashboard"]
        core["Core: Pool Manager, Sandbox Manager<br/>PolicyController (reconciler)"]
        embedded["Embedded local agent<br/>(Docker, Hyper-V drivers)"]
        api --> core --> embedded
    end

    embedded --> providers[("Local providers<br/>Docker, Hyper-V, ...")]
    remote["boxy agent (remote host)"] -. "gRPC + mTLS, agent dials out" .-> core
    remote --> remoteProviders[("Remote providers")]

A remote agent mode (boxy agent, connecting to the server over gRPC from a separate host) is implemented with the push model and full mTLS; see ADR-0005. The embedded local agent inside boxy serve remains available for local providers.


Core Domain Model

The canonical vocabulary for this model is in docs/domain-language.md.

Resource — A runtime record of a provisioned instance (VM, container, share, network, etc.). Has an ID, type, state, provider handle, and properties. Resources are single-use: once allocated to a sandbox they are never returned to a pool (ADR-0002). Resources retain immutable origin_pool provenance and a mutable current_pool ownership so promotion can move inventory without losing history.

Pool — A named, homogeneous inventory of pre-provisioned resources. Declared in config with a policy and either inline provisioning fields or a reusable template: reference.

Template — A reusable resource shape containing provider/source configuration and resource packages. Templates may extend one parent template; derived pools can promote surplus resources from an ancestor pool and apply the package delta.

Package — An immutable, parameterized configuration artifact applied at a lifecycle event. A package has a method (shell, powershell, or a future method), a list of scopes, events, and inputs. A script is only one possible package input; it is not the domain object.

Sandbox — A user-facing environment containing 1..N resources drawn from pools. Sandbox creation is asynchronous on the server: the API persists a sandbox request in pending, the reconcile loop fulfills it, and the sandbox transitions to ready or failed. Allocation-scoped packages can configure each resource with sandbox parameters before it is returned to the user. Boxy returns connection info; it is not a proxy.

Provider — An external system that provides resources (Docker, Hyper-V, Podman, VMware, etc.). Providers have a type that maps to a driver. Provider connection details (socket, host, certs) are owned by the agent, not the server. Drivers auto-discover their environment where possible.

Driver — Code that knows how to talk to a specific provider type. Interprets pool provisioning config. Lives in pkg/providersdk/drivers/. Drivers auto-discover their environment (e.g., Docker checks for local socket, Hyper-V discovers via PowerShell). A pool's type field maps directly to a driver (e.g., type: docker → Docker driver, type: hyperv → Hyper-V driver). Docker pools automatically pull a missing image on first provision instead of requiring a manual docker pull.

Agent — The runtime entity that transports provider operations using drivers. Two forms, both real today: embedded (local) runs in-process inside boxy serve and handles providers declared in server.providers; remote (distributed) is a separate boxy agent serve process that dials the daemon over gRPC with full mTLS (a push model — the agent connects out, the server never dials in) and executes provider operations on its own host. Agents are dumb pipes: the server resolves templates, packages, credentials, and policy; agents do not resolve package references or make lifecycle decisions. See ADR-0005.

The agent is the execution layer — Boxy core delegates all provider IO through the agent, never directly to drivers. The Provisioner interface is the agent seam.

PolicyController — The reconciler. Runs on a tick inside boxy serve. Compares desired pool state (from policy) to actual state and triggers promotion, provisioning, or destruction via the agent. Stateless and idempotent — every tick re-derives what's needed from scratch. One controller reconciles all pools.

Event — A lifecycle boundary such as provision, promotion, or allocation at which matching packages are applied. Notifications remain separate from configuration packages; package application is part of the controlled lifecycle flow.


Architecture

Server, CLI, and Agent (Vault-like model)

Single binary (boxy), three modes:

boxy serve                     — daemon: pool reconciler, REST API, gRPC agent server
boxy <command>                 — CLI client: talks to daemon via REST
boxy agent                     — distributed agent: connects to server via gRPC

REST API — for CLI-to-server communication. Standard HTTP REST, always served alongside the web dashboard by boxy serve (no separate api.enabled gate). TLS (Boxy's own private CA by default) and bearer API-key auth with user/auditor/admin roles are on by default; see ADR-0007.

gRPC over TLS — for remote agent-to-server communication. Bidirectional streaming lets an agent dial the server (NAT/firewall friendly) while the server pushes work down the stream. See ADR-0005.

Pool Routing

A pool's type field identifies the provider type (e.g., docker, hyperv, podman, vmware). The system routes work to any agent — embedded or remote — that has a matching provider. The abstract resource category (container, VM) is derived from the driver's capabilities, not declared on the pool.

If multiple agents support the same provider type, the system picks a capable agent. An optional agent: field on the pool can pin it to a specific agent when needed.

Reconciliation Flow
serveLoop ticker
    └─ pool.Manager.Reconcile(pool)
           observes: pool has 1 ready, policy says min_ready=3
           gap: need 2 more (background preheat target only)
           └─ Provisioner.Provision(pool)  ← agent impl
                  └─ driver.CreateVM / CreateContainer

sandbox fulfillment (on demand, not on a timer)
    └─ pool.Manager.EnsureReady(pool, count=1)
           admission is gated on count=1 — min_ready never blocks this
           └─ Provisioner.Provision(pool), synchronously — may still
                  best-effort top up toward min_ready even past count=1,
                  and a failure doing that can surface as an error here
                  even though count=1 was already satisfied (see #249)
Pool Build Cache (Cross-Pool Resource Reuse)

The provisioner can "steal" surplus resources from other pools when they share compatible config, instead of building from scratch. Compatibility is discovered automatically at runtime — no explicit base: references between pools.

Matching rules:

  • Same type, config is a subset → cache hit
  • Surplus only: steal from Pool X only if X.ready > X.policy.preheat.min_ready
  • If a match is found, take the resource and apply the delta (install packages, configure, etc.)
  • If no match, build from scratch

The config comparison is structural — Boxy core compares the opaque config blobs without understanding their contents. YAML anchors (&/*) can be used for DRY in the config file without creating Boxy-level coupling.

Post-Allocation Hooks

When a resource moves from a pool into a sandbox, hooks run to personalize it:

  • Set user credentials
  • Configure hostname/networking
  • Apply sandbox-specific policies

Resources in pools are intentionally generic (no specific user, no credentials). Hooks make them specific at allocation time. This means credentials don't exist until allocation — they are generated/set by the hook and returned as connection info.

Async Sandbox API Flow

Sandbox creation is a server-side async workflow:

  1. Client POSTs a sandbox request to /api/v1/sandboxes
  2. Server persists the sandbox and returns 202 Accepted with status: "pending"
  3. The daemon reconcile loop provisions and allocates resources
  4. Client polls GET /api/v1/sandboxes/{id} until status becomes ready or failed
sequenceDiagram
    actor Client
    participant API as REST API
    participant Reconciler as Reconcile loop

    Client->>API: POST /api/v1/sandboxes {requests}
    API-->>Client: 202 Accepted {status: pending}

    loop every reconcile tick
        Reconciler->>Reconciler: provision + allocate resources
    end

    loop poll until settled
        Client->>API: GET /api/v1/sandboxes/{id}
        API-->>Client: status: pending / provisioning
    end
    Client->>API: GET /api/v1/sandboxes/{id}
    API-->>Client: status: ready (connection info) or failed

The create API uses resource requests rather than allocated resource IDs:

{
  "name": "pentest-lab",
  "requests": [
    {"type": "container", "profile": "kali", "count": 3},
    {"type": "container", "profile": "ubuntu-targets", "count": 1}
  ]
}
Sandbox Access Model

Boxy is not a proxy. When a sandbox reaches ready, Boxy returns connection info for each resource:

  • SSH host/port/key for Linux VMs
  • RDP address for Windows VMs
  • SMB path for file shares
  • Container exec/attach details
  • etc.

The user connects with their native client. Connection info is generated by post-allocation hooks.


Configuration

Server Config (boxy.yaml)

The main sections are server, templates, packages, and pools. templates and packages are optional; old pool-only files remain valid. Remote agents are not config-declared — a boxy agent serve process dials the daemon and registers itself (push model, see ADR-0005).

server:
  listen: ":9090"
  providers: [docker, hyperv]

packages:
  baseline:
    version: 1.0.0
    method: powershell
    scopes: [resource]
    events: [provision, promotion]
    inputs:
      inline: |
        Write-Output "baseline"
      parameters:
        Environment: lab

templates:
  windows-base:
    type: vm
    provider: hyperv-local
    config:
      template: "Windows Server 2022 Standard"
      generation: 2
      cpu: 4
      memory_mb: 8192
      disk_gb: 80
      network_switch: "LabSwitch"
    packages: [baseline@1.0.0]
  windows-apps:
    extends: windows-base

pools:
  - name: win2022-base
    template: windows-base
    policy:
      preheat:
        min_ready: 5
        max_total: 10
      recycle:
        max_age: 168h

  - name: win2022-apps
    template: windows-apps
    policy:
      preheat:
        min_ready: 2
        max_total: 5

  - name: kali
    type: docker
    config:
      image: kalilinux/kali-rolling
      command: ["/bin/bash"]
    policy:
      preheat:
        min_ready: 3
        max_total: 8

Key design decisions:

  • server.providers declares what the embedded local agent handles. Drivers auto-discover connection details (socket paths, PowerShell, etc.) — no connection config needed.
  • server.grpc_cert_sans (repeatable CLI flag equivalent: --grpc-cert-san) adds extra DNS names/IPs to the agent gRPC server certificate's SANs, on top of the always-included localhost/127.0.0.1/listen-host entries — needed when remote agents connect through a passthrough route or load balancer using an external DNS name. The flag fully overrides the config value when passed (does not merge). Changing this takes effect automatically on the next boxy serve start — see ADR-0005 for the full gRPC transport/TLS design.
  • Pool type is the abstract resource category boxy provisions: container, vm, or share (""/container/docker all resolve to a container pool — see ResolvePoolExpectedType). provider picks which driver instance actually fulfills it (e.g. hyperv-local, docker-local) — omit it and boxy resolves by provider type across all available agents.
  • Pool config: is an opaque blob interpreted by the driver. Different providers expose different config options.
  • templates are reusable desired resource shapes; pools own inventory policy and may override template fields for a particular pool.
  • Config is stateless and declarative and is read once on startup. Runtime state (resources, sandboxes) lives in the state store — see State Store below.
Pool Policy Structure
policy:
  preheat:
    min_ready: N       # target number of ready resources
    max_total: N       # hard cap across ready + allocated resources from this pool
  recycle:
    max_age: "168h"    # destroy and replace unused resources older than this

Implementation note: preheat/recycle planning logic is intentionally kept in internal/pool (not exposed as a public pkg/ API) because this policy is Boxy-specific domain behavior rather than a generic reusable SDK contract.

Sandbox Definitions (.sandbox.yaml)

Sandbox classes are defined in separate files, not in the server config. A sandbox definition specifies which pools to draw resources from and how many:

# pentest-lab.sandbox.yaml
name: pentest-lab
resources:
  - pool: kali
    count: 3
  - pool: ubuntu-targets
    count: 1

Sandboxes are instantiated via CLI:

# From a file (primary path)
boxy sandbox create -f pentest-lab.sandbox.yaml

# Return after the daemon accepts the request instead of waiting for ready/failed
boxy sandbox create -f pentest-lab.sandbox.yaml --no-wait

The file-based path is the primary, repeatable, version-controlled way. The CLI compiles pool references from the spec into daemon API requests, submits them to boxy serve, and waits for a terminal sandbox status by default. If a matching pool has exhausted its max_total hard cap, the sandbox request fails with status: "failed" rather than provisioning beyond the cap.

See examples/ for complete configurations.


State Store

Runtime state (resources, sandboxes, pool inventory) is persisted via the pkg/store.Store interface. Today that's DiskStore — a plain JSON file (.boxy/state.json by default) — plus an in-memory implementation used in tests. A bbolt-backed implementation was the original plan and may still land, but isn't implemented; DiskStore's own doc comment notes it exists specifically so the CLI works end-to-end without pulling in a new dependency until that's needed. Config (boxy.yaml) is NOT stored in the state store — it's read fresh on every boxy serve startup.


CLI Surface

See docs/cli-wireframe.md for the canonical CLI reference with flags and example output.

boxy init                               — create starter boxy.yaml in current directory
boxy serve                              — start the daemon (API server + reconcile loop)
boxy status                             — check server health and summary
boxy config validate                    — validate config file and exit
boxy sandbox create -f <file>           — create sandbox from a spec file (waits by default; use --no-wait to return after acceptance)
boxy sandbox list                       — list sandboxes
boxy sandbox get <id>                   — get sandbox details
boxy sandbox delete <id>                — delete a sandbox (waits by default; use --no-wait to return after acceptance)
boxy sandbox extend <id> <duration>     — push a sandbox's auto-destroy expiry further out
boxy sandbox exec <id> -- <command>      — execute a one-shot command with live output (`--events` for NDJSON, `--buffered` for one final response)
boxy login --server <addr>               — store an API key in the OS keyring
boxy logout --server <addr>              — remove the stored API key
boxy admin api-key create                — create an API key (admin)
boxy agent list                          — list agents and connection status
boxy agent token create                  — create registration token
boxy agent revoke <id>                   — revoke an agent

Pools are config-driven — no boxy pool create command. Pool state is observable via the API and web dashboard.


Project Layout

cmd/boxy/                      — entry point
internal/
  cli/                         — CLI command wiring (cobra) + HTTP API client helpers
  config/                      — config loading and validation
  pool/                        — pool manager + Provisioner interface
  sandbox/                     — sandbox manager, async deletion/expiry reconciler
  server/                      — HTTP API handlers + embedded web dashboard
  skills/                      — bundled coding-agent skill assets
pkg/
  agentsdk/                    — Agent interface + embedded (in-process) implementation
  model/                       — core domain types (Resource, Pool, Sandbox)
  policycontroller/            — reconciler (public, self-contained)
  providersdk/                 — driver interface + capabilities (public API for driver authors)
    providers/
      docker/
      hyperv/
      devfactory/               — in-memory reference driver used for tests/local dev
  resourcepool/                — generic pool data structure (public utility)
  store/                       — store interface + DiskStore (JSON) / in-memory impls

internal/ = Boxy's private business logic. pkg/ = self-contained, no internal/ dependencies. The compiler enforces this boundary.


Open Questions

  • Config reloads: Is restart required on config change, or should boxy serve watch for changes and reconcile? Restart is simpler; hot reload is nicer.
  • Subset matching for build cache: Structural comparison of opaque config blobs to determine if one is a "subset" of another. What are the exact semantics? Is shallow key comparison sufficient, or do we need deep structural comparison?
  • Multi-agent routing: When multiple agents support the same provider type, how does the server choose? Round-robin? Load-based? Labels? For now, agent: pinning on the pool is the escape hatch.

Status

Early development. See the GitHub issue tracker for design details on upcoming work — notably #124 (an open design discussion on reframing "sandbox" toward a job/scheduler model for long-running and interactive workloads).

Contributing

Not yet open to outside code contributions — issues and questions are welcome though. See CONTRIBUTING.md.

License

Copyright (c) 2026 Geogboe. Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).

AGPL-3.0 permits personal, internal, and commercial use freely. If you modify Boxy and let users interact with the modified version over a network — including running it as an internal tool, not just as public-facing SaaS — you must make that modified source available to those users. See the full license text for the exact terms.

Boxy was previously licensed under Apache 2.0; the relicense to AGPL-3.0 took effect 2026-08-28 (see ADR-0015). No external contributors existed at the time of the change.

Directories

Path Synopsis
cmd
api-doc-gen command
Command api-doc-gen generates the checked-in REST API reference.
Command api-doc-gen generates the checked-in REST API reference.
boxy command
schema-gen command
internal
agentserver
Package agentserver implements the server side of the AgentTransport gRPC service: registration (single-use token or mTLS client cert), heartbeat tracking, and command dispatch to connected remote agents.
Package agentserver implements the server side of the AgentTransport gRPC service: registration (single-use token or mTLS client cert), heartbeat tracking, and command dispatch to connected remote agents.
auth
Package auth contains operator API-key generation and authentication helpers.
Package auth contains operator API-key generation and authentication helpers.
buildcfg
Package buildcfg defines project-level constants used by the install script generator and the self-update command.
Package buildcfg defines project-level constants used by the install script generator and the self-update command.
cli
internal/cli/agent_service.go
internal/cli/agent_service.go
credentials
Package credentials stores Boxy operator credentials in the operating system's keyring rather than in Boxy configuration or state files.
Package credentials stores Boxy operator credentials in the operating system's keyring rather than in Boxy configuration or state files.
server
Package server provides Boxy's REST API and optional web dashboard.
Package server provides Boxy's REST API and optional web dashboard.
svcmgr
Package svcmgr installs, uninstalls, starts, stops, and queries boxy processes (agent or server) as OS-managed background services: a real Windows Service via the Service Control Manager, a Windows Task Scheduler at-logon task as an unprivileged fallback, or a systemd system/user unit on Linux.
Package svcmgr installs, uninstalls, starts, stops, and queries boxy processes (agent or server) as OS-managed background services: a real Windows Service via the Service Control Manager, a Windows Task Scheduler at-logon task as an unprivileged fallback, or a systemd system/user unit on Linux.
userconfig
Package userconfig provides the shared per-user Boxy configuration root.
Package userconfig provides the shared per-user Boxy configuration root.
pkg
agentsdk
Package agentsdk defines the contract between the Boxy server and agents.
Package agentsdk defines the contract between the Boxy server and agents.
artifact
Package artifact defines the common identity and registry seam for Boxy's typed immutable artifacts.
Package artifact defines the common identity and registry seam for Boxy's typed immutable artifacts.
diagnostics
Package diagnostics provides bounded, redacted operational log storage.
Package diagnostics provides bounded, redacted operational log storage.
diskjson
Package diskjson provides a generic, mutex-guarded, atomically-written JSON file store for a single value.
Package diskjson provides a generic, mutex-guarded, atomically-written JSON file store for a single value.
eventstream
Package eventstream provides generic primitives for bounded event streaming.
Package eventstream provides generic primitives for bounded event streaming.
graph
Package graph contains small reusable graph primitives used by Boxy domain models that need explicit dependency traversal.
Package graph contains small reusable graph primitives used by Boxy domain models that need explicit dependency traversal.
httpjson
Package httpjson provides generic JSON HTTP response helpers.
Package httpjson provides generic JSON HTTP response helpers.
humanize
Package humanize converts raw machine values into human-presentable strings — comma-grouped integers today; the same category of helper as byte-size ("4.2 MB"), relative-time ("3 days ago"), or ordinal ("1st") formatting belongs here too as those needs come up (see dustin/go-humanize for the shape of a mature version of this idea).
Package humanize converts raw machine values into human-presentable strings — comma-grouped integers today; the same category of helper as byte-size ("4.2 MB"), relative-time ("3 days ago"), or ordinal ("1st") formatting belongs here too as those needs come up (see dustin/go-humanize for the shape of a mature version of this idea).
lifecycle
Package lifecycle provides generic event-driven lifecycle primitives.
Package lifecycle provides generic event-driven lifecycle primitives.
model
Package model contains Boxy's core domain data models.
Package model contains Boxy's core domain data models.
pki
Package pki generates and loads the private certificate authority used to secure agent<->server gRPC connections (see docs/adr/0005-remote-agent-transport-and-registration.md).
Package pki generates and loads the private certificate authority used to secure agent<->server gRPC connections (see docs/adr/0005-remote-agent-transport-and-registration.md).
policycontroller
Package policycontroller provides a small, reusable Observe → Decide → Act loop.
Package policycontroller provides a small, reusable Observe → Decide → Act loop.
providersdk
pkg/providersdk/availability.go
pkg/providersdk/availability.go
providersdk/builtins
Package builtins registers the built-in provider drivers with a Registry.
Package builtins registers the built-in provider drivers with a Registry.
providersdk/guestcred
Package guestcred contains provider-neutral primitives for generating caller-deliverable guest credentials.
Package guestcred contains provider-neutral primitives for generating caller-deliverable guest credentials.
providersdk/providers/devfactory
Package devfactory provides a reference implementation of the providersdk.Driver interface.
Package devfactory provides a reference implementation of the providersdk.Driver interface.
providersdk/providers/docker
Package docker provides a providersdk.Driver backed by the local docker CLI.
Package docker provides a providersdk.Driver backed by the local docker CLI.
providersdk/providers/hyperv
Package hyperv provides a providersdk.Driver for Microsoft Hyper-V. The agent must run on the Hyper-V host with Administrator privileges; no remote connection config is needed.
Package hyperv provides a providersdk.Driver for Microsoft Hyper-V. The agent must run on the Hyper-V host with Administrator privileges; no remote connection config is needed.
psdirect
Package psdirect implements vmsdk.GuestExec via PowerShell Direct using the go-psrp library's native PSRP/HvSocket transport.
Package psdirect implements vmsdk.GuestExec via PowerShell Direct using the go-psrp library's native PSRP/HvSocket transport.
resourcepack
Package resourcepack plans and applies immutable, parameterized resource configuration packages.
Package resourcepack plans and applies immutable, parameterized resource configuration packages.
resourcepool
Package resourcepool provides a small, reusable, homogeneous collection type with an attached (opaque) policy.
Package resourcepool provides a small, reusable, homogeneous collection type with an attached (opaque) policy.
secrets
Package secrets provides explicit server-owned secret backends.
Package secrets provides explicit server-owned secret backends.
vmsdk
Package vmsdk provides hypervisor-agnostic VM guest communication interfaces and implementations.
Package vmsdk provides hypervisor-agnostic VM guest communication interfaces and implementations.
Package scripts contains integration tests for the boxy install scripts.
Package scripts contains integration tests for the boxy install scripts.
generate command
Command generate renders install.sh and install.ps1 from templates using the shared constants in internal/buildcfg.
Command generate renders install.sh and install.ps1 from templates using the shared constants in internal/buildcfg.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL