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 averify-resourceawareCI stage asserts the chain down to cgroup values (#2602 #2623 #2628 #2633 #2637) - Fast Sandbox egress parity and hardening: port-scoped
NetworkRule.portsrules 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.machineat build time and projected identically into the CRD (#2623); template-mode creates forward it as per-sandboxresources.limitswhen — and only when — the selected pool reportsResourceAware, withLegacySlot/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}, theuse_server_proxyparameter, the matching SDK options, and the CLI/MCP flags now carry explicit deprecation markers (Deprecation: trueresponse 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: trueon a volume no longer silently stays writable, andcreateIfNotExist: falseno longer leaves automatic volume creation enabled; ten models (image/auth/platform/volume/OSSFS/credential-proxy/snapshot-renew requests) now useextra = "forbid", and the TypeScript authtokenproperty 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
timeoutSecondsas 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-Rangeintegers 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) andcommand_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.roundserialized0.0004 sastimeout: 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 orRetry-Afterwaits 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
SandboxRunningTimeoutErrorregardless of timing, soerrors.Asclassification 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
poolMaxnow 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.opaquedirectories, and the API's own.wh.markers now hide lower entries in stat/read/list/search — afterrm f && mkdir dthe deletedfandd/xwere still readable with their old contents, andRemoveAll("d")leftd/xlistable 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.txtno 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 viaevalwith</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
exitframe instead of racing it (a background child holding the pipe delaysexitby 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 return400instead of a filesystem-dependent 500/empty-200, and a malformed Range header's 416 carries a message (#2451); an invalid/files/searchglob returns 400 instead of 500 (or 200 on an empty directory) (#2687);created_atreports the inode birth time viastatx, 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
portsthrough 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 /policyon the classic sidecar no longer drops untouched rules: the shared merge key let an unrelated patch remove an existingdenyand 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=0disables), 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=falserenders no headless Service/peer port and the agent pulls directly from the origin store), and the controller chart gainscontroller.extraArgsfor unmodeled binary flags (#2566) - ResourceAware integration: a
ResourceAwareSandboxPool fixture withPOOL_MODE=resource-awareselection (#2622), averify-resourceawarestage 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.existingSecretis 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-consolesource collects Fast Sandbox guest console output — node-local VM discovery from driver-writtenfast-sandbox.jsonwithout 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);
Nonetable cells render as-instead of the literal text (#2691); an empty egress rule target is a usage error with theACTION=TARGEThint (#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
readonlyon a volume) are rejected; send each field once, alias or snake_case but not both; in the TypeScript SDK,ImageAuth.tokencombined withusername/passwordnow fails — send onlyusernameandpassword(#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_BODYinstead of a filesystem-dependent 500 or an empty/incomplete 200/206; use the/directories/listroute; a malformed-range 416 gains amessagefield (#2451) - Behavior change: idle-pause activity probe answers must be uncompressed, at most 4 KiB, and complete within
timeoutSecondsas a whole-exchange bound — anything else is an error verdict for that candidate (#2596) - Behavior change: pools whose idle pod count exceeds a lowered
poolMaxnow 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=0to keep the previous on-demand behavior, and matchpoolSizeGBto 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_PORTSwhen 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 tofastpath_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.1stable 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 ❤️
- @Jackmeson1
- @MannXo
- @Pangjiping
- @Yeaury
- @acodercat
- @chichi-1177
- @comqx
- @ddlw
- @fengcone
- @hpliStartAgain
- @ioworker0
- @jianpingpei
- @jwx0925
- @kittimzhe
- @liuyy2
- @mengqing88
- @mhsFlairs
- @pakkah
- @rudycelekli
- @ryrenz
- @ssfdgsg
- @yangjj-iso
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 + tagVerify what you installed against the BOM: see Release Verification.