github opensandbox-group/OpenSandbox release-1.2.0
OpenSandbox 1.2.0

5 hours ago

Stable cut of the 1.2.0 line, carrying everything merged since release-1.1.1 through two release candidates plus a post-rc hardening delta. Images shipped with the rcs; SDK/CLI/server packages publish at 1.2.0. Everyone on 1.1.x should upgrade. Baseline for every section below: release-1.1.1.

Highlights

  • Auto-pause and wake-on-access for fast sandboxes (OSEP-0024) lands end to end: the ingress records activity and parks requests to paused sandboxes until the restore completes, the server runs an idle sweeper behind an opt-in lifecycle.idlePolicy create field, and all five SDKs get the wake-retry contract — off by default, additive only (#2156 #2180 #2192 #2198 #2218)
  • Credential Vault (OSEP-0022) lands end to end for fast sandboxes: the egress sidecar holds the vault with durable per-subject persistence and sealed pause/resume transfer, the server exposes the user-reachable vault API with credential values never appearing on any public read, and the MITM CA is pinned per pool so an egress restart no longer rotates the trust anchor under live sandboxes — plus port-scoped egress rules end to end in the classic profile (#2249 #2252 #2255 #2263 #2251 #2212)
  • Fast Sandbox snapshots and template builds can publish to and boot from OCI registries (OverlayBD OCI lifecycle), and a reliability campaign removes whole-call timeouts from SDK file transfers in all five languages, makes execd report sandbox-scoped CPU/memory and OOM kills, and fixes a long list of Docker-backend correctness and PTY/session output issues (#2338 #2165 #2200 #2191)

Server

  • Implement the server side of OSEP-0024 auto-pause for fast sandboxes: the lifecycle.idlePolicy create field (idleTimeoutSeconds 30–86400, onIdle: pause, wakeOnAccess, optional activeProbe) persists as a reserved Sandbox CR annotation; an idle sweeper enumerates opted-in fsb sandboxes, checks activity in Redis, takes a per-sandbox pause lock, and issues an idempotent FastPath PauseSandbox; a declared activeProbe runs through the ingress gateway, and an OpenSandbox-Skip-Activity sentinel suppresses one observation and is stripped on both gateway and proxy hops. Config moves under [automation.idle_pause], and redis.dsn is validated at load when the sweeper is enabled (#2192 #2181 #2215)
  • Implement the Credential Vault API (OSEP-0022, fast-sandbox profile): POST|GET|PATCH|DELETE /v1/sandboxes/{id}/credential-vault pushes complete vault revisions over the FastPath handler relay and treats the relay response as the synchronous egress-validated ack — the CRD never carries vault material and every public read is sanitized server-side; PATCH composes in memory with expectedRevision as the cross-replica backstop, non-fsb backends proxy the sidecar's /credential-vault surface, and the fsb credentialProxy create-time rejection is lifted (#2263)
  • Bake the pinned MITM CA into server-created SandboxTemplates (OSEP-0022): new [kubernetes] template_mitm_ca_secret config (default off) emits spec.egressMitmCertificate — the certificate entry only, never the private key — on every template; the Secret must exist in each template namespace (ops-runbook distribution), and a missed copy fails the build observably (#2288)
  • Add lifecycle.renewalPolicy (OSEP-0009): the renew-on-access opt-in moves from the opaque extensions["access.renew.extend.seconds"] key to a structured renewalPolicy.extendSeconds (300–86400); the legacy key is deprecated (supported throughout 1.x, removed in 2.0), and fast sandbox rejects it with 400 rather than silently accepting a no-op (#2296)
  • Implement the diagnostics context endpoint (spec in #2236): GET /v1/sandboxes/{sandboxId}/diagnostics/context returns the sandbox ID, lifecycle state, backend type, and backend-defined identifiers for Docker, Kubernetes, and Fast Sandbox without connecting to execd — Kubernetes context stays available before a Pod is allocated; extension backends that don't implement it return 501 (#2292)
  • Forward the still-encoded path through the sandbox proxy: Starlette bound the {full_path:path} route from the percent-decoded path, so a decoded ? started a query, %2F became a separator, and %2E%2E segments were resolved — closing a prefix-escape on Docker host-mapped endpoints; the proxy now takes the client's still-encoded tail from the raw path (validating it decodes to the routed path) and reads the query from the raw query string, so backends receive request targets byte-for-byte, matching the ingress gateway (#2228)
  • Keep blocking Docker calls off the event loop: proxy endpoint lookups, network-existence validation, and per-PVC volume inspect/create ran synchronously inside async handlers, so a slow Docker daemon (e.g. during a large pull) froze every other request including proxy traffic; they now run on worker threads with tenant context preserved, and volume-validation failures still clean up auto-created volumes (#2268 #2366)
  • Stop reporting image-inherited labels as sandbox state on Docker: metadata reads fell back to every non-reserved container label — including labels merged in from the image — and restored containers inherited reserved labels such as expires-at, host ports, and the egress token; in the worst case a restored TTL sandbox was deleted as already expired after a server restart. Creates now record the request's metadata keys in a reserved label and read back only those keys, and reserved labels present on the image but not set by the sandbox are blanked before container start (#2363 #2269)
  • Reject gVisor snapshots that would silently drop writes: docker commit under gVisor's default --overlay2=root:self can report READY while restoring none of the sandbox's writes; snapshot preflight now refuses gVisor unless the effective args include --overlay2=none, and create fails closed when the filesystem diff is filestore-only (#2241)
  • Keep an fsb snapshot whose create reply was lost: a UNAVAILABLE/DEADLINE_EXCEEDED from FastPath after the request was durably accepted returned 500 without writing a row, stranding the SandboxSnapshot and its published artifacts with no record; the create now probes for the snapshot before failing and reports it as submitted when FastPath persisted it (#2361)
  • Fix the remaining Docker-backend rough edges: BatchSandbox expiry reports Terminated/SANDBOX_EXPIRED instead of Running past spec.expireTime (#2238); concurrent lifecycle conflicts map to 404/409 instead of 500 (#2387); concurrent metadata patches no longer race into 500s or dropped keys (#2267 #2281); createdAt stays stable across reads and pause/resume transitions are recorded (#2405); network policy actions are validated and unknown fields return 422 (#2383 #2385); the sandbox image is read from the container config so a docker rmi -f'd image no longer 500s GET /sandboxes (#2353); template reads trust the database projection instead of syncing the CR on every create-gate check (#2326); the renew cooldown applies to every renew source when the Redis consumer is enabled (#2276 #2279); BatchSandbox pods default to restartPolicy: Never so a kubelet restart can no longer wipe the writable layer while reporting Succeed (#2128); podman's pasta/slirp4netns/none network modes pass through (with networkPolicy rejected, since there is no bridge to attach to) (#2203); Docker timestamps with unusual fractional seconds no longer silently report the current time (#2220); and the execd cache container honors [docker] security profiles and secure_runtime (#2254)

SDKs

Code changes below ride the 1.2.0 packages.

  • Add the OSEP-0024 SDK contract to all five languages: a typed lifecycle.idlePolicy create field with client-side fail-fast validation, plus the wake-retry contract — on a 503 carrying the gateway-owned OpenSandbox-Wake: expired marker (its presence proves the request was parked and never forwarded, making any-method retry safe), the SDK resends after Retry-After under a bounded budget (default 5 attempts / 60 s, per-client knobs) and exhaustion raises a typed wake-timeout exception; bare 503 handling is unchanged (#2198 #2218). The contract initially bypassed streaming transports; the request that starts a stream now wake-retries in JavaScript, Python, and Go — established streams are never replayed — while C#/Kotlin stream starts currently fail fast (#2357 #2426)
  • Add the Credential Vault client contract to all five SDKs (OSEP-0022): template-backed sandboxes route vault calls through the server on the same template-origin signal as network policy, while sidecar-direct adapters stay untouched (#2263)
  • Stop capping file uploads and downloads at the request timeout (30 s default) in all five SDKs — the whole-call timeout covered sending the body and reading the response, so any large or slow transfer failed mid-flight even while healthy; transfers now run on a dedicated client with no whole-call deadline that still bounds the wait for response headers, so a stalled transfer still fails instead of hanging (#2165 #2186 #2196 #2206)
  • Implement diagnostics across the SDK surface (with #2292): context is exposed through both the manager and sandbox facades in all five languages, and JavaScript, Go, and C# gain public logs/events APIs matching the existing Python/JVM surface (#2292)
  • Add lifecycle.renewalPolicy to all five SDKs (OSEP-0009): a typed create field with client-side fail-fast validation (extendSeconds 300–86400), replacing the opaque extensions key (#2296)
  • Support port-scoped egress rules in all five SDKs: NetworkRule.ports (TCP 1–65535, at most 256 per rule, target now optional) with client-side validation mirroring the server (range, duplicates, cap, domain+ports rejected); JavaScript encodes the target-or-ports invariant as a union type (#2212)
  • Surface the authoritative execution_complete.exit_code in Go and Kotlin, preferring it over the previous evalue/inferred-0 fallbacks while staying compatible with older servers (#2330); honor an explicitly supplied OPEN-SANDBOX-API-KEY header over the configured key regardless of header-name casing in JavaScript, C#, and Kotlin (#2348 #2372 #2375); suppress sandbox.create metrics for client-pool warmup creates in Go, Python, and JavaScript (#2199)
  • Strip protected credentials on cross-origin redirects in JavaScript, C#, and Go: sandbox-prefixed headers, endpoint tokens (X-EXECD-ACCESS-TOKEN, X-API-Key), and configured authentication headers are removed whenever a redirect changes scheme/host/port, while same-origin hops and unrelated headers are preserved; file uploads never follow redirects in JavaScript, and in browsers an opaque redirect fails closed rather than risk replaying credentials (#2178 #2173 #2183)
  • Route the JavaScript and C# code-interpreter data-plane headers through the shared policy: direct-mode /code requests no longer carry the tenant OPEN-SANDBOX-API-KEY to execd, an explicitly configured connection key keeps precedence over the configured apiKey without duplicate case-variant entries, and endpoint-scoped credentials still win (#2149)

Python

  • Retry the gateway's wake timeout when starting a stream: the SSE clients stripped the retry wrapper entirely, so commands.run, session runs, and codes.run against an auto-paused sandbox failed with the marked 503 whenever the restore outlasted the gateway's park budget; streaming transports now carry a wake-only retry (no general retries, no added deadlines), and the docs no longer claim streams bypass retries (#2357)
  • Normalize the connection domain: a trailing slash or /v1 in domain / OPEN_SANDBOX_DOMAIN produced //v1/... URLs that the server 404'd — the same variable worked in every other SDK; Python now strips both (#2347)
  • Reuse SSL contexts across owned transports: CA certificates were reloaded on every call, blocking the event loop under asyncio concurrency — measured create P50 improves ~18–28% under concurrency (#2360)
  • Preserve the first SSE event after a UTF-8 BOM (including one split across network chunks) (#2226); close wake-timeout response bodies on cancellation (#2371); close interrupted async file-download responses so a cancelled or failed read no longer occupies the connection (#2369); keep the status code when an error response carries a non-JSON body — a proxy's HTML/empty 404/500 previously surfaced as InvalidArgumentException with no status (#2402); raise the shared structured error from Code Interpreter codes.run failures instead of a hand-built exception that lost retry_after (#2364); keep code-interpreter output fragments together in Execution.text instead of inserting extra newlines (#2408); round sub-millisecond command timeouts up to 1 ms instead of sending 0 (which disabled the timeout) (#2419); stop propagating a cancelled endpoint fetch to concurrent waiters (#2277); forward credential_proxy through sandbox pools, which silently dropped it before (#2283); stop reporting failed file replacements as successful (#2222); clean up cancelled async pool startup instead of getting stuck in STARTING (#2209); regenerate the lifecycle client to drop stale hardcoded defaults (wire behavior unchanged) (#2280); MCP: cancelling one tool call no longer cancels a coalesced shared connect for the same sandbox, which failed every other waiting call (#2355)

JavaScript

  • Preserve execution events with CR line endings: the SSE parsers only split on LF, so a valid CR-delimited stream silently dropped the execution ID, output, and completion event (#2374)
  • Scope in-flight endpoint-cache invalidation to each sandbox, so invalidating sandbox B no longer discards a pending fetch's result for sandbox A (#2349)
  • Preserve reused byte chunks in file uploads: an async producer that reuses a Uint8Array/Buffer view uploaded the mutated content twice instead of both snapshots (#2428); keep caller cancellation active after response headers arrive — aborting mid-SSE-stream or mid-download previously left the body running (#2350)
  • Strip protected headers on cross-origin redirects (see the cross-SDK entry above) (#2173)

Kotlin

  • Keep an explicit API key header ahead of the configured key in any casing, and stop attaching an empty key header when unconfigured (#2375)
  • Preserve the raw response body on deserialize failures and catch LinkageError from a serializer/runtime mismatch — previously an AbstractMethodError escaped every handler, killing SSE streams silently with the server's actual response unrecoverable (#2416)

C#

  • Honor an explicit API key header regardless of casing: the case-sensitive check added a second, conflicting default entry, and the metrics reporter replaced the caller's credential (#2372)
  • Strip protected credentials on cross-origin redirects and route code-interpreter requests through the shared data-plane header policy (see the cross-SDK entries above) (#2183 #2149)

Go

  • Stop retrying non-idempotent writes by default: with retry enabled, 27 POST/PATCH call sites (sandbox/session/command/file/snapshot/vault mutations) could execute twice and still return success — POST/PATCH no longer retry status codes by default, retry only transport failures proven to be pre-send (dial/CONNECT/TLS before request bytes), and SSE bootstraps bypass the generic retry loop; callers who accept duplicate-execution risk can opt in via RetryConfig.RetryableStatusCodesNonIdempotent (#2342)
  • Retry the gateway's wake timeout when starting an SSE stream: the six streaming call sites (command/code/session runs, metrics watch) previously failed immediately with the marked 503; the bootstrap now parks within the same Retry-After budget and returns the typed wake-timeout error on exhaustion — established streams are never replayed, a bare 503 is still not retried even with write-status opt-in, and a 503 reached through a redirect chain loses wake provenance so an already-executed request is never resent; callers relying on fail-fast can pass WakeRetryDisabled() (#2426)
  • Keep line terminators in Execution.Text(): execd already includes \n/\r in event text and requires concatenation to reconstruct stdout, but the helper inserted an extra newline between terminated events ("a\n" + "b\n" became "a\n\nb\n"); a separator is now added only when the preceding event does not already end in a terminator (#2381)
  • Strip the tenant API key from direct-mode data-plane requests: a tenant key supplied through ConnectionConfig.Headers was delivered to execd and the egress sidecar inside the untrusted sandbox — completing the credential-exposure fixes across all five SDKs (#2337)
  • Authenticate proxied data-plane requests with the API key header instead of smuggling the tenant key into the sandbox, fixing 401s in multi-tenant proxy mode (#2274); surface execution_complete.exit_code (#2330)

Controller

  • Retain a BatchSandbox until cleanup completes: a deleted sandbox could disappear from the lifecycle API (GET 404) while its owned pod still ran, letting a replacement sandbox write the same persistent directory before the old writer exited; deletion now uses foreground cascading deletion alongside the existing finalizers — queries report Stopping during managed cleanup, and 404 arrives only after the runtime is gone (#2154)
  • Fix task-executor treating an empty exit file as exit code 0, which reported a failed or interrupted task as Succeeded when the shim was killed mid-write (#2270)

Execd

  • Report sandbox-scoped CPU/memory metrics and OOM kills: GET /metrics read the host's /proc inside the container, so dashboards showed host-wide numbers; usage now comes from the sandbox's own cgroup when limits are configured (host view kept for unlimited guests), and a cgroupfs poller diffs the kernel OOM kill counter every 5 s to export execd.system.oom.kills with structured warn logs — no capabilities required (#2200 #2191)
  • Serve the session-private /tmp through the files API: the strict isolation profile's /tmp was an anonymous tmpfs with no host-side path, so files-API paths under /tmp failed and callers had to shuttle artifacts through base64 shell commands; /tmp is now a host-backed per-session directory bind-mounted into the namespace — same private semantics, but readable and writable through the existing files endpoints (#2424)
  • Support browser PTY WebSocket authentication: browsers cannot set X-EXECD-ACCESS-TOKEN on a native WebSocket handshake, so terminal connections needed a BFF relay; the PTY upgrade now accepts the existing execd token via the opensandbox-pty subprotocol's credential entry (Kubernetes-style) with no new token issuer and header precedence preserved for SDK/CLI clients (#2334)
  • Keep isolated sessions alive when a foreground run is cancelled or disconnected: the run's context derived from the HTTP request, so a disconnect or timeout_seconds expiry SIGINT'd the whole process group including the persistent session shell — the session died and every later run 404'd; runs now interrupt only the running command (with a bounded SIGKILL escalation) while the session survives, and silent foreground runs emit periodic SSE pings so gateway idle timeouts no longer close the stream (#2351)
  • Preserve bash session output line endings: run_in_session stdout events dropped \n/\r bytes, so blank lines were unrecoverable and concatenating events did not reconstruct the output; event text now carries its original line ending — consumers should concatenate event text per run (#2318)
  • End a Jupyter execution when its kernel dies or the connection drops: both cases previously left the code-interpreter request open indefinitely with SSE pings keeping it alive; they now fail the execution (KernelDied / KernelConnectionLost) exactly once (#2401)
  • Keep reading session output past a line longer than 16 MiB: the read loop stopped at the cap — bash sessions hung until the 24 h default timeout, and isolated sessions leaked output into the next run; over-long lines are now passed on in pieces (never splitting UTF-8) (#2275)
  • Run large background code in isolated sessions: the wrapper passed code as a single bash -c argument, and Linux caps one exec argument at 128 KiB — background code above that failed with only Argument list too long; it is now delivered through stdin like the foreground path (#2400); probe private-network capability so restricted runtimes get a structured error for share_net=false sessions instead of an opaque EOF (#2333)
  • And the smaller fixes: report exit_code explicitly on command completion and errors (#2303 #2316); send run errors after the stream started as framed SSE error events instead of a bare JSON body the Python SDK dropped (#2354); keep PTY output buffered until drained when the shell exits, so the last screen of output is no longer lost (#2362); always send the WSCloseTakenOver (4001) close frame on PTY takeover, even mid-write (#2376 #2378); write an isolated run's script while reading its output so a chatty run can no longer deadlock on the stdin/stdout pipes (#2399); reject out-of-scope absolute paths in the session files view instead of silently touching the wrong file (#2322); serve binds mount points through the isolated session files API (#2299); truncate command output files before unlink so daemons holding the fd can't cause invisible disk growth (#2295); omit invalid upper-usage metrics and fail quota admission closed on scan errors (#2257); deduplicate JDK MITM CA imports across shared trust stores (~6.2 s → ~0.6 s for four JDKs) (#2310); default PTY sessions to TERM=xterm-256color when unset (#2313); percent-encode quotes and backslashes in Content-Disposition filenames (#2161); and parse the Range header before setting download headers so a malformed range returns a clean JSON 416 (#2216)

Networking

egress

The OSEP-0022 vault items concern the fast-sandbox profile's credential-vault surface; the OSEP-0023 items remain scoped to the default-off experimental revision runtime.

  • Add the egress-held vault for fast sandboxes (OSEP-0022), landing in stages: the internal /vault/{sandboxUid} relay surface with create-only POST (409 on duplicate, atomic across server replicas), full-revision PUT with expectedRevision and an expectedIncarnation precondition so a stale writer can never land on a reset's successor, value-including GET, and DELETE that keeps the policy (#2249); durable per-subject persistence — one file per subject under a Pod-scoped volume (optional tmpfs), the same commit protocol as policy files so acked means durable and applied, incarnation-fenced reload, and purge on unbind (#2252); and the superseded fastlet-proxy vault route removed (#2247)
  • Carry the vault across pause/resume (OSEP-0022 "Vault Lifecycle"): VAULT_EXPORT copies the vault file into the snapshot bundle strictly before runtime teardown and seals the subject (all vault ops answer 409 while paused; enforcement continues), and VAULT_IMPORT is the only authorized incarnation transfer — a genuine export failure fails the pause checkpoint so teardown never runs without a bundle, and a vaultTransfer capability signal lets an egress without vault persistence skip the transfer instead of failing (#2255 #2290)
  • Pin the fast-sandbox MITM CA (OSEP-0022): the pool-pinned, versioned CA is seeded from a read-only secret mount into the mitmdump confdir, replacing generate-on-start — an egress restart no longer rotates the CA and silently invalidates every live sandbox's TLS trust; a missing or mismatched CA refuses only the MITM addon with mitmCaFingerprint / mitmCaDown surfaced on /healthz (#2251)
  • Define stale-subject takeover on slot reuse: the fast-sandbox dispatch key is the slot IP and slots are reused, so a SET_BINDING can claim a key still held by a dead-but-registered subject — egress now treats that as authoritative, tearing down the stale subject's nft rules, DNS refcount, MITM redirect, and vault slice before registering the successor deny-first (#2250)
  • Align the fast-sandbox network policy with the vault pattern: the recorded policy becomes egress-held state — a /policy/{sandboxUid} relay surface with revision preconditions, durable per-subject files, POLICY_EXPORT/POLICY_IMPORT snapshot transfer, and, after an egress restart, the committed file superseding the replayed create-time binding input (#2309)
  • Support port-scoped egress rules end to end (classic profile): NetworkRule.ports (TCP 1–65535, ≤256 per rule) makes target optional — port-only rules, or ports combined with an IP/CIDR; domain+ports is rejected since domain rules evaluate at the DNS proxy layer; an operator-shipped reachable.ports.cap whitelist is enforced as an always-deny ahead of every user rule so even defaultAction: allow cannot open a non-whitelisted port, and the fast-sandbox route rejects ports with 400 until the fastlet nft path can enforce them (fail-closed, documented) (#2212)
  • Exempt the MITM redirect target from port-scoped rules: with the operator port floor and transparent MITM both active, every intercepted flow was silently blackholed — the iptables REDIRECT rewrote dport 80/443 to the local listener before the filter chain ran, so the floor-complement drop judged the rewritten port; the listener tuple is now accepted ahead of every port-scoped rule, adding no new reachability, and a non-whitelisted intercepted port now fails visibly at the proxy instead of passing silently (#2435)
  • Continue the OSEP-0023 experimental foundation [EXPERIMENTAL] across the line: live credential-bound Vault admission with per-request rotation (#2176), acknowledged Policy revisions before publication (#2182), HTTP/2 admission with bounded drain (#2201), runtime-fault containment on an unexpected MITM exit (#2172), atomic revision policy-file persistence (#2162), nft apply-effect classification (#2167), static TLS passthrough selectors with startup rejection for conflicting native lists (#2229), TLS admission counters (#2265), the installation-only subject revision IPC foundation (#2284), and converged Fast Sandbox subject transitions ahead of the v2 lifecycle consumers (#2294)
  • And the smaller items: ship egress-trace, a read-only admission viewer inside the egress image (live policy state, nft sets, DNS leases, per-connection verdicts, and a probe subcommand; decrypted contents are never shown) (#2230); forward DNS queries over TCP when the client used TCP, so large truncated answers (SPF/DKIM TXT, many-record responses) finally arrive (#2358); preserve shared gateway DNS rules when the last subject on one fast-sandbox gateway releases, which previously removed the entire shared nft table (#2427); trim fsyncs from the create-time policy bootstrap and add per-stage create-latency instrumentation — real warm delivery measured sub-100 ms end to end with policy enforced (#2327)

ingress

  • Implement OSEP-0024 activity tracking and wake-on-access: with --activity-enabled, every routed request records a monotonic activity timestamp to Redis — fire-and-forget, never on the hot path; with --wake-enabled, a request routed to a paused fast sandbox is parked instead of answered 503: one ResumeSandbox per replica per sandbox under a checkpoint-fenced singleflight, a bounded parking lot, and stale-route replay restricted to idempotent methods with reproducible bodies; the wake-timeout answer carries the OSEP-defined marker (503 + Retry-After: 1 + OpenSandbox-Wake: expired), stripped from every upstream response so a sandbox cannot forge provenance; with both flags unset, behavior is byte-identical to before (#2156 #2180)
  • Stop rewriting unclean request paths: http.ServeMux answered paths with //, ., or .. segments with a 301 to the cleaned path — so requests never reached the sandbox and a followed POST became a GET; the gateway now dispatches status endpoints by exact escaped path and proxies everything else unchanged (#2403)

Fast Sandbox

  • Publish snapshots and template builds to OCI registries: each OverlayBD build can target an OCI registry (rootfs and snapfiles images, no name index — Sandboxes boot from status.manifestRef), Fastlets enable OCI by default with an explicit opt-out, OverlayBD templates require OCI storage, the controller consolidates snapshot artifact cleanup and refreshes Fastlet capacity after runtime deletion, and a bounded manifest cache survives Fastlet replacement; the SandboxSnapshot CRD gains oci:// manifest references and makes spec.templateName optional for OCI snapshots (#2338)
  • Place pool sandboxes by resources instead of fixed slots: spec.sandboxResources gains a mode (LegacySlot | ResourceAware) with a separate cpu/memory overcommitRatio (required in ResourceAware mode); cpu/memory limits become runtime limits/defaults that a ResourceAware pool may override per request, Sandbox status gains caller-owned immutable resources.limits (ResourceAware pools only), and pool status reports resource commitment plus fastlet/sandbox counters (#2442)
  • Make sandbox deletion reliable: deletion now sends a fenced Fastlet delete before any runtime-absence check, so a transport failure or empty inspect response is no longer treated as proof the sandbox is gone (which could orphan the microVM while the CR disappeared); the Fastlet records a per-identity tombstone so a delayed create cannot revive a deleted sandbox. Create placement also recovers faster: FastPath refreshes its candidate list within a bounded budget after proven rejections (#2214)
  • And the smaller items: read-only snapfiles are shared within each Fastlet (fsb-vm shares them by default), with recovery of shared snapfiles and orphan-cache cleanup (#2438); batch guest NAT updates into one iptables-restore --noflush so reapplying a guest address no longer accumulates rules (#2214); defer SET_BINDING while the network attachment has no IP, stop SandboxPool status-update loops, gate route removal on runtime deletion, and canonicalize Docker Hub credential hosts (#2338); and emit per-create phase timing from Fastlets, feeding the create-latency evidence pipeline (steady-state warm delivery measured at 16–17 ms past create) (#2327)

Misc

  • Console: land the developer console MVP (all five planned PRs) — a FastAPI BFF with httpOnly sessions proxying the lifecycle, snapshots, and diagnostics APIs, plus a React SPA covering tenant overview, sandbox list/create/detail with logs, snapshots and diagnostics pages, and admin/platform-ops views (/api/admin/sandboxes aggregation, pools and overview KPIs, env-gated K8s probes) with deployment docs and CI (#2129 #2130 #2131 #2132 #2133 #2271)
  • CLI: add osb terminal <sandbox-id> — a real PTY inside the sandbox over the execd PTY WebSocket, with raw mode, resize, replay on reconnect, and remote exit code propagation (#2313); plus smaller fixes: config values coerce to their declared types, output stays writable on non-UTF-8 streams, osb config set uses a real TOML parser preserving comments, installed skills land where each tool reads them, and out-of-range durations report as validation errors (#2169 #2187 #2194 #2208 #2272)
  • OSEPs: accept and document OSEP-0024 (proposal plus architecture page and user guide) (#2152 #2217); revise OSEP-0022 around the sealed-revision binding and the per-pool pinned CA (#2174 #2282); and withdraw the planned runtime driver ask (#2259)
  • Docs: unify component telemetry metrics into one reference at docs/guides/telemetry.md (#2205); redesign the docs homepage with an animated dark theme (#2345); highlight the project's AAIF (Agentic AI Foundation / Linux Foundation) affiliation and update the legal footer (#2395 #2343); update the gVisor notes for current upstream reality (#2431); and the usual upkeep — refreshed network architecture pages, markdown reflow across 148 files, CONTRIBUTING/test-path/PR-size-policy corrections, and AGENTS.md dedupe (#2210 #2285 #2175 #2339 #2412 #2211 #2225)
  • Security/dependency bumps: resolve 41 of 46 open Dependabot security alerts across Go, Python, and npm manifests (klauspost/compress, moby/sys/user, certifi, setuptools, undici, fast-uri, …); five egress h2 alerts remain open by design — three are not exploitable in this deployment, and the two real request-smuggling CVEs need a base-image/mitmproxy 12 migration tracked as follow-up (#2411)
  • Fast-sandbox integration environment: split the 2.4k-line entry script into entry + env + verify parts with sharpened policy-transfer coverage (#2319); drive the verify stages through the published osb CLI and add an opt-in IDLE_PAUSE=1 profile verifying the full auto-pause → wake loop (#2171 #2202); exercise the full MITM CA chain end to end — CA-baked template, pinned egress trust anchor, real credential injection through the mitm, and trust surviving pause/resume (#2393); run the SDK e2e suites against the overlaybd template only when OCI_STACK=1 (#2436); split the template verify matrix into per-stack steps (#2389); and let fast-sandbox upstream PRs trigger the OpenSandbox KVM integration suite against their head SHA pre-merge (#2335)
  • node-agent: fix syscall BPF verification failing when system header changes affect only DWARF checksums (#2325)
  • Tests/CI: cache Go modules for the Kubernetes nightly e2e jobs (#2306); close the credential-vault happy path in the fast-sandbox smoke (#2256); require the Fast Sandbox IP-rebind kernel regression in privileged CI (#2415); dump fastlet Pod container logs on pause/resume verify failure (#2290); align the node-agent log-collection e2e with execd's preserved trailing newline (#2164); and make the umbrella release workflow dispatch-only (#2223)
  • Examples: add an all-in-one Docker image for the DeerFlow example bundling the OpenSandbox server and the DeerFlow backend, for one-command workshop starts (#2262)
  • release: inject the server package version into the server image build so /version no longer reports a fallback dev version (#2356); bump platform version to 1.2.0 across charts and image references

Upgrade & Compatibility

  • Breaking (Go SDK): POST/PATCH requests are no longer retried by default — status-code retries for writes require explicit opt-in via RetryConfig.RetryableStatusCodesNonIdempotent, and ambiguous transport failures (read/write timeouts, unexpected EOF) are no longer retried for writes; callers that relied on write retries must handle the returned error or opt in (#2342)
  • Behavior change (Python and Go SDKs): the request that starts a stream (commands.run, codes.run, session/isolated runs, metrics watch) now retries the gateway's marked wake-timeout 503 within the wake budget instead of failing fast — established streams are never replayed; keep the old fail-fast with WakeRetryDisabled() (Go) or disabled wake retry (Python) (#2357 #2426)
  • Behavior change: SDK display helpers no longer insert extra newlines between already-terminated stdout events (Go Execution.Text(), Python code-interpreter Execution.text), and the Go helper treats a trailing CR as a terminator for legacy pre-1.2 bash-session events (#2381 #2408)
  • Behavior change: bash-session stdout event text now includes its original line endings, and blank lines appear as events — consumers must concatenate event text per run to recover exact output instead of treating each event as a newline-free line (#2318)
  • Behavior change: a BatchSandbox past its spec.expireTime now reports Terminated / SANDBOX_EXPIRED instead of Running, even while the pod is still Ready — callers that treat Running as alive should check expiry (#2238)
  • Behavior change: BatchSandbox pods now default to restartPolicy: Never — deployments that relied on the implicit Always and want restarts must set it explicitly in their batchsandbox_template_file (#2128)
  • Behavior change: deleted sandboxes stay queryable in Stopping until runtime cleanup completes, and only then return 404 — clients sharing a persistent directory should wait for the 404 before starting a replacement (#2154)
  • Behavior change: Docker-backed snapshots of gVisor containers without --overlay2=none are rejected with SNAPSHOT::UNSUPPORTED_RUNTIME (409) instead of publishing a silently incomplete image (#2241)
  • Behavior change: execd CPU/memory metrics report sandbox-scoped values when the sandbox cgroup has limits configured (previously host-wide); metric names and wire format are unchanged, but dashboards will shift (#2200)
  • Behavior change: on Docker, labels inherited from the image no longer appear as sandbox metadata, restored sandboxes no longer inherit the source's reserved labels, and image.uri reports the reference the sandbox was created with (#2363 #2269 #2353)
  • Behavior change: unknown NetworkPolicy/NetworkRule fields now return 422 and unrecognized create-time actions return 400 (previously silently ignored); trailing newlines in labels and resource names are rejected; contract-valid inputs are unchanged (#2383 #2385)
  • Behavior change: networkPolicy is rejected for Docker network modes none/pasta/slirp4netns — previously accepted silently for none (#2203)
  • Behavior change (Go SDK, direct mode): the tenant API key supplied through ConnectionConfig.Headers is no longer sent to execd/egress inside the sandbox (#2337)
  • Behavior change: template reads (GET /templates, the create gate) report the database projection with freshness bounded by watch delivery (normally milliseconds) — a create racing a just-finished build can see a brief retryable 404 (#2326)
  • Behavior change: isolated-session files-API paths outside the session's mounts are rejected with an error instead of silently resolving into the workspace (#2322)
  • Behavior change (experimental egress live mode only): nonempty native ignore_hosts/allow_hosts now reject startup — migrate static TLS bypass entries to OPENSANDBOX_EGRESS_MITMPROXY_PASSTHROUGH_HOSTS (#2229)
  • Behavior change: execd upper-directory quota admission fails closed when upper_max_bytes > 0 and a usage scan fails (previously the quota check was silently bypassed) (#2257)
  • Deprecation: the extensions["access.renew.extend.seconds"] create key is deprecated in favor of lifecycle.renewalPolicy.extendSeconds — supported throughout 1.x, removed in 2.0; setting both is accepted only when values match (#2296)
  • Fast Sandbox: OverlayBD templates now require OCI storage, and snapshot spec.templateName is optional with status.manifestRef admitting oci:// — update consumers that assumed the name index or a required templateName (#2338)
  • Images: opensandbox/<component>:release-1.2.0 (all three registries)
  • Packages: server / CLI / SDKs all at 1.2.0
  • Charts: render from this tag (helm template ./manifests/charts/opensandbox)
  • Kubernetes: supported range v1.21 – v1.34 (charts declare kubeVersion: ">=1.21.1-0")
  • Skew: server ↔ CLI/SDK same line supported; ±1 minor warns

👥 Contributors

Thanks to these contributors ❤️

Artifacts

  • BOM: docs/releases/1.2.0.yaml
  • Images: opensandbox/<component>:release-1.2.0 (Docker Hub / GHCR / ACR)
  • Packages: PyPI ×5, npm ×2, Maven Central ×5, NuGet ×2 — all at 1.2.0
  • Go module: github.com/alibaba/OpenSandbox/sdks/sandbox/go@v1.2.0

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.0
helm dependency build manifests/charts/opensandbox  # package file:// sub-charts (not committed)
# Before first install, configure the server API key and enable the ingress
# gateway (required on Kubernetes; the bare render disables it, and the server
# refuses to start with an empty api_key) — see the deployment guide:
# https://open-sandbox.ai/deployment/
helm template ./manifests/charts/opensandbox | kubectl apply -f -
# or point your GitOps platform (Argo / Flux) at the repo path + tag

# SDKs
pip install opensandbox==1.2.0            # Python
npm install @alibaba-group/opensandbox@1.2.0   # JavaScript
# Kotlin/JVM: implementation("com.alibaba.opensandbox:sandbox:1.2.0")
dotnet add package Alibaba.OpenSandbox --version 1.2.0
go get github.com/alibaba/OpenSandbox/sdks/sandbox/go@v1.2.0

# Run the server locally
uvx opensandbox-server==1.2.0

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

Don't miss a new OpenSandbox release

NewReleases is sending notifications on new releases.