github opensandbox-group/OpenSandbox release-1.2.1-rc.2
OpenSandbox 1.2.1-rc.2

pre-releaseone hour ago

Second release candidate on the 1.2.1 patch line, carrying everything merged since release-1.2.1-rc.1 (2026-11-04). The headline is OSEP-0026 resource-aware oversubscription landing end to end: the server records each template's effective machine shape, forwards it as per-sandbox limits on ResourceAware pools, and replays recorded limits across snapshot restores, validated by a dedicated KVM verify lane. Fast Sandbox networking reaches parity on port-scoped egress rules, the experimental OSEP-0023 subject revision runtime advances from session foundations to an enforced readiness gate, and the pinned fast-sandbox gains opt-in rootfs-only OCI snapshots. A safety batch closes real data-loss and availability bugs: unknown fields in closed request objects are now rejected, a controller restart can no longer evict pods of live sandboxes, and the server-proxied data path enters maintenance mode. Images publish now; SDK/CLI packages ship at the 1.2.1 stable bump.

Highlights

  • Resource-aware oversubscription (OSEP-0026) lands end to end: template machine shapes are recorded at build time, forwarded as per-sandbox limits when the selected pool reports ResourceAware, snapshot restores replay the source's recorded limits instead of failing, and a verify-resourceaware CI stage asserts the chain down to cgroup values (#2602 #2623 #2628 #2633 #2637)
  • Fast Sandbox egress parity and hardening: port-scoped NetworkRule.ports rules are now enforced per subject (the 400 rejection is lifted) (#2662); DNS leases renew in the kernel and survive neighbour removal (#2652 #2653); the experimental OSEP-0023 subject revision runtime reaches a readiness gate on acknowledged snapshots (#2666)
  • Safety and lifecycle: closed request objects reject unknown fields with 422 instead of silently ignoring them (#2576); a controller restart no longer evicts pods allocated to live sandboxes (#2398); the server-proxy data path is deprecated into maintenance mode — route sandbox traffic through the ingress gateway (#2548)

Server

  • Resource-aware oversubscription (OSEP-0026): each template's effective machine shape (post-default CPU/memory) is recorded in spec_json.machine at build time and projected identically into the CRD (#2623); template-mode creates forward it as per-sandbox resources.limits when — and only when — the selected pool reports ResourceAware, with LegacySlot/older FastPath pools failing safe to field-omitted and the pool-mode read TTL-cached (#2628); snapshot creation records the source's per-sandbox limits and restores replay them — previously a shaped template sandbox restored on a ResourceAware pool fell back to pool defaults and FastPath rejected the restore outright (#2633); FastPath stubs are regenerated with the resource-aware proto fields as groundwork (#2619); the contract is defined in OSEP-0026 (#2602)
  • The server-proxied data path is deprecated into maintenance mode: /sandboxes/{id}/proxy/{port}, the use_server_proxy parameter, the matching SDK options, and the CLI/MCP flags now carry explicit deprecation markers (Deprecation: true response header, runtime warnings per language), with security and bug fixes only — deployments should expose sandbox traffic through the ingress gateway (#2548)
  • Unknown fields in closed request objects are rejected with the sanitized 422 envelope instead of being silently discarded — readonly: true on a volume no longer silently stays writable, and createIfNotExist: false no longer leaves automatic volume creation enabled; ten models (image/auth/platform/volume/OSSFS/credential-proxy/snapshot-renew requests) now use extra = "forbid", and the TypeScript auth token property is deprecated (#2576)
  • The Kubernetes informer tracks writes per object instead of invalidating the whole cache: every create/patch/delete previously marked the entire workload cache unsynced until a full relist (up to 60 s), so a replica with steady writes (renewals are enough) served almost every read from the API server and ended in 429s; reads now go live only for the written object until the WATCH event confirms it (#2599)
  • The idle-pause activity probe honors its verdict contract: it never raises and every failure only drops the current candidate — a deeply nested 200 answer used to escape and skip the rest of the sweep; answers must now be uncompressed, fit 4 KiB, and arrive within timeoutSeconds as a whole-exchange bound (#2596)

SDKs

Code changes below ride the 1.2.1 stable release — rc ships no SDK packages.

Python

  • Malformed or out-of-range Content-Range integers are treated as unknown metadata (64-bit bounded), and async streaming responses are closed when response-metadata mapping fails in standard and isolated-session downloads — previously the underlying stream could stay open (#2570)
  • The Python MCP server exposes background command status and incremental logs: command_get_status (running state, exit code) and command_get_logs (content + opaque cursor), with polling guidance in the tool descriptions (#2648)

JavaScript

  • Positive sub-millisecond command timeouts round up to at least 1 ms — Math.round serialized 0.0004 s as timeout: 0, which disables the runtime bound for ordinary commands and selects the server default in Bash sessions (#2440)

Go

  • The wake budget bounds in-flight retries: after a marked wake 503, further attempts get a deadline for the remaining WakeOverallDeadline (headers only — established SSE streams and download bodies are never cut off) (#2572), and generic backoff or Retry-After waits that cannot fit the remaining budget are skipped so a mixed-failure retry no longer sleeps past the deadline or hides the ordinary error behind an older wake marker (#2611)
  • Pool hardening: a cancelled acquire no longer consumes a healthy idle entry or triggers a creation (#2616); graceful shutdown honors caller cancellation while waiting for independent acquires to drain (#2617); idle maintenance (shrink, near-expiry reaping) continues during creation backoff instead of being skipped for the whole tick (#2565)
  • An SSE response starting with a UTF-8 BOM no longer drops the first data frame or its event/id field — a command init frame could lose the execution ID (#2618); a running-timeout deadline expiring inside a status request now returns SandboxRunningTimeoutError regardless of timing, so errors.As classification is stable (#2668)

SDK READMEs catch up with the shipped surface: lifecycle idlePolicy + wake-retry, renewalPolicy, and port-scoped rules documented in all five languages (#2590); exchange credentials and fsb template create/poll flows documented for JavaScript, Python, Go (#2597) and Kotlin (#2620); the network isolation example uses the real imports and rule class (#2711)

Controller

  • Pool eviction after a controller restart no longer deletes pods of live sandboxes: the in-memory allocation store was only recovered inside Schedule, but eviction ran first on every pool reconcile — on the first reconcile after a restart every pod labelled for eviction looked idle and was deleted, including allocated ones; recovery now runs before any store read (#2398)
  • Lowering poolMax now scales idle pool pods in: it previously only capped growth, so idle pods above the new bound ran indefinitely; only idle pods are deleted, and already-planned deletions count toward the bound (#2470)
  • A deleted BatchSandbox's status-RV expectation is dropped, fixing unbounded leader heap growth — the entry for the last status patch of every BatchSandbox ever reconciled stayed for the life of the process (#2646)
  • Leader election timing is configurable: --leader-elect-lease-duration, --leader-elect-renew-deadline, and --leader-elect-retry-period (plus chart values), so operators on slower control planes can stop a 10 s API-server stall from forcing a full re-election; defaults are unchanged (#2651)
  • Chart: the controller feature-flags ConfigMap is rendered into the controller's namespace — it went to the Helm release namespace, where the controller never read it, so controller.podRecovery.* was silently ignored (#2694)

Execd

  • The isolated-session files API hides overlay-deleted entries: kernel whiteouts (0/0 devices), user.overlay.opaque directories, and the API's own .wh. markers now hide lower entries in stat/read/list/search — after rm f && mkdir d the deleted f and d/x were still readable with their old contents, and RemoveAll("d") left d/x listable until now (#2469)
  • Copy-up on rename and chmod is faithful to the lower layer: mode (including setuid/setgid/sticky) and session ownership are preserved, symlinks stay symlinks, lower-only directories can be renamed, and rm f.txt && mv f.txt g.txt no longer resurrects the deleted file; chmod refuses symlinks instead of following them to a host path (#2638)
  • MITM CA trust races against egress restarts are closed: bootstrap only trusts a CA that appeared after an observed absence or survived a stabilization window, and a lifetime watcher reinstalls system/merged/NSS/JDK trust whenever the exported CA content changes — previously a sidecar restart left the sandbox with the rotated-out CA everywhere until rebuild (#2582)
  • Foreground isolated runs no longer write their script to the shell's stdin: code that reads stdin (read, cat) consumed the rest of the script including the end marker, corrupting the exit code and hijacking the session; code now runs via eval with </dev/null, a syntax error ends the run with status 2 and the session survives (#2517)
  • Smaller fixes: the PTY holder's final output chunk is flushed before the exit frame instead of racing it (a background child holding the pipe delays exit by at most 2 s) (#2644); cgroup v1 metrics and OOM watch honor the container's mount root, so Docker-on-cgroup-v1 reported host CPU/memory instead of the sandbox's limits (#2641); directory downloads return 400 instead of a filesystem-dependent 500/empty-200, and a malformed Range header's 416 carries a message (#2451); an invalid /files/search glob returns 400 instead of 500 (or 200 on an empty directory) (#2687); created_at reports the inode birth time via statx, so chmod/rename no longer change it (#2604); Jupyter completion is derived from the kernel's actual outcome (reply + idle, IOPub errors retained) instead of racing it (#2606)

Networking

egress

  • Port-scoped egress rules are enforced per subject in fast sandbox: the per-subject nft path gained TCP port allow/drop chains (MITM matches the connection's original port, not the proxy listener), the fsb server passes ports through and merges by (target, ports), and the create-time 400 rejection is lifted — deny-before-allow and TCP-only semantics match the classic profile, and domain+ports stays rejected in both (#2662)
  • PATCH /policy on the classic sidecar no longer drops untouched rules: the shared merge key let an unrelated patch remove an existing deny and open blocked traffic (fail-open); base rules of scopes absent from the patch are now kept, matching the documented contract (#2664)
  • Fast-sandbox DNS leases renew in the kernel: the plain timed-element add succeeds but leaves the kernel expiry unchanged, so new connections to a cached IP were dropped once the first timeout lapsed; renewal now uses the add/delete/re-add sequence (#2652), and rebuilding the shared nft table when one subject is removed re-adds the remaining subjects' unexpired leases instead of leaving idle cached IPs blocked (#2653)
  • The experimental OSEP-0023 subject revision runtime advances [EXPERIMENTAL] (behind OPENSANDBOX_EGRESS_EXPERIMENTAL_REVISION_RUNTIME, admission still disabled): installation-only subject sessions with authenticated per-child IPC and fresh control generations (#2589), a FIFO lifecycle drain worker recording SET/REMOVE/rebind/takeover intents (#2609), per-subject decision snapshots installed after binding and after every Vault/policy mutation (#2634 #2649), and the data-plane-ready hook now gates on an acknowledged snapshot for the current fence/generation/digest — returning 503 while authority or the ACK is missing (#2666); the ownership and revision integration contract is pinned in OSEP-0022/0023 (#2586)
  • The experimental sidecar live revision runtime rejects extra interception ports at startup and fails closed on non-canonical (non-443) TLS destination ports, instead of letting them reach the later HTTP authority gate (#2587)

ingress

  • Wake policy reads reuse a single Kubernetes config: serving regular and fast-sandbox routes with wake enabled parsed REST config twice, and the second global flag registration panicked at startup with flag redefined: cluster (#2600)

Fast Sandbox

  • Native ublk warm pool on OCI deployments: OverlayBD now starts with a 4/8 pool of 2 GiB devices (chart-tunable runtime.oci.poolLow/poolHigh/poolSizeGB, poolLow=0 disables), so template creates hit pre-allocated devices instead of allocating on demand; CI asserts warm hits with no misses or daemon restarts (#2594 fast-sandbox#163)
  • Opt-in rootfs-only OCI snapshots (FSEP-0001) on the OverlayBD path: capture persists only the writable rootfs layer without a memory dump, and restore boots instead of resuming in place — smaller artifacts with no memory-sized staging window and no CPU-model pinning, with the artifact manifest driving restore; a failed RootfsOnly recovery suspends the route until recovery succeeds; runtime-level capability, the OpenSandbox snapshot API wiring comes later (#2706 fast-sandbox#173 fast-sandbox#167)
  • Lifecycle hardening: Fastlets are isolated from VM-pool OOM and retain drain errors, resource diagnostics, and failed runtime identities across readiness phases (#2626 fast-sandbox#162); immediate pause after create no longer races the controller's status projection — an interrupted pause keeps its intent and a concurrent resume preserves checkpoint lineage, so no client-side settle delay is needed (#2626 fast-sandbox#166)
  • Snapshot and reconciler recovery: quarantined captures are reconciled and unsafe routes withdrawn, exited quarantined runtimes retired, vsock paths isolated, suspended routes restored, and terminal Fastlet Pods are replaced only in active pools; reset revision timestamps preserve fractions (#2706 fast-sandbox#172 fast-sandbox#170)
  • Charts: the fast-sandbox controller and firecracker-runtime workloads accept podLabels/podAnnotations/nodeSelector/affinity, the runtime's DART P2P mesh can be disabled (runtime.p2p.enabled=false renders no headless Service/peer port and the agent pulls directly from the origin store), and the controller chart gains controller.extraArgs for unmodeled binary flags (#2566)
  • ResourceAware integration: a ResourceAware SandboxPool fixture with POOL_MODE=resource-aware selection (#2622), a verify-resourceaware stage asserting shaped-template forwarding at CR/cgroup/saturation levels (#2637), and the PR integration lane now runs the ResourceAware stack by default while nightly keeps LegacySlot coverage (#2647)
  • Smaller fix: the fast-sandbox chart skips the dev route-key Secret when routeKeys.existingSecret is set — production installs no longer render the published development-only Ed25519 private key (#2695)

Misc

  • Console: a browser sandbox shell bridges xterm to execd's PTY over a BFF WebSocket — entries on the tenant and admin lists, tenant keys and execd tokens stay on the BFF, one page open is one PTY (#2233); a dropped execd connection after connect reports as a lost shell instead of an error traceback claiming the shell failed to open (#2639); UI labels are translated to English (#2657)
  • node-agent: an opt-in vm-console source collects Fast Sandbox guest console output — node-local VM discovery from driver-written fast-sandbox.json without CR watches, raw-byte fidelity per sandbox UID across placement changes, and restart-safe acknowledged offsets (#2234); the integration environment deploys it and asserts collection end to end (#2676); ops guidance covers path alignment, node coverage, and discovery rules (#2677)
  • CLI: non-table configuration sections are rejected with a clear error instead of crashing every command (#2551); None table cells render as - instead of the literal text (#2691); an empty egress rule target is a usage error with the ACTION=TARGET hint (#2692)
  • OSEP-0025 follow-up: resolver caller authentication is specified — a cached Ed25519 JWT on every TCP resolve dial, verified offline against a server-published JWKS, closing the deferred R4 gap at the application layer (#2631)
  • Governance: maintainer nomination and removal rules are tightened — majority approval for component maintainers, path-owner support, sponsor LGTMs, and an emeritus/removal process (#2678); @jianpingpei is nominated as node-agent maintainer (#2679)
  • OSEPs and docs: OSEP-0024/0026 and six earlier OSEPs are marked implemented, OSEP-0006 (developer console) is withdrawn (#2624 #2670 #2671 #2672 #2673); the OSEP index page is removed from the docs site — oseps/ on GitHub is the canonical index (#2674)

Upgrade & Compatibility

  • Breaking: unknown fields in closed request objects now return 422 — previously ignored keys (for example readonly on a volume) are rejected; send each field once, alias or snake_case but not both; in the TypeScript SDK, ImageAuth.token combined with username/password now fails — send only username and password (#2576)
  • Deprecation: the server-proxied data path (/sandboxes/{id}/proxy/{port}, use_server_proxy, the SDK/CLI/MCP options) is in maintenance mode — no new features, security and bug fixes only; expose sandbox traffic through the ingress gateway (#2548)
  • Behavior change: code in a foreground isolated run can no longer read the session's stdin (it only ever got the run's own script that way), and a syntax error now ends the run with status 2 while the session survives — previously it killed the shell (#2517)
  • Behavior change: downloads of a directory return 400 INVALID_REQUEST_BODY instead of a filesystem-dependent 500 or an empty/incomplete 200/206; use the /directories/list route; a malformed-range 416 gains a message field (#2451)
  • Behavior change: idle-pause activity probe answers must be uncompressed, at most 4 KiB, and complete within timeoutSeconds as a whole-exchange bound — anything else is an error verdict for that candidate (#2596)
  • Behavior change: pools whose idle pod count exceeds a lowered poolMax now scale the excess in (only idle pods are deleted) (#2470)
  • Fast Sandbox: OCI-enabled deployments reserve an idle ublk device pool by default — set runtime.oci.poolLow=0 to keep the previous on-demand behavior, and match poolSizeGB to the built template capacity for custom disk sizes (#2594)
  • Fast Sandbox: port-scoped egress rules (NetworkRule.ports) are now accepted and enforced on fast-sandbox routes — they previously returned 400; existing rules are unaffected (#2662)
  • Experimental egress live revision runtime only: extra interception ports are rejected at startup — remove OPENSANDBOX_EGRESS_MITMPROXY_EXTRA_PORTS when enabling that runtime (#2587)
  • Experimental fast-sandbox revision runtime only (OPENSANDBOX_EGRESS_EXPERIMENTAL_REVISION_RUNTIME=true): the data-plane-ready hook may return 503 until the subject snapshot is acknowledged; creates then wait up to fastpath_wait_ready_seconds (default 30 s) and return Pending/Unavailable (#2666)
  • Images: opensandbox/<component>:release-1.2.1-rc.2 (all three registries; fsb images for fast-sandbox)
  • Packages: unchanged — SDK/CLI artifacts publish at the 1.2.1 stable release
  • Charts: render from this tag (helm template ./manifests/charts/opensandbox)
  • Kubernetes: supported range v1.21 – v1.34
  • Skew: server ↔ CLI/SDK same line supported; ±1 minor warns

👥 Contributors

Thanks to these contributors ❤️

Artifacts

  • Images: opensandbox/<component>:release-1.2.1-rc.2 (Docker Hub / GHCR / ACR)

Installation

# Platform (Kubernetes) — render the chart at this tag and apply
git clone https://github.com/opensandbox-group/OpenSandbox
cd OpenSandbox
git checkout release-1.2.1-rc.2
helm dependency build manifests/charts/opensandbox  # package file:// sub-charts (not committed)
helm template ./manifests/charts/opensandbox | kubectl apply -f -
# or point your GitOps platform (Argo / Flux) at the repo path + tag

Verify what you installed against the BOM: see Release Verification.

Don't miss a new OpenSandbox release

NewReleases is sending notifications on new releases.