First release candidate on the 1.2.1 patch line, cut from release-1.2.0 (2026-10-27). Headline: OSEP-0025 Credential Vault Exchange — an exchange credential source with per-request header resolution through a pluggable resolver, wired end to end across egress, server, all five SDKs, fast-sandbox e2e, and the Credential Vault guide. Also notable: execd atomic uploads, digest validation for rootfs snapshots, Kubernetes create-timeout startup diagnostics, and informer-cache scoping for large clusters. Baseline for every section is release-1.2.0. rc releases publish images but hold packages — SDK/CLI artifacts ship at the 1.2.1 stable bump.
Highlights
- Credential Vault Exchange (OSEP-0025): per-request dynamic header resolution, fail-closed and never persisted, across server and all five SDKs (#2457 #2535 #2534 #2560)
- Data-safety: execd uploads replace atomically so a failed write keeps the old file (#2433); the controller refuses rootfs snapshots whose commit result lacks digests (#2482)
- Operability: create timeouts report the pod startup failure reason (#2434); controller and fsb controller gain
--namespacescache scoping (#2571 #2581)
Server
- Sandbox list pushes metadata equality filters into the Kubernetes label selector, so large namespaces no longer materialize every workload before filtering (#2559)
- Template OCI hardening: publish repository syntax is validated at create, and sandbox creation requires a digest-pinned publish reference — a succeeded template can no longer resolve to a mutable or unrelated manifest (#2491)
- Kubernetes create timeouts append the pod startup failure reason (unschedulable,
ImagePullBackOff, warning events) before rollback — image pull and scheduling failures are diagnosable through the API again (#2434) - Docker: a concurrent container removal no longer aborts the startup expiration restore, and remaining Docker errors fail startup instead of silently dropping timers (#2533); duplicate, root, and server-mount-clashing destinations are rejected with 400 before provisioning (#2421 #2505)
- An explicit POSIX root in
storage.allowed_host_pathsnow covers its descendants; the descendant check produced//and never matched (#2474) - Proxy: a GET that declares a body is forwarded with it, so Elasticsearch-style requests stop failing with 500 (#2518); pool exhaustion returns
503 PROXY_POOL_EXHAUSTEDinstead of a 180-second wait ending in 500 (#2519) - Smaller fixes:
metadata=env=filters on the empty value instead of matching everything (#2461); gateway, bucket-name, and env-name validators reject trailing newlines (#2499)
SDKs
Exchange credential vault payloads (exchange source + typeless binding auth) are accepted across all five SDKs, aligned with the server and egress wire contract (#2534 #2560); see the Kotlin/C# ABI note in Upgrade & Compatibility. Detailed download methods expose response metadata (status, Content-Range, isPartial) in all five languages; existing signatures unchanged, custom filesystem implementations must add them (#1597)
Python
- SSE command streams get a read timeout:
commands.run()raisesSandboxTimeoutExceptionaftermax(request_timeout, 30s)of silence instead of blocking forever on a stalled execd (#2527) - Async pool graceful shutdown drains pending kills instead of cancelling them and leaving untracked sandboxes until TTL (#2507)
- Download streams release the HTTP response when dropped, closed early, or abandoned mid-iteration — no more connection-pool exhaustion (#2487 #2531)
- Transport errors during stream requests and body reads raise SDK exceptions instead of leaking raw
httpxerrors (#2515 #2525) - Smaller fixes: endpoint cache invalidation scoped to the inflight key (#2409);
NetworkPolicyAdapterpropagates redirect options and event hooks (#2553); the MCP server reports SDK errors instead of a generic message (#2524)
JavaScript
- Isolated-session requests throw
SandboxApiExceptionwith status/request id/error code instead of a flatError(#2466) - An infinite or negative redirect deadline is treated as none —
requestTimeoutSeconds: Infinityno longer fails every authenticated request (#2488) - Closing a download stream inside its own loop ends iteration cleanly instead of throwing a
TypeError(#2514)
Kotlin
- Sub-millisecond command timeouts round up to 1 ms; 500 µs previously serialized as
0, which execd treats as unbounded (#2537)
C#
Covered by the cross-SDK exchange models (#2560).
Go
- Retry/timeout hardening:
Retry-Aftersaturation and the 60 s cap now apply on the generic retry path (#2452 #2508); backoff saturates instead of wrapping negative (#2509);ReadyTimeoutis enforced under longer caller deadlines (#2528); interpreter readiness probes are deadline-bounded and require a confirmed zero exit (#2504 #2480); telemetry POSTs get their own five-second bound (#2540) - Endpoint cache: in-flight fetches fenced across invalidation (#2503); lifecycle mutations invalidate cached endpoints (#2564); the last waiter leaving cancels the shared fetch (#2579); config/endpoint headers merge case-insensitively (#2529)
- Pool: idle FIFO survives remove/reinsert (#2453); shutdown waiters honor caller cancellation (#2542);
Destroybounds state-store draining (#2580) Execution.Text()concatenates code fragments without separators, so"Loading"+"...done\n"no longer gains a newline; command formatting unchanged (#2447)- Smaller fixes: SSE parser accepts CR-delimited events (#2439); multipart uploads keep their generated boundary (#2468); a flaky diagnostic assertion checks
net.Error.Timeout()(#2479)
Controller
- Rootfs snapshot commits whose result lacks digests now fail (
CommitResultUnavailable/CommitResultIncomplete) and reportPauseFailedwith pods kept — previously pause reported success, deleted the pods, and every later resume failed permanently (#2482) --namespacesscopes the informer cache, fixing startup OOM on large shared clusters; empty default keeps full-cluster semantics; chart valuecontroller.namespaces(#2571)
Execd
- Uploads are atomic: staged temp file + rename, preserving mode/owner and following symlinks — a failed upload (ENOSPC, I/O error, kill) no longer destroys the replaced file (#2433)
- The Jupyter token is discovered from the code-interpreter image's
jpserver-*.jsonwhenJUPYTER_TOKENis unset; explicit config wins (#2448) - The Jupyter startup wait is restored to ~5 minutes — a backoff
Capinteraction had aborted after ~11 s against cold images (#2486) EXECD_SSE_LEGACY_COMPAT(default off) omitsexit_codefrom SSE events for pre-1.0.18 SDKs whose kotlinx runtime crashes on the unknown key (#2472)filename*is encoded with RFC 8187 attr-chars, so names with=,:,'parse in strict clients (#2516)
Networking
egress
- Credential Vault Exchange (OSEP-0025):
exchangesource (UDS/HTTPS resolver endpoint), typeless binding auth, marker-only snapshot bindings, aPOST /credential-vault/_resolveendpoint, and addon integration injecting resolved headers with fail-closed 407/stream-kill — resolved material is never persisted, cached, or read back; inline-only vaults unchanged (#2457 #2495 #2501 #2500 #2535 #2538); sidecar profile integrated end to end with fast-sandbox e2e (#2549 #2547 #2561); guide chapter added (#2563) - Resolver caller auth is deferred (D5):
https://resolver endpoints must be egress-only reachable (UDS permissions or network-scoped nft rules) as the current trust boundary (#2549) - OSEP-0023 groundwork continues [EXPERIMENTAL]: Vault v2 safe reader (#2441), strict legacy management with owner fencing (#2460), strict-mode Vault v2 writer with server caller (#2493) — defaults remain v1; strict mode requires the paired fast-sandbox producer
- A replayed
data-plane-readyhook is idempotent (200) instead of a spurious 409 (#2510)
ingress
- Startup validation:
--wake-park-budgetmust be below--wake-max-flight-lifetime; the misconfiguration previously answered parked requests with 400 or empty 200 instead of 503 +Retry-After(#2511) - A queued wake request is bounded by one park budget instead of nearly two (#2513)
- Smaller fixes: the final status is recorded after a 1xx response, so
103 Early Hintsno longer masquerades as the request status (#2484); legacy host-route ports are validated (sb-70000no longer 502s,sb-08080no longer reaches port 8080) (#2578); a test-only data race fixed (#2512)
Fast Sandbox
- Checkpoint chains stay bounded: pause/restore checkpoint layer chains are squashed through overlaybd-merge during commit, so repeated cycles no longer grow the restore chain without limit (#2546 fast-sandbox#158)
- Node-local VM discovery: the firecracker runtime writes a write-once
fast-sandbox.jsonrecord per VM — full sandbox UID, namespace, name, placement — next to the process log, so node-local agents discover local VMs by directory scan instead of watching Sandbox CRs cluster-wide; publication never blocks provisioning and teardown removes the record (#2546 fast-sandbox#154) - Memory: opt-in guest memory reclamation with pressure-based admission, and free-page reporting enabled by default in built templates (#2546 fast-sandbox#114)
- Fastlet hardening: the strict managed Actions v2 transport (producer half of this release's egress strict-management receiver), owner-retirement race fixes, terminal-fence preservation across absent inventory, and parallelized admission probes (#2546 fast-sandbox#152)
- fsb controller:
--namespacesscopes the informer cache (refuses to start when the set misses the runtime-environment namespace) and--kube-client-qps/--kube-client-bursttune the shared API-server client rate limiter — opt-in chart valuescontroller.namespaces/controller.kubeClientQPS/controller.kubeClientBurst, defaults render no new flags (#2581 fast-sandbox#161) - Init topology:
runtime.execd_run_as_initnow bakesEXECD_INIT=1into fsb golden-image template envs — previously ignored on fsb, so execd never took the OSEP-0018 init role; the injected value wins over a client-provided key (#2494); the integration environment pins the full in-guest topology and the whole e2e surface defaults it on (#2497) - Integration environment: overlaybd template builds probe the ublk add/del path up front and self-heal wedged kernel devices instead of failing ~16 minutes into a doomed build, and teardown releases orphan devices (#2454); smoke assertions surface real nft errors instead of reading them as missing rules (#2543); a full-chain exchange-resolver verify stage (server relay → egress
_resolve→ injected headers observed by the guest) rides verify-native/verify-overlaybd (#2561)
Misc
- CLI: text is escaped instead of parsed as Rich markup — paths like
/tmp/notes[draft].txtprint correctly and no longer fail successful commands (#2520); broken config files report cleanly instead of crashing every command (#2521);config setstores string-key values verbatim (007123stays007123) (#2522); the PTY session is deleted when the WebSocket never opens (#2523) - node-agent: a log stream whose directory never appeared is forgotten after the retention window instead of persisting forever (#2471)
- Docs: image-signing claims aligned with actual workflow coverage (#2467); AAIF charter and governance consolidation — code contributions now require DCO sign-offs (#2490); S3 template examples set
format: native(#2506); OSEP-0025 status and contract pins (#2539) - Housekeeping: dead code removed across modules (#2496); execd CI mounts tracefs for the eBPF smoke (#2473)
Upgrade & Compatibility
- Kotlin/C# SDKs are source-compatible but not ABI-compatible with 1.2.0 — recompile; no wire changes (#2560)
- Python: silent SSE streams now raise
SandboxTimeoutExceptionaftermax(request_timeout, 30s)(#2527); download-body errors raise SDK exceptions instead of rawhttpxerrors (#2525) - Go:
Execution.Text()no longer inserts separators between code fragments (#2447); nonempty positionalExecution{...}literals no longer compile — use keyed literals - Execd atomic uploads: writable file in non-writable directory is rejected, device/FIFO destinations refused, xattrs/ACLs/setgid bits not carried, bind-mounted destinations fail (
EBUSY), a SIGKILL mid-upload can leave a.osb-upload-*staging file, peak disk ~3× file size (#2433) - Server startup: a Docker error during expiration restore (other than concurrent removal) now prevents startup — run under a restarting supervisor (#2533)
- Ingress:
--wake-park-budgetbelow--wake-max-flight-lifetimeis now enforced at startup; defaults unaffected (#2511) - Template list:
metadata=env=now filters on the empty value (#2461) - OSEP-0025: exchange is additive and opt-in, but resolver caller auth is deferred —
https://resolver endpoints must be egress-only reachable (#2549 #2563) - Fast Sandbox: free-page reporting on by default; per-VM
fast-sandbox.jsonrecord;controller.namespacesmust cover pools/sandboxes and the runtime-environment namespace or the controller refuses to start (#2546 #2581) - Images:
opensandbox/<component>:release-1.2.1-rc.1(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 ❤️
- @BootstrapperSBL
- @Dev-next-gen
- @Galaxy-0
- @Jackmeson1
- @JasonBuildAI
- @Laihu08
- @Pangjiping
- @Yzzz88
- @acodercat
- @delete-cloud
- @hittyt
- @hpliStartAgain
- @jwx0925
- @kittimzhe
- @mayuriphad
- @mengdehong
- @ryrenz
- @sangkyoonnam
- @xiong-wei1
- @yangjj-iso
Artifacts
- Images:
opensandbox/<component>:release-1.2.1-rc.1(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.1
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.