Skip to content

Releases: aryanmehrotra/sbx

v0.16.1

Choose a tag to compare

@github-actions github-actions released this 30 Sep 07:25
578949b

sbx v0.16.1 — sbx env uses only this backend's record of a sandbox

Released 2026-09-30.

Upgrade if one machine reaches more than one backend - docker and kubernetes, firecracker, or two
docker engines - with sandboxes of the same name. Nothing else changes: no spec field, command, flag
or wake-path behaviour differs from v0.16.0.

Before you upgrade

  • Behaviour change: a record of how a sandbox was created (~/.sbx/origins) that another backend
    wrote is ignored, so the command falls back to --spec or the sandbox.json in the working
    directory. Do this: nothing. Records written before v0.16.0 name no backend and are still used.

Fixes

  • sbx env, sbx fork and other commands that look up how a sandbox was created no longer use the spec of a same-named sandbox on another backend.

Upgrade

brew upgrade sbx                  # first install: brew install aryanmehrotra/tap/sbx
curl -fsSL https://raw.githubusercontent.com/aryanmehrotra/sbx/main/scripts/install.sh | VERSION=v0.16.1 sh
go install github.com/aryanmehrotra/sbx@v0.16.1
sbx doctor

Binaries for macOS, Linux, FreeBSD and Windows (amd64, arm64) are attached with SHA256SUMS.
Cluster activator: ghcr.io/aryanmehrotra/sbx-activator:v0.16.1.

Full changelog: v0.16.0...v0.16.1

What's Changed

Full Changelog: v0.16.0...v0.16.1

v0.16.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 04:27
ac6ea95

sbx v0.16.0 — sbx ready and sbx egress you can trust on any engine

Released 2026-09-30.

Upgrade now if you run v0.15.1 or earlier on colima or Docker Desktop with an egress policy: the
filter let a sandbox reach the VM, other containers and your Mac's loopback. This release also makes
sbx create, ready and wake say "serving" only when something outside can connect, and fixes
data loss in sbx with and sbx snapshot. Advisory: https://github.com/aryanmehrotra/sbx/blob/v0.16.0/SECURITY.md

Before you upgrade

  • Breaking: egress_allow, egress_policy and egress: "allow" carry only ports 80 and 443. Do this: write "host:port" in egress_allow for another port.
  • Breaking: sbx with <name> refuses a name that exists; it used to reuse that sandbox, then delete it and its volumes. Do this: sbx env or sbx exec against an existing sandbox.
  • Breaking: sbx snapshot <sandbox> <name> refuses an existing name instead of merging into it. Do this: --replace.
  • Breaking: cap_add: ["ALL"] is refused. Do this: list the capabilities the workload needs.
  • Breaking: $${ in an env value is a literal ${; v0.15 expanded $${X}. Do this: check env values with sbx validate.
  • Behaviour change: sbx list has an ISOLATION column before ADDRESS, and sbx add joins the sandbox's isolation tier, refusing a different --isolation or SBX_ISOLATION. Do this: read sbx list --json in scripts.
  • Behaviour change: sbx create stops with an error naming the holder when a lock stays held for 10 minutes; it went ahead unlocked after 90 s. Do this: nothing.
  • Behaviour change: sbx ready, wake and create check inside each container that something listens where outside can reach it, and fail at once on a verdict that cannot change. Do this: bind 0.0.0.0, not 127.0.0.1.
  • Behaviour change: sbx validate refuses unknown cap_add names, non-plain ${...} in env, and cpu, memory or gpus values no provider accepts. Do this: sbx validate each spec.
  • Behaviour change: sbx gc lists and --force removes dead lock files and origin records; records written before v0.16.0 are counted, never removed. Do this: nothing.
  • New requirement: restart sbx serve after upgrading, and do not run two sbx versions at once. Lock files, pause marks and the egress filter changed format. Do this: stop the old sbx serve, start the new one.

Highlights

  • Allow one port for one host: "egress_allow": ["db.example.com:5432"].
  • Delete one snapshot: sbx snapshot --rm golden. It refuses while a fork still runs from it.
  • A snapshot pauses running services while it copies, so a database such as ClickHouse is saved at one instant.
  • sbx list shows each service's isolation tier and frozen services; --json adds isolation and state.
  • sbx env prints <SERVICE>_HOST and _PORT for services no export names, such as ones from sbx add.
  • sbx init --from-devcontainer translates ${localEnv:X} and lists what it could not evaluate.
  • Every command takes flags before, between or after names: sbx logs -f main redis.

Fixes

  • Security: the egress filter no longer reaches the VM, the default bridge or host loopback, a network created after it, or itself; CONNECT no longer tunnels raw TCP to any port.
  • egress_allow works under --isolation gvisor, keeps every service's hosts, and follows an edited spec on sbx create.
  • A sbx egress change can no longer be reverted by the daemon writing the policy at the same moment.
  • sbx create, ready and wake no longer print ✓ or "serving" for a service that then fails, exited, or could not be asked; scratch and distroless images are checked too.
  • sbx with removes a sandbox whose create failed or was interrupted, and two of one name no longer remove it under each other.
  • Ctrl-C or SIGTERM during sbx create says what it kept, releases its locks and exits 130 or 143.
  • Concurrent creates no longer share a slot or wait on each other's health checks; a pid reused by another process no longer holds a lock.
  • The daemon no longer serves the old ports after sbx rm then sbx create of one name, or binds the ports of a sandbox a running --only daemon serves.
  • A failed or interrupted sbx snapshot leaves nothing behind, and --rm finds what an older one left. sbx fork writes its own spec file, so forks of one snapshot or of build services work.
  • A service sleeps within about a second of its idle window, and wakes the services it depends_on after sbx sleep.
  • sbx resume refuses a running service; sbx exec passes piped stdin and exits with the command's status.

Upgrade

brew upgrade sbx                  # first install: brew install aryanmehrotra/tap/sbx
curl -fsSL https://raw.githubusercontent.com/aryanmehrotra/sbx/main/scripts/install.sh | VERSION=v0.16.0 sh
go install github.com/aryanmehrotra/sbx@v0.16.0
sbx doctor

Binaries for macOS, Linux, FreeBSD and Windows (amd64, arm64) are attached with SHA256SUMS.
Cluster activator: ghcr.io/aryanmehrotra/sbx-activator:v0.16.0.

Full changelog: v0.15.1...v0.16.0

What's Changed

  • fix: everything the functional QA sweep found — egress host door, sbx with data loss, stale daemon, and 40 more by @aryanmehrotra in #18
  • docs(release-notes): v0.16.0 by @aryanmehrotra in #19

Full Changelog: v0.15.1...v0.16.0

v0.15.1

Choose a tag to compare

@github-actions github-actions released this 29 Sep 10:24
a70b43b

sbx v0.15.1 — sbx create over an asleep sandbox no longer fails on files

Released 2026-09-29.

Upgrade if you re-run sbx create over sandboxes that may be asleep and declare files. Nothing
else changes: no spec field, command, flag or wake-path behaviour differs from v0.15.0.

Before you upgrade

  • Behaviour change: sbx create over a sandbox that already exists and is asleep leaves each
    asleep service as it is, and does not re-run its mount check, health wait or init. Do this:
    to re-run them, sbx wake <sandbox> and then sbx create again.

Fixes

  • sbx create re-run over an asleep sandbox no longer reports a files entry as "did not mount as a file".

Upgrade

brew upgrade sbx                  # first install: brew install aryanmehrotra/tap/sbx
curl -fsSL https://raw.githubusercontent.com/aryanmehrotra/sbx/main/scripts/install.sh | VERSION=v0.15.1 sh
go install github.com/aryanmehrotra/sbx@v0.15.1
sbx doctor

Binaries for macOS, Linux, FreeBSD and Windows (amd64, arm64) are attached with SHA256SUMS.
Cluster activator: ghcr.io/aryanmehrotra/sbx-activator:v0.15.1.

Full changelog: v0.15.0...v0.15.1

What's Changed

Full Changelog: v0.15.0...v0.15.1

v0.15.0

Choose a tag to compare

@github-actions github-actions released this 29 Sep 08:56
8313f21

sbx v0.15.0 — sbx install adds what sbx doctor says is missing

Released 2026-09-29.

sbx doctor reports what this machine lacks, including the gVisor, Kata and checkpoint runtimes
that --isolation and sbx checkpoint need. sbx install now installs them, and the tools doctor
lists, after showing every command and asking first. sbx doctor itself still changes nothing.

Before you upgrade

  • Behaviour change: sbx install checkpoint restarts dockerd, and sbx install gvisor reloads it.
    Do this: read the plan with sbx install --dry-run first. The plan warns before a restart.
  • Behaviour change: each runtime is merged into /etc/docker/daemon.json, and the previous file is
    saved as daemon.json.sbx-bak. Do this: nothing, unless you manage that file another way.

Highlights

  • Install what is missing: sbx install (everything it can), or by name: sbx install gvisor.
  • See the plan without running it: sbx install gvisor --dry-run.
  • gVisor, a runtime that gives containers their own user-space kernel, comes from a release pinned
    by sha512 (amd64, arm64) and is registered with dockerd as runsc. Running containers are untouched.
  • Checkpoint installs CRIU, the Linux tool that saves a running process to disk, and turns on
    dockerd's experimental mode. Kata comes from the distribution's package (dnf) and needs /dev/kvm.
  • Tools come from apt-get, dnf, pacman, apk or brew: docker, kubectl, cloudflared, redis-cli,
    mkfs.ext4, iptables. With sudo, the plan runs through it.
  • New docs: a quickstart, a CLI reference, one how-to guide, self-hosting and an FAQ, all rewritten
    against the code: https://github.com/aryanmehrotra/sbx/blob/v0.15.0/docs/QUICKSTART.md

Fixes

  • An egress_allow host no longer answers 502 "operation was canceled" to busybox wget (every alpine image) over HTTPS.
  • An OpenSandbox API sandbox stopped right after it started now shuts down gracefully, not with exit -1.
  • sbx serve --help lists firecracker among the --provider values.

Known limitations

  • On Docker Desktop, colima or Rancher Desktop, and so on every Mac, the VM that runs docker owns
    its config. The runtimes are refused there with that reason. Tools still install through brew.
  • Checkpoint was installed but not proven: the test machine's kernel cannot run CRIU. On such a
    kernel doctor still shows ✓ docker checkpoint, and sbx install warns after criu check fails.
    Ubuntu 24.04 dropped criu; the plan names ppa:criu/ppa.
  • Not run for real: Kata, the dnf, pacman, apk and brew paths (unit-tested), and a restart through
    systemd. Without systemd, install stops at the restart and says to restart dockerd yourself.
  • Kata from its upstream release is not offered, because that release is not pinned here yet.

Upgrade

brew upgrade sbx                  # first install: brew install aryanmehrotra/tap/sbx
curl -fsSL https://raw.githubusercontent.com/aryanmehrotra/sbx/main/scripts/install.sh | VERSION=v0.15.0 sh
go install github.com/aryanmehrotra/sbx@v0.15.0
sbx doctor                        # the last line names what `sbx install` can add here

Binaries for macOS, Linux, FreeBSD and Windows (amd64, arm64) are attached with SHA256SUMS.
Cluster activator: ghcr.io/aryanmehrotra/sbx-activator:v0.15.0.

Full changelog: v0.14.0...v0.15.0

What's Changed

  • feat: sbx install — install what sbx doctor reports missing, including gVisor, Kata and checkpoint by @aryanmehrotra in #11
  • fix(execd): register signal handlers before serving, so an early SIGTERM is a graceful stop by @aryanmehrotra in #13
  • docs: rebuild the repo's presentation - README, guides, release notes, benchmarks, visuals, and a docs contract CI enforces by @aryanmehrotra in #10
  • site: a landing page for GitHub Pages, deployed only once Pages is switched on by @aryanmehrotra in #14
  • fix(egress): half-closed clients no longer get 502 · release v0.15.0 by @aryanmehrotra in #15

Full Changelog: v0.14.0...v0.15.0

v0.14.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 12:55
1bf19bf

A microVM's escape reaches less, and a create copies nothing

v0.14.0 hardens the Firecracker provider. v0.13 put every VMM under the jailer. This release
closes two of the gaps v0.13 listed as open, removes the per-VM image copy, and stops a slow sleep
from losing a VM's memory.

v0.13 v0.14
a jailed VMM's network the host's namespace its own namespace: only its tap, no address, no route
one file a compromised VMM can write unbounded its largest drive + 64 MiB, then SIGXFSZ
a new VM's root filesystem a copy of the image the image, read-only and shared, plus its own sparse layer
a sleep whose seal is not confirmed the VM stops and wakes cold asked again (10 s, 20 s, 30 s); if still unconfirmed, the VM keeps running and retries

Nothing changes on docker or Kubernetes.


What proves it

These results come from CI on the merged code, on a GitHub-hosted runner with /dev/kvm and the
jailer on:

check result
fc-jail-watch across every microVM step 153 VMMs seen, each chrooted, in a network namespace of its own, and running as a non-root uid no other VMM had
upstream tests/go (v0.10.0 tier) on microVMs 48 passed · 0 failed · 4 skipped
the same, with a warm pool on microVMs 27 passed · 0 failed · 1 skipped
docker: v0.10.0 tier · pool · e2e 50 · 0 · 2, 20 / 20, and 3 / 3
real jailer, microVM e2e, pool e2e, host guard pass

The e2e test now checks that the VMM's /proc/<pid>/ns/net is its VM's namespace and not the
host's, with no routes. The real-jailer test reads Max file size from /proc/<pid>/limits.

Burst TTI on microVMs (4 concurrent creates, then node -v, 3 rounds, node:22-slim) is
unchanged within runner noise: 144 ms from a frozen pool, 759 ms from an asleep pool, 2,637 ms
cold (v0.13: 141, 699 and 2,821). That image is small, so it cannot show the layered root's gain,
which is in large images. v0.12 measured about 16 s per create to copy the 7.4 GiB
code-interpreter image on a CI runner. A layered create hard-links it instead.


What changed

  • A network namespace per VM. With the jailer on, each VMM runs in
    /var/run/netns/sbxfc<slot>-<index>. That namespace holds only its tap (owned by the VM's uid),
    bridged through a veth into the sandbox's guarded bridge, with no address and no route. A VMM
    escape reaches what the guest reaches, not the host's network. Unjailed
    (SBX_FC_JAILER=off), there is no namespace.
  • A file-size limit. A jailed VMM runs with RLIMIT_FSIZE, set to the largest file it may
    legitimately write plus 64 MiB. It bounds each file, not how many: the state filesystem still has
    no per-uid quota, and SECURITY.md says what the operator can do about that.
  • A layered root. A create hard-links the image's cached rootfs.ext4 read-only as its base,
    and gives the VM an empty, sparse upper.ext4 of SBX_FC_DISK_SIZE (10G by default). The guest
    lays overlayfs over both. Snapshots link the base and clone only the layer.
  • A sleep that is not confirmed keeps the VM. Seal is asked up to three times with a longer
    bound each time. If none is confirmed, execd is re-keyed, the VM stays running with its memory,
    and the next idle check tries again. After three unconfirmed sleeps in a row it stops as before,
    so a guest cannot keep its memory by stalling forever.
  • The daemon's root, written down. SECURITY.md lists the capabilities the daemon needs and
    why it keeps them, for a host that bounds it with CapabilityBoundingSet=. No VMM holds any
    capability (CapEff 0, checked in CI).

⚠️ Before you upgrade a microVM host

  • The host needs ip netns. That means iproute2, a writable /var/run/netns, and
    CONFIG_NET_NS plus CONFIG_VETH, which every distribution kernel has. A host that cannot make
    a namespace now refuses a jailed microVM, and the error quotes ip. sbx running inside a
    container that forbids namespaces must move to the host, or accept the risk with
    SBX_FC_JAILER=off. See TROUBLESHOOTING.md.
  • A custom guest kernel needs CONFIG_OVERLAY_FS=y. The pinned kernels have it. Otherwise set
    SBX_FC_ROOTFS=copy to keep v0.13's full-copy layout.
  • Existing VMs and snapshots keep their layout. A VM or snapshot made before v0.14 keeps its
    whole rootfs.ext4; only new creates are layered. Volumes on a layered VM start one drive later
    (vdd).
  • A VM's writes are bounded by SBX_FC_DISK_SIZE. The default is 10G, sparse. A workload that
    writes more must raise it, or write to a volume.

What it still does not do

  • The daemon runs as root. It redoes privileged work on every create and wake, so it cannot
    drop root after starting up.
  • No quota on the whole state filesystem. Put SBX_FC_STATE on a filesystem of its own, or
    enable project quotas. sbx doctor warns when it shares /.
  • No bare-metal benchmark, and the helper VM path (M3+ Mac, Windows 11) is not yet run end to
    end on a Mac.

Adopting it

brew upgrade sbx          # or: go install github.com/aryanmehrotra/sbx@v0.14.0
sbx doctor                # checks /dev/kvm, the jailer, iptables, the bridge guards

What's Changed

  • microVM hardening: per-VM network namespace, shared read-only base, fsize limit, seal retry (K3, K4, K6) by @aryanmehrotra in #8
  • v0.14.0: release notes (microVM netns, layered root, fsize, seal retry) by @aryanmehrotra in #9

Full Changelog: v0.13.2...v0.14.0

v0.13.2

Choose a tag to compare

@github-actions github-actions released this 27 Sep 11:31
c320752

Closer to upstream's OpenSandbox API: pool, e2e, and honest refusals

v0.13.1 was tagged and never published. The release gate (go test -race ./...) caught a
flaky test, TestSleepAndWakeReachAScopedDaemon, which failed 6 times in 900 runs. The
test's own readiness probe could reach the service after sbx sleep and wake it again, which
is correct behaviour for a connection. The test now waits for that probe to land before it
sleeps, and passed 900 of 900. This release is v0.13.1 plus that test fix; no product code
differs.

Upgrade if you drive sbx through the OpenSandbox SDKs. This release fixes two places where sbx
was stricter than upstream's own server, so upstream's pool and e2e test files now pass. It
also corrects error messages that promised features in a release that never shipped them.

Nothing changes for sbx.json, the wake path, docker, Kubernetes, or the microVM provider.


What proves it

These are upstream's tests/go at release-1.1.0, unmodified, run in CI on every change:

check v0.13.0 v0.13.2
v0.10.0 tier, docker 50 passed · 0 failed · 2 skipped same
v0.10.0 tier, microVMs (code_interpreter included) 48 · 0 · 4 same
pool (v0.11.0 tier), with a Redis 4 passed · 8 failed · 8 skipped (not gated) 20 passed · gated
e2e (v0.12.0 tier), keyless never ran 3 passed · gated

The rest of those tiers, isolated_session (48 tests) and credential_vault (4 tests), is
not built. ROADMAP.md §4 lists what is left.


What changed

  • A renew may shorten the expiry. sbx refused any renew-expiration that did not move the
    expiry later. Upstream's server checks only that the new time is in the future
    (ensure_future_expiration). Its SDK pool relies on that: when it hands out a member, it
    renews it to the caller's timeout, which is usually earlier. That broke 8 pool tests. A renew to
    now or to the past is still refused with 400.
  • Running means execd answers. sbx used to hold a code-interpreter sandbox Pending until its
    Jupyter answered. Upstream's e2e creates that image with tail -f /dev/null as the entrypoint,
    so Jupyter never starts, and every e2e test timed out. Upstream reports Running once the
    container runs and leaves Jupyter to its SDK, whose CreateCodeInterpreter waits for it. sbx
    now does the same, one step stricter: it still waits for execd. A client that does not use the
    SDK still gets a bounded wait on its first /code call, inside execd. The reversal and its
    reason are in DECISIONS.md ("Running means usable").
  • Refusals no longer promise a release. Nine parts of the API that sbx has not built answered
    501 saying they arrive in sbx v0.11.0, and v0.11.0 shipped none of them. They now name the feature
    and point to the roadmap. The nine are isolated sessions, server-side pools, registry
    credentials, secureAccess, credentialProxy, lifecycle hooks, renew-on-access,
    server-proxied endpoints and signed endpoints.

Fixed in the tooling

  • scripts/osb-conformance.sh --no-key works. Under pipefail, its probe for
    --osb-insecure-no-key always read as "flag unknown". The daemon therefore started with a key
    and answered 401, and upstream's e2e file had never run.

Docs

  • BENCHMARKS.md adds create → first command on microVMs, from CI: 141 ms
    from a frozen pool, 699 ms from an asleep pool, 2,821 ms cold. These ran on a nested-KVM
    runner, not on bare metal.
  • COMPARISON.md adds agent sandboxes: sbx beside OpenSandbox, E2B, Daytona,
    Modal, Cloudflare, Vercel and isorun, plus ComputeSDK's Burst TTI leaderboard. Every vendor
    figure is linked to its source.

⚠️ Behaviour you may notice

  • A code-interpreter sandbox now reports Running before its Jupyter answers. The upstream
    SDKs wait for Jupyter themselves. If you call /code directly, the first call waits inside
    execd for a Jupyter that is still starting.
  • A renew with an earlier, future expiresAt now succeeds and shortens the sandbox's life. It
    used to be refused.

Adopting it

brew upgrade sbx          # or: go install github.com/aryanmehrotra/sbx@v0.13.2

What's Changed

  • v0.13.2: fix the flaky scoped-wake test that stopped v0.13.1's release by @aryanmehrotra in #7

Full Changelog: v0.13.1...v0.13.2

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 20:14
9356b09

A microVM warm pool, and the jailer on by default

v0.13.0 makes the OpenSandbox API on microVMs both faster and confined. v0.12.0 served the
API from Firecracker with no pool and an unconfined root VMM. Both have changed:

  • A warm pool on microVMs. --osb-pool works on --provider firecracker. Members are parked
    asleep (snapshot on disk) or frozen (paused in memory), and each one is re-keyed when claimed.
  • Every VMM runs under Firecracker's jailer by default. Each VM gets its own non-root uid, a
    chroot inside its own directory, and a cgroup v2 sized from the spec.
  • The host guard fails closed. If a VM's bridge cannot be guarded, the VM is refused.
sbx serve --provider firecracker --osb-addr 127.0.0.1:8090 --osb-pool node:22-slim=4

The warm pool's design is in the spec, and the reasoning is in
DECISIONS.md ("Every VMM runs under Firecracker's jailer").


What proves it

These results come from CI on the release commit, on a runner with /dev/kvm:

check result
upstream tests/go conformance (v0.10.0 tier) on microVMs 48 passed, 0 failed, 4 skipped
the same suite on microVMs with a warm pool 27 passed, 0 failed, 1 skipped
the same suite on docker 50 passed, 0 failed, 2 skipped
the real jailer (TestTheRealJailer), microVM e2e and pool e2e pass
fc-jail-watch across every microVM step 63 VMMs seen, each chrooted and running as a non-root uid that no other running VMM had

Burst TTI on microVMs: 3 rounds of 4 concurrent create → runCommand('node -v'), image
node:22-slim, on a GitHub-hosted x86_64 runner:

mode median p95
cold create 2,821 ms 5,769 ms
warm pool, frozen (--osb-pool-freeze) 141 ms 207 ms
warm pool, asleep 699 ms 957 ms

All 12 pooled creates were served from the pool. A frozen claim's create() takes a median of
about 37 ms; most of the rest is node -v itself. These are numbers from a shared CI runner,
not a bare-metal benchmark.

Every CI job is green on the release commit. zeropod is skipped, as it is on every run.


Confinement: what the jailer changes

  • The VMM is not root. Each VMM runs under its own uid, taken from a range
    (SBX_FC_JAILER_UID_BASE moves it). The uid range must not hold a real account; sbx does not
    check /etc/passwd.
  • A chroot per VM. The chroot holds that VM's kernel, drives, snapshot files, /dev/kvm,
    /dev/net/tun, /dev/urandom and its sockets. The shared kernel stays root's and read-only.
    So does any drive the VMM only reads, meaning the agent drive and read-only volumes: a
    compromised VMM cannot rewrite them for the next sandbox.
  • Sockets are checked by peer uid. A socket symlink planted in a jail is never followed. The
    API and vsock sockets are accepted only from the jail's own uid (SO_PEERCRED); anything else
    is refused as a foreign socket rather than read as "asleep".
  • A pool claim respects jail mode. A claim refuses a member that was parked in the other jail
    mode.
  • The IPv6 guard is rechecked as well. The guard's IPv6 half is rechecked along with the
    iptables half, on every wake and on every in-place resume.
  • sbx doctor now grades the jailer, the host firewall mode, and whether the microVM state
    directory shares /.

Opting out, deliberately

  • SBX_FC_JAILER=off brings back v0.12's risk exactly. It is meant for a development host
    that cannot run the jailer (no cgroup v2, no mknod), and it warns on every use. The OpenSandbox
    API refuses to serve on firecracker without the jailer unless you pass
    --osb-insecure-no-jailer.
  • --fc-firewall=unmanaged tells sbx that the operator owns the host firewall, so a bridge
    that cannot be guarded is not refused.

Also in this release

  • The OpenSandbox API through the helper VM (M3+ Mac, Windows 11). sbx serve --osb-addr
    serves the API from inside the helper VM and fronts it on the host. It is unit-tested but
    not yet run end to end on a Mac. --osb-pool is not carried into the helper VM yet.
  • A daemon started with --only is now found. It writes ~/.sbx/daemons/<pid>.json, so
    create, list, ui, doctor and the egress note recognise the sandboxes it fronts.
    Previously they all reported that no daemon was running. (Known issue in v0.10.0.)
  • A wake mounts only the host directories that create validated. The canonical bind paths
    are pinned on the container. A start or restore re-checks them, and if a path changed while the
    sandbox slept (a swapped symlink, for example), sbx refuses with HostBindChanged rather than
    following it. (Known issue in v0.10.0: the host-volume TOCTOU.)
  • Kill now signals the recorded VMM even when its argv no longer names the VM.
  • A running VMM is addressed in the jail mode it was launched with.

What it does not do yet

  • ⚠️ No network namespace per VM. The VMM shares the host's network namespace. A VMM escape
    reaches whatever a non-root uid on the host's network can reach.
  • ⚠️ No disk quota per VM. One sandbox, or a compromised VMM, can fill the state filesystem
    (SBX_FC_STATE). Put that directory on a filesystem of its own or enable project quotas;
    sbx doctor warns when it shares /.
  • The daemon still runs as root. It needs root for taps, bridges, iptables and the jailer
    itself.
  • Each VM still copies its image's data where reflink is unavailable.
  • No warm pool through the helper VM, and no bare-metal benchmark yet.

The first three are the remaining prerequisites before anonymous or untrusted use. See
SECURITY.md.


Adopting it

brew upgrade sbx          # or: go install github.com/aryanmehrotra/sbx@v0.13.0
sbx doctor                # checks /dev/kvm, cgroup v2, the jailer, iptables and the bridge guards
sbx serve --provider firecracker --osb-addr 127.0.0.1:8090 --osb-pool node:22-slim=4

⚠️ Behaviour change on firecracker: a host that cannot run the jailer, or cannot guard a
bridge, now refuses microVM creates that v0.12 would have started. sbx doctor names the
fix. Setting SBX_FC_JAILER=off or --fc-firewall=unmanaged restores the old behaviour.

Nothing changes on docker or Kubernetes.

What's Changed

  • v0.13: microVM warm pool + jailer on by default, guard fails closed by @aryanmehrotra in #2

Full Changelog: v0.12.0...v0.13.0

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 12:52
01656bd

The OpenSandbox API on microVMs

v0.12.0 serves the OpenSandbox API from Firecracker microVMs. v0.11.0 gave sandboxes their
own kernel and refused --osb-addr on that provider. That refusal is gone:

sbx serve --provider firecracker --osb-addr 127.0.0.1:8090

Every sandbox the API creates is then a microVM with execd inside it. The SDKs, the upstream
conformance suite and the endpoints are the same as on docker. The plan is in
the spec.


What proves it

check result
upstream tests/go conformance (v0.10.0 tier) on microVMs, CI with /dev/kvm 48 passed, 0 failed, 4 skipped (skips are listed in expected.tsv)
same suite on docker 50 passed, 0 failed, 2 skipped
bridge guard against the real kernel (TestGuardLive) pass
microVM end to end (TestFirecrackerE2E) pass

Every CI job is green on the release commit (15 run; zeropod is skipped, as on every run).


What changed underneath

  • execd runs the workload itself (RunsAgent). The image's entrypoint starts as execd's
    child inside the VM, so code-interpreter images start their Jupyter the way they do in a
    container.
  • The API owns the token. A VM's execd secrets come from the API, and neither the image nor
    the caller can choose them.
  • Volumes. A pvc volume is an ext4 block drive. Host-path volumes are refused by name,
    because a microVM has no virtio-fs.
  • Snapshots are disk-only for API sandboxes.
  • Readiness is honest. An image that runs Jupyter is Running only once Jupyter answers.
    Jupyter gets its own bound (3 min by default), and a timeout says which part was slow.
  • A Failed sandbox always says why, in the status, the daemon log and the history.
  • Faster rootfs copies. Where reflink is unavailable, a VM's root filesystem is cloned by
    its data extents, so holes are never read. In CI the code-interpreter image is 7.4 GiB of data
    and clones in 16 s; the VM then boots and serves in 1.1 s.

Fixed on the way

  • Over vsock, only the first request on a connection had a live context. execd's vsock
    connection returned *fs.PathError, which net/http does not treat as a network error. After
    the first response net/http cancelled the whole connection's context, so every later request
    on that keep-alive connection arrived already cancelled. Any handler that consulted its
    context failed — Jupyter readiness and the /code routes among them. The connection now
    returns *net.OpError, like TCP does.
  • /etc/hosts inside a VM now has localhost and the hostname, as docker's does.

Network: a microVM's only door is its filter

  • Each bridge gets its own egress filter on its gateway address. Everything else on the host is
    closed to the guests on that bridge.
  • By default a VM filter refuses RFC1918, CGNAT and ULA ranges and the host's own subnets. A
    sandbox's policy cannot reopen what the host refuses.
  • The bridge guard drops guest traffic in mangle before docker's DNAT can forward it. The
    guard is re-checked on every wake and daemon reconcile.
  • sbx doctor grades iptables and each bridge guard.

What it does not do yet

  • ⚠️ The VMM runs as unconfined root. There is no jailer in this release. Do not use it
    for untrusted or anonymous workloads. The jailer, with a guard that fails closed, is v0.13.
  • No warm pool on microVMs. --osb-pool is refused on the firecracker provider; the microVM pool is v0.13.
  • A copy per VM without reflink. On a filesystem without reflink, every VM still copies its
    image's data: about 16 s for a 7.4 GiB image in CI. On XFS with reflink=1 or on btrfs the
    clone is instant.
  • No bare-metal benchmark yet. The only numbers claimed are the ones above.

Adopting it

brew upgrade sbx          # or: go install github.com/aryanmehrotra/sbx@v0.12.0
sbx doctor                # checks /dev/kvm, iptables and the bridge guards
sbx serve --provider firecracker --osb-addr 127.0.0.1:8090

Nothing changes for docker or Kubernetes, or for API servers already running on docker.

What's Changed

New Contributors

Full Changelog: v0.11.0...v0.12.0

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 01:42

A sandbox with its own kernel

v0.11.0 adds a third backend: Firecracker microVMs. The same sandbox.json, with a
dedicated guest kernel underneath instead of the host's - and a sleep that keeps memory and
running processes, because sleeping a microVM is a snapshot and waking it is a restore.

sbx create web --provider firecracker --template nginx

It is the item ROADMAP.md called "the big one", and it lands smaller than planned
because a spike measured the risky parts first - see
the spike.


Where it runs

host what happens proven by
Linux with /dev/kvm Firecracker directly e2e inside a Linux VM with KVM; a new CI job runs it wherever the runner exposes /dev/kvm (its first run is this release's)
Mac, M3 or later, macOS 15+ sbx manages a small Linux helper VM (colima) and runs Firecracker inside it; sbx on the Mac works unchanged, and a connection on the Mac still wakes the sandbox e2e on an M4: 16/16
Mac on lima instead of colima same design not yet run end to end - colima is the verified driver
Windows 11 with WSL2 same design, in a WSL distro built and unit-tested; not yet run on a Windows host
Kubernetes with a kata-fc RuntimeClass --isolation firecracker unit-tested only
anything else refused, with the one thing to change -

sbx doctor says which of these applies to your machine and why.


What it measures

On an Apple M4 through nested virtualisation (helper VM 2 vCPU / 2 GiB, nginx, 12 interleaved
rounds - scripts/fc-anywhere-e2e.sh):

median p95
wake from a snapshot to first byte 224 ms 282 ms
request to an awake sandbox 8.8 ms -
sbx exec round trip 788 ms -
sbx sleep 709 ms -
sbx create 11.5 s -

On a Mac this is parity for development, not speed: nested virtualisation pays for every
page the guest touches after a restore, and the docker warm pool from v0.10.0 remains the fast
path there. On bare-metal Linux the restore should be far cheaper; that has not been measured
yet
, and the numbers above are the only ones this release claims. Where the wake time goes, and
where create's goes, is broken down phase by phase in BENCHMARKS.md.


How it holds together

  • execd is the in-VM agent, over vsock - the same binary that serves the OpenSandbox API
    inside containers. sbx exec, sbx cp and health go through it.
  • A restored VM never answers with its parent's identity. execd is sealed before every
    snapshot and re-keyed with a fresh token and a rotated secret before a wake is let through.
  • The API choices fail closed. A failed sleep stops the VM instead of leaving it half-sealed;
    a slow VMM is an error, not "asleep"; wake and sleep take the VM's lock before reading its
    record.

Two independent reviews - one of the design, one of correctness and security - went over this
provider before release. Every finding either became a fix with a test that failed before it,
or is listed below.


What it costs you, and what it does not do yet

  • A sleeping microVM is not free at rest. Its memory snapshot is a file roughly the size of
    the VM's RAM - 256 MiB each by default. sbx doctor shows the total.
  • Refused by name, not ignored: build, files, mounts, init, gpus, cap_add,
    egress_allow / egress_policy, and images whose USER is not root. The OpenSandbox API
    (--osb-addr) is refused on this provider; it stays docker-backed.
  • Networking: a microVM has no route out (egress: "deny"). sbx writes no firewall rules, so
    isolation between microVMs depends on the host's FORWARD policy being DROP - sbx doctor
    checks it and create warns when it is not - and a guest can reach host services listening on
    0.0.0.0. SECURITY.md has the detail.
  • Known issue: under heavy host memory pressure a sleep's seal can time out (once in 46 rounds
    here). The VM is then stopped rather than snapshotted, so that sandbox wakes with a cold boot
    and loses what was only in memory.
  • Not yet: forking a microVM snapshot, the OpenSandbox API and warm pool on microVMs, a
    bare-metal benchmark.

Adopting it

brew upgrade sbx          # or: go install github.com/aryanmehrotra/sbx@v0.11.0
sbx doctor                # which microVM path this machine has
sbx create box --provider firecracker --template nginx

Nothing changes for docker or Kubernetes sandboxes, or for the OpenSandbox API.

Full Changelog: v0.10.0...v0.11.0

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 22:01

A sandbox in 14 ms, and the rest of OpenSandbox

v0.9 made sbx serve speak OpenSandbox. v0.10.0 makes it fast, and fills in what was missing:
a warm pool that answers create in about 14 ms, code interpreter contexts, terminals,
snapshots and templates, volumes, and a port proxy. OpenSandbox's own e2e suite now runs 52
tests against sbx, up from 38, and they pass.

It also carries every fix from v0.9.1: if you are on v0.9.0 and run the API without
a key, read that first.


A warm pool: create answers before a container could start

sbx serve --osb-addr 127.0.0.1:8080 --osb-pool python:3.11-slim=8

sbx keeps that many sandboxes of the image already created, with execd answering. create
claims one, gives it a fresh execd token and the caller's env, and returns it Running. The pool
refills behind it. A request the pool cannot honour exactly - volumes, a snapshot, a template, a
network policy, any field it does not know - takes the ordinary path instead, so a member is never
handed out without something the caller asked for.

Measured with scripts/osb-bench.sh, create through the first successful command, the way
ComputeSDK's Burst-TTI benchmark times it (M4, colima 3 vCPU / 3 GiB):

median p95 success
warm pool, one at a time 13.7 ms 41 ms 10/10
cold, one at a time 207 ms 356 ms 10/10
warm pool, 100 at once 472 ms 567 ms 100/100

Cold creates got faster too. A create used to cost about 4 s: 3 s asking the registry about an
image already on the machine, and the rest waiting for the SDK's next 2-second poll. Now the
image is pulled only when absent, and create answers once the sandbox is running, at about
170 ms. A burst of 100 cold creates is too noisy on this machine to state, varying 6x between
runs; the pool is the answer to bursts.


New through the API

Code interpreter Jupyter contexts per language, state kept between cells, results as mime bundles (images, HTML), interrupt. Upstream's code_interpreter suite: 7/7
Terminals PTY sessions over WebSocket, with resize, one writer and read-only viewers, and replay from any point after a reconnect
Port proxy GetEndpoint(port) for any port in the sandbox, over HTTP and WebSocket, authenticated like the rest of execd
Snapshots and templates snapshot a running sandbox, create new ones from it, and name images as templates. The snapshot is the container's filesystem, the same as OpenSandbox's docker runtime
Volumes pvc as docker named volumes (namespaced, so a caller can only name its own), and host only under roots you allow with --osb-host-paths
Egress env OPENSANDBOX_EGRESS_* variables go to the filter and never reach the workload

Tested as people use it

Beyond upstream's suite, scripts/osb-usecases-e2e.sh runs fifteen end-to-end flows against a
real daemon: an agent over MCP, a coding loop through the SDK, the code interpreter, idle freeze
keeping a counter's memory, pause, expiry across a daemon crash, a live egress change, snapshot
and fork, volumes, a terminal, the port proxy, a classic sandbox.json sandbox beside API ones,
scope safety, ten concurrent sandboxes that leave nothing behind, and the pool.

That suite, and the Linux run of the unit tests, found four bugs, fixed here:

  • sbx rm left the image's own data volumes behind - a redis's /data, a postgres's data
    directory - with the sandbox's data in them. It now removes them with the sandbox.
  • sbx rm said it removed an egress filter that never existed.
  • sbx history <sandbox> --json ignored --json when it came after the name.
  • sbx mcp: cancelling a command_run in the moment between execd starting the command and the
    client reading its id left the command running with no interrupt. It is now interrupted.

An independent design review also found, and this release fixes: a failed pause or resume could
leave a sandbox unreachable with no call able to release it; a snapshot image carried the source
sandbox's live execd token; a host volume path could be swapped for a symlink between the check
and the mount; readonly_volumes and volume_mounts had leaked into the public sandbox.json
format. Each fix came with a test that failed before it.


What it costs you

  • Pool members are running containers. Fifty python members held about 620 MiB here. Size the
    pool for your burst, not for your fleet, or pass --osb-pool-freeze to freeze them while they
    wait - which saves their CPU and costs each claim a thaw.
  • API sandboxes check health once a minute, quickly only while they start: 12x fewer
    docker exec calls than v0.9 at the same sandbox count (116 per minute for ten sandboxes, now 10).
  • The OpenSandbox API is docker-only. Kubernetes answers 501 with the reason; a cluster needs
    an init-container way to place execd, which does not exist yet.
  • Known issue: a daemon started with --only is not found by sbx sleep / sbx wake, which
    then fail with "never became ready". Drive a scoped daemon through the API instead.
  • Known issue: a host volume's source is re-resolved by docker on every container start, so a
    sandbox that sleeps with sbx.idle=sleep and wakes again follows a symlink swapped in since. The
    default freeze-on-idle does not restart the container and is not affected.
  • Not yet: isolated sessions, the credential vault, server-proxy mode, and a microVM provider.
    They answer 501 naming the release that adds them.

Adopting it

brew upgrade sbx          # or: go install github.com/aryanmehrotra/sbx@v0.10.0
sbx serve --osb-addr 127.0.0.1:8080 --osb-pool python:3.11-slim=8
export OPEN_SANDBOX_API_KEY="$(cat ~/.sbx/osb/key)"

Check it yourself: scripts/osb-conformance.sh --tier v0.10.0 and scripts/osb-usecases-e2e.sh,
each against a docker engine with no sandboxes on it.

Full Changelog: v0.9.0...v0.10.0