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.idlePolicycreate 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.idlePolicycreate field (idleTimeoutSeconds30–86400,onIdle: pause,wakeOnAccess, optionalactiveProbe) 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 FastPathPauseSandbox; a declaredactiveProberuns through the ingress gateway, and anOpenSandbox-Skip-Activitysentinel suppresses one observation and is stripped on both gateway and proxy hops. Config moves under[automation.idle_pause], andredis.dsnis 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-vaultpushes 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 withexpectedRevisionas the cross-replica backstop, non-fsb backends proxy the sidecar's/credential-vaultsurface, and the fsbcredentialProxycreate-time rejection is lifted (#2263) - Bake the pinned MITM CA into server-created SandboxTemplates (OSEP-0022): new
[kubernetes] template_mitm_ca_secretconfig (default off) emitsspec.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 opaqueextensions["access.renew.extend.seconds"]key to a structuredrenewalPolicy.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/contextreturns 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,%2Fbecame a separator, and%2E%2Esegments 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 commitunder gVisor's default--overlay2=root:selfcan reportREADYwhile 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_EXCEEDEDfrom 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_EXPIREDinstead ofRunningpastspec.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);createdAtstays 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 adocker rmi -f'd image no longer 500sGET /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 torestartPolicy: Neverso a kubelet restart can no longer wipe the writable layer while reportingSucceed(#2128); podman'spasta/slirp4netns/nonenetwork modes pass through (withnetworkPolicyrejected, 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 andsecure_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.idlePolicycreate field with client-side fail-fast validation, plus the wake-retry contract — on a503carrying the gateway-ownedOpenSandbox-Wake: expiredmarker (its presence proves the request was parked and never forwarded, making any-method retry safe), the SDK resends afterRetry-Afterunder a bounded budget (default 5 attempts / 60 s, per-client knobs) and exhaustion raises a typed wake-timeout exception; bare503handling 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):
contextis 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.renewalPolicyto all five SDKs (OSEP-0009): a typed create field with client-side fail-fast validation (extendSeconds300–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,targetnow 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_codein Go and Kotlin, preferring it over the previousevalue/inferred-0 fallbacks while staying compatible with older servers (#2330); honor an explicitly suppliedOPEN-SANDBOX-API-KEYheader over the configured key regardless of header-name casing in JavaScript, C#, and Kotlin (#2348 #2372 #2375); suppresssandbox.createmetrics 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
/coderequests no longer carry the tenantOPEN-SANDBOX-API-KEYto execd, an explicitly configured connection key keeps precedence over the configuredapiKeywithout 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, andcodes.runagainst 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
/v1indomain/OPEN_SANDBOX_DOMAINproduced//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
InvalidArgumentExceptionwith no status (#2402); raise the shared structured error from Code Interpretercodes.runfailures instead of a hand-built exception that lostretry_after(#2364); keep code-interpreter output fragments together inExecution.textinstead 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); forwardcredential_proxythrough 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 inSTARTING(#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/Bufferview 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
LinkageErrorfrom a serializer/runtime mismatch — previously anAbstractMethodErrorescaped 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-Afterbudget 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 passWakeRetryDisabled()(#2426) - Keep line terminators in
Execution.Text(): execd already includes\n/\rin 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.Headerswas 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
Stoppingduring 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
Succeededwhen the shim was killed mid-write (#2270)
Execd
- Report sandbox-scoped CPU/memory metrics and OOM kills:
GET /metricsread the host's/procinside 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 exportexecd.system.oom.killswith structured warn logs — no capabilities required (#2200 #2191) - Serve the session-private
/tmpthrough the files API: the strict isolation profile's/tmpwas an anonymous tmpfs with no host-side path, so files-API paths under/tmpfailed and callers had to shuttle artifacts through base64 shell commands;/tmpis 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-TOKENon a native WebSocket handshake, so terminal connections needed a BFF relay; the PTY upgrade now accepts the existing execd token via theopensandbox-ptysubprotocol'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_secondsexpiry 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_sessionstdout events dropped\n/\rbytes, 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 -cargument, and Linux caps one exec argument at 128 KiB — background code above that failed with onlyArgument 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 forshare_net=falsesessions instead of an opaque EOF (#2333) - And the smaller fixes: report
exit_codeexplicitly on command completion and errors (#2303 #2316); send run errors after the stream started as framed SSEerrorevents 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 theWSCloseTakenOver(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); servebindsmount 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 toTERM=xterm-256colorwhen unset (#2313); percent-encode quotes and backslashes inContent-Dispositionfilenames (#2161); and parse theRangeheader 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 withexpectedRevisionand anexpectedIncarnationprecondition 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_EXPORTcopies the vault file into the snapshot bundle strictly before runtime teardown and seals the subject (all vault ops answer 409 while paused; enforcement continues), andVAULT_IMPORTis the only authorized incarnation transfer — a genuine export failure fails the pause checkpoint so teardown never runs without a bundle, and avaultTransfercapability 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/mitmCaDownsurfaced 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_BINDINGcan 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_IMPORTsnapshot 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) makestargetoptional — port-only rules, or ports combined with an IP/CIDR; domain+portsis rejected since domain rules evaluate at the DNS proxy layer; an operator-shippedreachable.ports.capwhitelist is enforced as an always-deny ahead of every user rule so evendefaultAction: allowcannot open a non-whitelisted port, and the fast-sandbox route rejectsportswith 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 aprobesubcommand; 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: oneResumeSandboxper 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.ServeMuxanswered 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 gainsoci://manifest references and makesspec.templateNameoptional for OCI snapshots (#2338) - Place pool sandboxes by resources instead of fixed slots:
spec.sandboxResourcesgains amode(LegacySlot|ResourceAware) with a separate cpu/memoryovercommitRatio(required in ResourceAware mode);cpu/memorylimits become runtime limits/defaults that a ResourceAware pool may override per request, Sandbox status gains caller-owned immutableresources.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 --noflushso reapplying a guest address no longer accumulates rules (#2214); deferSET_BINDINGwhile 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/sandboxesaggregation, 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 setuses 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, andAGENTS.mddedupe (#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
h2alerts 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
osbCLI and add an opt-inIDLE_PAUSE=1profile 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 whenOCI_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
/versionno longer reports a fallback dev version (#2356); bump platform version to1.2.0across 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 withWakeRetryDisabled()(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-interpreterExecution.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.expireTimenow reportsTerminated/SANDBOX_EXPIREDinstead ofRunning, even while the pod is still Ready — callers that treatRunningas alive should check expiry (#2238) - Behavior change: BatchSandbox pods now default to
restartPolicy: Never— deployments that relied on the implicitAlwaysand want restarts must set it explicitly in theirbatchsandbox_template_file(#2128) - Behavior change: deleted sandboxes stay queryable in
Stoppinguntil 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=noneare rejected withSNAPSHOT::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.urireports the reference the sandbox was created with (#2363 #2269 #2353) - Behavior change: unknown
NetworkPolicy/NetworkRulefields 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:
networkPolicyis rejected for Docker network modesnone/pasta/slirp4netns— previously accepted silently fornone(#2203) - Behavior change (Go SDK, direct mode): the tenant API key supplied through
ConnectionConfig.Headersis 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_hostsnow reject startup — migrate static TLS bypass entries toOPENSANDBOX_EGRESS_MITMPROXY_PASSTHROUGH_HOSTS(#2229) - Behavior change: execd upper-directory quota admission fails closed when
upper_max_bytes > 0and 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 oflifecycle.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.templateNameis optional withstatus.manifestRefadmittingoci://— update consumers that assumed the name index or a requiredtemplateName(#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 ❤️
- @0ranan
- @Aling33
- @BootstrapperSBL
- @Claire-ovo
- @Dev-next-gen
- @EtiennePerot
- @GodBlf
- @Jackmeson1
- @JasonBuildAI
- @Jinzhengxu
- @Jun516
- @KennyMcSimpson
- @MaxFreedomPollard
- @Pangjiping
- @acodercat
- @alex-feel
- @comqx
- @hardness1020
- @hittyt
- @hpliStartAgain
- @jiangjiang248
- @jianpingpei
- @jwx0925
- @kittimzhe
- @markankamaransheogarat-sys
- @mengdehong
- @ninan-nn
- @pingkui
- @ranjianghong526-hash
- @ratishjain12
- @xxwtiancai
- @yangjj-iso
- @zw11591-sketch
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.0Verify what you installed against the BOM: see Release Verification.