SecondBox

module
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT

README

SecondBox

Durable, isolated development sandboxes — as a service you run yourself.

CI Release License

SecondBox runs untrusted workloads — AI agents, user code, plugins, CI jobs, long-lived dev environments — inside isolated Sandboxes whose filesystems survive between sessions. The supported production backend boots each Sandbox as a Firecracker microVM; an experimental gVisor backend serves hosts without KVM.

  • Hardware isolation. On the supported production path every Sandbox is a Firecracker microVM, not a container. The experimental gVisor backend substitutes a userspace-kernel sentry for hosts without hardware virtualization; its isolation boundary is the sentry, not KVM.
  • Durable workspaces. A Sandbox keeps its disk across stops, restarts, and generations. Snapshot it and restore in place.
  • Real terminals. A genuine PTY with raw mode, resize forwarding, and bounded reconnect — not a line-buffered exec loop.
  • Multi-tenant by construction. Every row is scoped to an opaque tenant and subject reference. Application tokens carry fixed scopes and explicit Profile grants.
  • Immutable Profiles. Operators fix image, resources, lifecycle, network, and port policy. Each Sandbox pins the revision resolved at creation.
  • Self-hosted. One unprivileged control plane, PostgreSQL, and one or more privileged runners you place yourself.

[!NOTE] SecondBox is a networked control plane, not an embeddable library. Sandboxes run on separately deployed runners, and a client only ever talks to the control plane over HTTPS. There is no daemonless mode.

How it works

A Sandbox is the durable public resource; the Instance running it is replaceable compute fenced to one Sandbox generation. Each Sandbox is placed at creation on one home Runner, whose reflink-capable filesystem owns that Sandbox's Workspace and local Snapshots. Ordinary lifecycle and automatic recovery never relocate it. An operator may relocate a stopped Sandbox with no retained Snapshots through the explicit asynchronous relocation operation.

secondboxd stores desired state in PostgreSQL. Workspace bytes stay on the owning Runner except while secondboxd forwards a bounded, in-memory stream for an explicit stopped-Sandbox relocation; it never persists those bytes.

Getting started

Guided single-host install

The guided installer turns one qualified Linux amd64 systemd host into a loopback-only development deployment with PostgreSQL, the control plane, and one same-host Firecracker Runner. It verifies a published release, records every accepted path and authority decision, and finishes by running a hello-world command inside a microVM.

The host needs Docker Engine with Compose v2, cgroup v2, accessible KVM and TUN devices, hardware virtualization, at least 6 logical CPUs and 12 GiB of memory. Runner storage needs at least 65 GiB: 50 GiB for the durable-coding Workspace, approximately 11 GiB for verified execution assets, and a 4 GiB margin. Use a dedicated non-root XFS/Btrfs filesystem with that capacity, or let the installer create a fully allocated Btrfs image of at least 65 GiB; the image choice additionally needs its full allocation plus the reviewed control-service, download, and backing reserves on /var/lib. Check the host without changing it:

secondbox-deploy install --check

To fetch the small published bootstrap and run the wizard:

curl -fsSL https://github.com/SecondStack-AI/SecondBox/releases/latest/download/install.sh | sh

To update a completed guided deployment after stopping every Sandbox, pass its recorded operation directory to the latest bootstrap:

curl -fsSL https://github.com/SecondStack-AI/SecondBox/releases/latest/download/install.sh \
  | sh -s -- update /absolute/path/to/secondbox-install-operation

Run the same command with update --check first for read-only compatibility, drift, and staging-capacity validation. The guided updater accepts completed v0.6.0 or newer installations; v0.6.0 is a clean-install boundary, so earlier installations require a fresh deployment with explicit workload migration. Updates preserve the existing PostgreSQL volume, generated authority, Runner identity, Workspaces, Snapshots, storage, ports, and Compose project. The v1 updater rejects releases that change runtime or toolchain bundle digests because existing Sandboxes remain pinned to their immutable Profile revisions.

The bootstrap downloads only the release-pinned Linux amd64 secondbox-deploy binary to a temporary directory, verifies its embedded SHA-256 digest, and dispatches the requested install or update operation. It does not invoke sudo or modify the host itself. The installer shows its exact privileged action list before asking sudo to run its narrow host-preparation entry point.

If you do not pipe scripts into a shell, download and inspect the same assets first:

install_dir=$(mktemp -d)
cd "$install_dir"
curl -fLO https://github.com/SecondStack-AI/SecondBox/releases/latest/download/install.sh
curl -fLO https://github.com/SecondStack-AI/SecondBox/releases/latest/download/SHA256SUMS
grep '  install.sh$' SHA256SUMS | sha256sum -c -
less install.sh
sh install.sh

See guided single-host installation for every prerequisite, wizard choice, created resource, recovery command, and durability boundary.

Other deployment paths

The guided path is deliberately Linux amd64, same-host, loopback-only, and development-mode. It does not replace these separate workflows:

  • Download individual release binaries or SDKs from the latest release when you only need a client.
  • Follow deployment and runtime operations for production authority, remote Runners, or a manually reviewed same-host topology.
  • For a manually reviewed same-host Runner declaration, start with secondbox-deploy runner-template; it emits every required Runner-host path and authority field without installing anything.
  • Use just deploy-development-up .tmp/secondbox-development for control-plane-only source-checkout development. That topology has synthetic development artifact identity and cannot execute a Sandbox until a real Runner and verified assets are configured.
  • Follow the microVM image pipeline and release distribution when producing or independently materializing execution assets.
Control-plane-only start
just deploy-development-up .tmp/secondbox-development

This creates one private, versioned secondbox.toml, generates unique referenced secrets, compiles a protected environment transport, and starts the reviewed loopback PostgreSQL and control-plane topology. The generated environment is never operator input. This topology is useful for control-plane development and API work, but it cannot execute a Sandbox. Read deployment and runtime operations before exposing the API, configuring production, or enrolling a Runner.

Install the CLI

Use the release binary above, or build the current checkout from source:

go build -o ./dist/secondbox ./cmd/secondbox
Log in once
secondbox login \
  --url https://secondbox.example.com \
  --token "$SECONDBOX_PLATFORM_TOKEN" \
  --tenant-ref acme \
  --subject-ref alice

Credentials are verified against the deployment before anything is written, then stored at mode 0600. Every later command resolves them from the first source that has them: an explicit flag, then SECONDBOX_URL / SECONDBOX_TOKEN / SECONDBOX_TENANT_REF / SECONDBOX_SUBJECT_REF, then that file. secondbox whoami shows what resolved and from where; it never prints the token.

Run something
secondbox run durable-coding -- python3 -c 'print("hello from a microVM")'

Using the CLI

Terminal presentation and scripts

Interactive terminals receive compact summaries, width-aware tables, and lifecycle status on stderr. Pipes and redirects retain the existing machine bytes. Global presentation flags precede the command:

secondbox --output plain --color never sandboxes list
secondbox --output json sandboxes list | jq '.items[] | .id'
secondbox --accessible login

--output auto|json|plain chooses automatic TTY presentation, original JSON, or an unstyled human view. --color auto|always|never controls ANSI color; NO_COLOR disables automatic color, and SECONDBOX_ACCESSIBLE=1 is equivalent to --accessible. Raw file/log responses, generic operation output, Docker Compose output, and all guest streams ignore human rendering. See the complete CLI output contract before using a command in automation.

One-off commands

run creates a Sandbox, waits for it, runs one command, and deletes it:

secondbox run durable-coding -- python3 -c 'print("hello")'
secondbox run durable-coding --shell -- 'ls -la /workspace && whoami'
echo 'piped in' | secondbox run durable-coding --stdin -- cat

The guest's stdout and stderr land on your two streams, unmerged, and its exit status becomes the CLI's exit status — so secondbox run … -- false exits 1 and prints nothing of its own, exactly like a local command.

Named Sandboxes
# Create one and keep it
secondbox run durable-coding --name my-box --keep -- true

# Address it by name from any machine
secondbox exec my-box --shell -- 'printf "hello from my-box\n" > /workspace/hello.txt'
secondbox exec my-box -- cat /workspace/hello.txt
secondbox shell my-box

Names are the reserved metadata key secondbox.dev/name, unique per tenant and subject and resolved server-side — so the same name works from anywhere, with nothing cached locally. A deleted Sandbox releases its name.

A Sandbox created without a source Snapshot starts with its own empty durable Workspace. Populate /workspace through the File API or guest commands; SecondBox never mounts or copies the caller's host checkout implicitly.

run, exec, and shell accept a name or an opaque sbx_… identifier, telling them apart by the identifier prefix. The transport-level commands below take the identifier only; secondbox sandboxes list shows both.

Interactive shell
secondbox run durable-coding --tty              # throwaway shell, deleted on exit
secondbox run durable-coding --tty -- /bin/bash # choose the shell
secondbox shell my-box                              # attach to one that already exists
secondbox shell my-box --command /bin/bash --detachable

run --tty is the docker run -it --rm shape: it creates a Sandbox, waits for it, drops you into a terminal, and deletes it when you disconnect — including on a dropped connection, since the Sandbox exists only for that session. Add --keep to retain it and reconnect later with secondbox shell.

shell resolves the name, applies the Sandbox's current generation, acquires and renews a Lease for the session, and releases it on exit. You get a real PTY: raw mode, local dimensions, SIGWINCH forwarding, byte-exact binary I/O, and your terminal restored on exit, cancellation, or transport failure.

Every value it supplies is an overridable default — pass --lease, --generation, or --session and yours wins.

Everything else

The remaining commands are thin transport over the published API — repeatable --path, --query, and --header pairs, and --body taking a file or -:

secondbox sandboxes list

secondbox files read --path sandboxId=sbx_123 --query path=/workspace/out.txt \
  --header SecondBox-Generation=4

secondbox snapshots create --path sandboxId=sbx_123 \
  --header 'If-Match="revision-5"' --header Idempotency-Key=$(uuidgen) \
  --body ./snapshot.json

secondbox exec stream --sandbox sbx_123 --generation 4 \
  --idempotency-key $(uuidgen) --request ./stream.json

Routes that mutate a Sandbox require both Idempotency-Key and an If-Match revision validator; secondbox sandboxes get reports the current revision.

secondbox operation <operationId> reaches any route in the table directly. Local operator commands — logs tail, logs follow, diagnostics bundle, timings summary — need no API credentials for the log routes.

Full reference: SDK, CLI, and Flue quick starts.

SDKs

Go and TypeScript share one handwritten composition layer over generated transports and wire types: idempotency keys, bounded-wait looping, lease keepers that renew in the background, outcome decoding, and run.

client, _ := secondboxclient.NewSecondBoxSubjectClient(
    "https://secondbox.example.com", token, "acme", "alice", http.DefaultClient)

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

handle, outcome, err := client.Run(ctx, secondboxclient.RunRequest{
    Profile: "durable-coding",
    Command: secondboxclient.Command{ArgvCommand: &secondboxclient.ArgvCommand{
        Mode: "argv", Executable: "python3", Arguments: []string{"-c", "print('hello')"},
    }},
    DeadlineMilliseconds: 30_000,
    MaximumOutputBytes:   1 << 20,
})
fmt.Print(string(outcome.Result.Stdout))
_ = handle // Run never deletes; disposal is yours
TypeScript
import { SecondBox, SecondBoxClient } from "@secondstack-ai/secondbox";

const api = new SecondBox(
  new SecondBoxClient("https://secondbox.example.com", token, fetch, "acme", "alice"),
);

const { handle, result } = await api.run({
  profile: "durable-coding",
  command: {
    mode: "argv",
    executable: "python3",
    arguments: ["-c", "print('hello')"],
  },
  deadlineMilliseconds: 30_000,
  maximumOutputBytes: 1_048_576,
  readyTimeoutMilliseconds: 300_000,
});

if (result.kind === "exited") process.stdout.write(result.stdout);

Run never deletes the Sandbox it created in either client — disposal stays your decision.

Repository layout

Path Contents
cmd/secondbox the CLI
cmd/secondboxd unprivileged control plane
runner privileged Firecracker runner and guest agent
contracts canonical public, runner, and guest-agent protocols
internal domain, API, scheduling, reconciliation, persistence
migrations/postgres database migration lineage
sdk Go and TypeScript clients
deploy Compose, systemd, and deployment examples
docs/design architecture and compatibility contracts
docs/operations installation, backup, diagnostics

Validation

The portable gate needs no KVM and is what CI runs:

just test-non-kvm

Firecracker validation requires a dedicated Linux host with KVM and the configured assets:

just test-firecracker

The external scenario gate joins the HTTP API, PostgreSQL, the runner protocol, and real Firecracker guests. It needs a self-hosted Linux x86-64 machine with writable KVM and TUN devices, cgroup v2, a separately verified signed microVM bundle, and an XFS or Btrfs workspace root with reflink support:

SECONDBOX_REQUIRE_QUALIFIED_SCENARIO=1 just test-scenario

See scenario qualification for optional end-to-end testing and timing budgets. Every commit admitted to main must pass the GitHub-hosted CI workflow. Releases are built locally, uploaded to a private draft, and published as stable GitHub, GHCR, and npm artifacts without rebuilding. See release operator setup.

Security

Runner connections require TLS 1.3, a CA-signed certificate identifying the Runner, and a pre-shared Runner credential. The HTTP API accepts the deployment-wide platform token for operators, and explicitly configured application authorities bound to fixed tenant and subject references, exact operation scopes, and named Profile grants. None of these authorities are interchangeable.

Loss of an unbacked home-runner workspace filesystem loses that Sandbox: PostgreSQL recovery cannot reconstruct runner-local data. Back up each Runner's stable identity and workspace root as one consistent unit — see backup and recovery and the threat model.

License

MIT. Third-party components and execution assets retain their own licenses; see THIRD_PARTY_NOTICES.md.

Directories

Path Synopsis
cmd
secondbox command
secondboxd command
Package deployassets provides the Compose assets compiled into secondbox-deploy.
Package deployassets provides the Compose assets compiled into secondbox-deploy.
examples
gen
internal
api
Package api exposes the versioned standalone SecondBox HTTP contract.
Package api exposes the versioned standalone SecondBox HTTP contract.
assetcatalog
Package assetcatalog owns immutable provider-neutral execution-asset identity.
Package assetcatalog owns immutable provider-neutral execution-asset identity.
cliui
Package cliui owns terminal capability detection and human-facing rendering for the secondbox and secondbox-deploy commands.
Package cliui owns terminal capability detection and human-facing rendering for the secondbox and secondbox-deploy commands.
config
Package config loads required standalone SecondBox process configuration.
Package config loads required standalone SecondBox process configuration.
deployconfig
Package deployconfig compiles the sole operator-edited SecondBox deployment manifest into explicit process environment artifacts.
Package deployconfig compiles the sole operator-edited SecondBox deployment manifest into explicit process environment artifacts.
install
Package install owns the durable authority and orchestration contracts for the guided single-host installer.
Package install owns the durable authority and orchestration contracts for the guided single-host installer.
lifecycle
Package lifecycle computes durable desired-state reconciliation actions.
Package lifecycle computes durable desired-state reconciliation actions.
observability
Package observability owns fixed-cardinality in-process timing observations.
Package observability owns fixed-cardinality in-process timing observations.
pagination
Package pagination defines provider-neutral opaque traversal cursors.
Package pagination defines provider-neutral opaque traversal cursors.
ports
Package ports defines SecondBox control-plane persistence boundaries.
Package ports defines SecondBox control-plane persistence boundaries.
reconcile
Package reconcile defines restart-safe assignment recovery decisions.
Package reconcile defines restart-safe assignment recovery decisions.
runnercontrol
Package runnercontrol authenticates and validates runner-initiated protocol sessions.
Package runnercontrol authenticates and validates runner-initiated protocol sessions.
runnercontrol/conformance
Package conformance provides reusable runner protocol state-machine qualification.
Package conformance provides reusable runner protocol state-machine qualification.
scheduler
Package scheduler selects compatible runners and persists fenced assignment authority.
Package scheduler selects compatible runners and persists fenced assignment authority.
service
Package service validates and coordinates standalone SecondBox authority.
Package service validates and coordinates standalone SecondBox authority.
store
Package store implements PostgreSQL-backed SecondBox control-plane authority.
Package store implements PostgreSQL-backed SecondBox control-plane authority.
store/lifecycleprojection
Package lifecycleprojection owns PostgreSQL projections shared by durable lifecycle transitions and the runner evidence transactions that establish those transitions' prerequisites.
Package lifecycleprojection owns PostgreSQL projections shared by durable lifecycle transitions and the runner evidence transactions that establish those transitions' prerequisites.
store/rowlock
Package rowlock owns the invariant PostgreSQL lock order for durable resource mutations.
Package rowlock owns the invariant PostgreSQL lock order for durable resource mutations.
subjectcleanup
Package subjectcleanup coordinates restart-safe Subject expiry and teardown.
Package subjectcleanup coordinates restart-safe Subject expiry and teardown.
worknotify
Package worknotify turns PostgreSQL commit notifications into process-local, coalesced wakeup hints.
Package worknotify turns PostgreSQL commit notifications into process-local, coalesced wakeup hints.
migrations
postgres
Package postgresmigrations applies the embedded, immutable SecondBox schema lineage.
Package postgresmigrations applies the embedded, immutable SecondBox schema lineage.
pkg
buildinfo
Package buildinfo contains immutable release identity injected at link time.
Package buildinfo contains immutable release identity injected at link time.
contracts
Package contracts defines the public and persisted SecondBox domain language.
Package contracts defines the public and persisted SecondBox domain language.
networkpolicycontract
Package networkpolicycontract defines the provider-neutral logical gateway syntax shared by deployment tooling and every Runner network backend.
Package networkpolicycontract defines the provider-neutral logical gateway syntax shared by deployment tooling and every Runner network backend.
portdirect
Package portdirect defines the bounded caller-facing handshake that precedes every byte of a direct SecondBox data-plane connection.
Package portdirect defines the bounded caller-facing handshake that precedes every byte of a direct SecondBox data-plane connection.
releasecontract
Package releasecontract defines the public, provider-neutral identity of a SecondBox release.
Package releasecontract defines the public, provider-neutral identity of a SecondBox release.
releaseverify
Package releaseverify retrieves and independently verifies a public release artifact manifest without a repository checkout.
Package releaseverify retrieves and independently verifies a public release artifact manifest without a repository checkout.
resourceapply
Package resourceapply checks and converges declarative SecondBox resources.
Package resourceapply checks and converges declarative SecondBox resources.
standardresources
Package standardresources materializes release-owned resource bundles from a verified release artifact manifest and explicit deployment bindings.
Package standardresources materializes release-owned resource bundles from a verified release artifact manifest and explicit deployment bindings.
sdk
tests
openapicheck
Package openapicheck validates live HTTP responses against the canonical OpenAPI contract.
Package openapicheck validates live HTTP responses against the canonical OpenAPI contract.
scenario/stress command

Jump to

Keyboard shortcuts

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