sbox is a small TypeScript library and CLI that adds strict project
configuration, automatic Docker image preparation, restricted networking,
simple QCOW2 base volumes, and remote access to
Microsandbox.
The repository publishes two Node.js packages:
@sohcah/sbox, including thesboxCLI;@sohcah/sbox-sandcastle, implementing Sandcastle's isolated-sandbox provider over the generalsboxAPI.
Microsandbox remains authoritative for native resources and lifecycle. sbox
does not maintain a second sandbox database or orchestration state machine.
Phase 9 is complete: documentation, samples, package metadata/licenses,
publish dry-run, expanded read-only doctor, feature inventory, repository
audit, secret-canary consolidation, and 0.1.0 certification checklist.
Phase 8 remains in place: @sohcah/sbox-sandcastle adapts an existing
SboxClient to Sandcastle's isolated provider contract (peer
@ai-hero/sandcastle), and the CLI run command creates a unique sandbox,
executes once, and removes in finally.
Phase 7 remains in place for remote Host transport: authenticated foreground
sbox serve, createRemoteHost, HTTP/WebSocket process and transfer parity,
and remote-aware doctor.
Phase 6 remains in place for managed QCOW2/ext4 volumes: project volume
declarations, profile attachments, host-local bases under
~/.sbox/volumes (override with SBOX_VOLUME_DATA_ROOT), disposable child
overlays on ordinary create, exclusive volume shell maintenance mounting the
base directly, and CLI volume list / volume shell / volume remove. Bases
are formatted via a pinned formatter image that already contains mkfs.ext4
(default sbox-volume-formatter:1, auto-built from the shipped
formatter/Dockerfile on first volume ensure; override with
SBOX_VOLUME_FORMATTER_IMAGE) plus host qemu-img (required
only when volumes are used).
Phase 5 remains in place for curated networking and runtime secrets: profile
network.mode (disabled | default-deny), outbound allow rules (domain /
suffix / IP / CIDR with TCP/UDP ports), published ports (loopback bind by
default; dynamic host ports are capability-gated and currently off on
Microsandbox 0.6.6 because allocated ports are not inspectable), and
Microsandbox secret interception (external value, guest placeholder,
destinations). Secret destinations never grant network access. Unconfigured
creates use default-deny with DNS and loopback only.
Phase 4 remains in place for Dockerfile-backed profiles: content-addressed
identity, Docker build → ownership stamp → export → msb image load,
in-process coalescing, and CLI build / image list / image remove.
pnpm install
pnpm check # format, lint, typecheck, build, unit tests
pnpm test:acceptance # optional real Microsandbox (needs runtime)
# prints `sbox-acceptance-status: passed|unavailable|failed`
# unavailable is reported as a skipped Vitest test, not a passsbox init --project demo
# Edit sbox.yaml — volumes + networking example:
# volumes:
# cache:
# size: 4GiB
# profiles:
# default:
# image: alpine:3.20
# volumes:
# - volume: cache
# path: /cache
# network:
# mode: default-deny
# allow:
# - domain: example.com
sbox config validate
sbox up default
sbox run default -- printf '%s' hello # unique sandbox; removed in finally
sbox volume list
sbox volume shell default cache # exclusive base maintenance
sbox stop default && sbox remove default
sbox volume remove cacheProgrammatic equivalent:
import { createSboxClient, parseProjectConfig } from "@sohcah/sbox";
const client = createSboxClient({
project: parseProjectConfig({
version: 1,
project: "demo",
profiles: {
default: { image: "alpine:3.20", memoryMiB: 512 },
},
}),
});
const handle = await client.up({ profile: "default" });
const result = await handle.exec(["printf", "%s", "hello"]);
// Non-zero guest exit is a ProcessResult, not an thrown error.
await handle.copyToGuest("./payload.bin", "/tmp/payload.bin");
await handle.stop();
await handle.remove();
await client[Symbol.asyncDispose]();up is deliberately narrow: create-if-absent, start-if-stopped, success-if-running.
Changed immutable creation settings are reported as drift and require explicit
recreate. Disposal closes local objects only and never stops or removes a
sandbox.
- Exact argv never passes through a host or guest shell. Use CLI
exec --shell -- <expression>or libraryhandle.shell(...)for guest-shell interpretation (profile.shell, default/bin/sh). - CLI
shell [profile]opens the configured profile shell interactively. Local targets use Microsandbox's native terminal attach; other hosts use the portable PTY stream bridge. - Collected stdout/stderr default to 10 MiB each; overflow cancels the process
and throws
output_limit. - Streaming events are byte-oriented:
started/stdout/stderr/exited. UTF-8 and line helpers are optional. - PTY supports arbitrary Node streams, merged output, resize, and cancellation.
The library PTY and non-local CLI fallback use an isolated private
agent-protocol adapter; local CLI shells use the pinned SDK's terminal-bound
attach*API (seepatches/README.md). - Transfers preserve bytes, executable bits, and safe symlinks. They reject traversal, escaping links, and special files. Ownership and timestamps are not preserved.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Operational failure |
| 2 | Validation / configuration error |
| 3 | Ownership conflict or creation drift |
| 4 | Not found |
| 5 | Already exists |
| 130 | Cancellation |
For exec, shell, and run, the guest process exit code becomes the CLI
exit code on success of the Host operation. --json emits a single result
object for collected commands; --stream --json emits typed NDJSON events.
Design docs:
docs/product.mddocs/system-plan.mddocs/non-goals.mddocs/implementation-plan.mddocs/prior-art.mddocs/api.mddocs/cli.mddocs/configuration.mddocs/sandcastle.mddocs/remote.mddocs/networking.mddocs/volumes.mddocs/prerequisites.mddocs/troubleshooting.mddocs/feature-inventory.mddocs/host-mounts-plan.mddocs/publishing.mddocs/certification.md
Samples: samples/local, samples/remote.
Publish dry run: pnpm publish:dry-run. Releases: tag v* → GitHub Actions
trusted publishing (see docs/publishing.md).
The Microsandbox TypeScript 7 declaration patch and the private PTY agent
adapter are documented under patches/README.md.