release-1.2.0-rc.1

opensandbox-group/OpenSandboxrelease-1.2.0-rc.1Oct 15, 2026by github-actions[bot]

Upgrading?

Full Release Notes

First release candidate on the `1.2.0` line, carrying everything merged since `release-1.1.1` — the headline is OSEP-0024: auto-pause and wake-on-access for fast sandboxes, landing end to end. Images publish now; SDK/CLI packages ship at the `1.2.0` stable bump. Please test and report issues.

## Highlights

- Auto-pause and wake-on-access for fast sandboxes (OSEP-0024) lands end to end: ingress activity tracking and the wake path, the server-side idle sweeper, the opt-in `lifecycle.idlePolicy` create field, and the wake-retry contract in all five SDKs — off by default, additive only ([#2156](https://github.com/opensandbox-group/OpenSandbox/pull/2156) [#2180](https://github.com/opensandbox-group/OpenSandbox/pull/2180) [#2192](https://github.com/opensandbox-group/OpenSandbox/pull/2192) [#2198](https://github.com/opensandbox-group/OpenSandbox/pull/2198) [#2218](https://github.com/opensandbox-group/OpenSandbox/pull/2218))
- Egress sidecar (experimental, opt-in): live credential-bound Vault admission, HTTP/2 support with bounded drain, atomic policy-file persistence, and runtime fault containment ([#2176](https://github.com/opensandbox-group/OpenSandbox/pull/2176) [#2201](https://github.com/opensandbox-group/OpenSandbox/pull/2201) [#2182](https://github.com/opensandbox-group/OpenSandbox/pull/2182) [#2162](https://github.com/opensandbox-group/OpenSandbox/pull/2162) [#2172](https://github.com/opensandbox-group/OpenSandbox/pull/2172))
- SDK reliability: file uploads and downloads are no longer aborted at the 30-second request timeout in any of the five SDKs; execd now reports sandbox-scoped CPU/memory and OOM kills ([#2165](https://github.com/opensandbox-group/OpenSandbox/pull/2165) [#2186](https://github.com/opensandbox-group/OpenSandbox/pull/2186) [#2196](https://github.com/opensandbox-group/OpenSandbox/pull/2196) [#2206](https://github.com/opensandbox-group/OpenSandbox/pull/2206) [#2191](https://github.com/opensandbox-group/OpenSandbox/pull/2191) [#2200](https://github.com/opensandbox-group/OpenSandbox/pull/2200))

## Server

- Implement the server side of OSEP-0024 auto-pause for fast sandboxes: the lifecycle create field `lifecycle.idlePolicy` (`idleTimeoutSeconds` 30–86400, `onIdle: pause`, `wakeOnAccess`, optional `activeProbe`) validates with pydantic bounds and persists as the reserved `sandbox.opensandbox.io/idle-policy` Sandbox CR annotation (annotation-write failure stays fail-safe — the sandbox is created unpolicied); an idle sweeper enumerates opted-in fsb sandboxes from the informer cache, checks activity in Redis (monotonic-max writes, missing-key tombstones can only delay a pause), takes a per-sandbox pause lock with re-verify under lock, and issues an idempotent FastPath `PauseSandbox`; a declared `activeProbe` executes through the ingress gateway and a busy answer records ordinary activity; an `OpenSandbox-Skip-Activity: skip` sentinel suppresses the observation for one request and is stripped on both gateway and server-proxy hops ([#2192](https://github.com/opensandbox-group/OpenSandbox/pull/2192) [#2181](https://github.com/opensandbox-group/OpenSandbox/pull/2181))
- Configure idle-pause under the new lifecycle-automation umbrella: `[idle_pause]` becomes `[automation.idle_pause]` and the redundant `redis.enabled` gate is removed — `redis.dsn` is required (validated at load) when `enabled = true`, failing loudly instead of silently not starting the sweeper; `configuration.md` is rewritten for conciseness and now documents `ingress.secure_access` and the missing env-var overrides ([#2215](https://github.com/opensandbox-group/OpenSandbox/pull/2215))
- Default BatchSandbox pods to `restartPolicy: Never`: a kubelet in-place container restart recreated the main container from its image — wiping the writable layer and in-memory state — while the sandbox kept reporting `Succeed`; restarting is never a meaningful recovery for a sandbox, so the honest default is no restart, which also makes the controller's terminal-failure path reachable. An explicit `restartPolicy` in the template still wins, the Windows profile keeps forcing `Always` (multi-phase installation relies on it), and pool mode is unchanged ([#2128](https://github.com/opensandbox-group/OpenSandbox/pull/2128))
- Treat `pasta`/`slirp4netns`/`none` as container-runtime network modes and pass them straight through, making podman's kernel-level networking selectable — on Windows 11 + WSL2 where netavark breaks bridge mode, `pasta` was the only working network and previously failed create with a 400 asking for a user-defined Docker network; `networkPolicy` is now rejected for these modes since there is no bridge for the egress sidecar to attach to (previously silently accepted for `none`) ([#2203](https://github.com/opensandbox-group/OpenSandbox/pull/2203))
- Parse Docker timestamps whose fractional second is not 3 or 6 digits: Docker writes RFC3339Nano (trailing zeros dropped), Python 3.10's `fromisoformat` rejects 5-digit fractions, and the helper fell back to `datetime.now()` — so `createdAt` and `status.lastTransitionAt` silently reported the current time; the fraction is now zero-padded to 6 digits ([#2220](https://github.com/opensandbox-group/OpenSandbox/pull/2220))

## SDKs

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

- Add the OSEP-0024 SDK contract to all five languages: a typed `lifecycle.idlePolicy` create field (`IdlePolicy`/`ActivityProbe` models with client-side fail-fast validation mirroring the server: timeout 30–86400 s, `onIdle: "pause"` pairing, probe path/timeout bounds; wired through both standard and template create) and 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), exhaustion raises a typed wake-timeout exception carrying attempts and waited time, the server-side resume is never canceled, bare `503` handling is unchanged, and SSE/streaming transports bypass wake retry ([#2198](https://github.com/opensandbox-group/OpenSandbox/pull/2198) [#2218](https://github.com/opensandbox-group/OpenSandbox/pull/2218))
- Stop capping file uploads and downloads at the request timeout (30 s default) in all four remaining SDKs — the whole-call timeout (`http.Client.Timeout`, OkHttp `callTimeout`, `AbortController`, `HttpClient.Timeout`) covered sending the request 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](https://github.com/opensandbox-group/OpenSandbox/pull/2165) [#2186](https://github.com/opensandbox-group/OpenSandbox/pull/2186) [#2196](https://github.com/opensandbox-group/OpenSandbox/pull/2196) [#2206](https://github.com/opensandbox-group/OpenSandbox/pull/2206))
- Suppress `sandbox.create` client metrics for client-pool warmup creates in Go, Python, and JavaScript, aligning with Kotlin: warmup traffic (hardcoded `skipHealthCheck`, hundreds of ms) previously mixed into user-facing create-latency metrics and dragged p50/p99 down; direct-create from pool acquire and user-facing creates keep reporting ([#2199](https://github.com/opensandbox-group/OpenSandbox/pull/2199))

### Python

- Clean up cancelled async pool startup: cancelling `SandboxPoolAsync.start()` mid-await left the instance stuck in `STARTING`, where a subsequent `start()` silently returned and startup-owned resources skipped cleanup; cancellation now reuses the reconciliation cleanup path, moves to `STOPPED`, and re-raises so the caller can retry ([#2209](https://github.com/opensandbox-group/OpenSandbox/pull/2209))
- Stop reporting failed file replacements as successful: the ordinary filesystem adapters caught and discarded the generated client's `JSONDecodeError`, so an empty or non-JSON HTTP 400/500 from `replace_contents()` returned success and an agent could keep building on an unapplied edit; the catch is removed and the documented `SandboxException` failure contract is restored (isolated-session adapters keep their empty-body compatibility catch) ([#2222](https://github.com/opensandbox-group/OpenSandbox/pull/2222))

### Go

- Strip all `OPEN-SANDBOX-*`/`OPENSANDBOX-*` headers and every configured authentication header (including `X-EXECD-ACCESS-TOKEN`, `X-API-Key`, and custom `WithAuthHeader` names) on cross-origin redirects, comparing each destination against the initial request's scheme, host, and effective port so credentials cannot reappear on later foreign-origin hops; applied across lifecycle, execd, egress, file transfers, SSE, and creation metrics while preserving custom `CheckRedirect` policies ([#2178](https://github.com/opensandbox-group/OpenSandbox/pull/2178))

## 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 task-cleanup and pool-release finalizers, and TTL reconciliation stops once deletion is accepted — `DELETE` still returns 204 when accepted, queries report `Stopping` during managed cleanup, and 404 arrives only after the runtime is gone; all five SDKs' `kill` docs and logs describe the accepted-deletion semantics ([#2154](https://github.com/opensandbox-group/OpenSandbox/pull/2154))

## Execd

- Report OOM kills via cgroupfs polling: kernel-side evidence was out of reach from inside a sandbox (no `CAP_SYSLOG`, no `/dev/kmsg`), so OOM events were unobservable; execd now polls the sandbox cgroup every 5 s and diffs the kernel OOM kill counter (`memory.events` on v2, best-effort on v1) — the hierarchical counters cover nested cgroups like bwrap isolated sessions — exporting `execd.system.oom.kills` with a `local`/`descendant` level attribute plus structured warn logs with best-effort victim attribution; the watcher runs independently of OTLP export and needs no capabilities ([#2191](https://github.com/opensandbox-group/OpenSandbox/pull/2191))
- Report sandbox-scoped CPU/memory metrics: `GET /metrics` and the `execd.system.cpu.usage`/`execd.system.memory.usage_bytes` gauges read `/proc` through gopsutil, which inside a container describes the host — dashboards showed host-wide memory and CPU; when the sandbox cgroup carries configured limits, usage now comes from the sandbox's own cgroup (`cpu.stat`/`cpu.max`, `memory.current`/`memory.max`, v1 fallback, hybrid hosts prefer v1), with the previous host view kept for unlimitied guests such as fast-sandbox microVMs; the wire format is unchanged ([#2200](https://github.com/opensandbox-group/OpenSandbox/pull/2200))
- Percent-encode quotes and backslashes in `Content-Disposition`: a filename containing `"` or `\` produced a quoted-string no standard parser accepts (`mime.ParseMediaType` rejected it and clients got no filename at all); such names now route through the same `filename*=UTF-8''` encoding as non-ASCII names ([#2161](https://github.com/opensandbox-group/OpenSandbox/pull/2161))
- Parse the `Range` header before setting download headers: a malformed range answered 416 with the file's `Content-Disposition` and `Content-Length` still attached, truncating the JSON body or dropping it entirely — surfacing in the Python SDK as a connection error instead of a 416 `SandboxApiException`; the error response is now a clean JSON 416, and 200/206 responses are unchanged ([#2216](https://github.com/opensandbox-group/OpenSandbox/pull/2216))

## Networking

### egress

All items below concern the default-off OSEP-0023 experimental revision runtime; legacy and DNS-only deployments are unchanged.

- Add experimental live credential-bound Vault admission: Vault mutations flow through the revision transaction and install the private authenticated snapshot before publishing the public result; bound HTTPS connections are admitted and header credentials injected only after exact request matching with SNI/HTTP/1 authority agreement (ECH-extension ClientHellos stay opaque), with per-request credential rotation, fencing of removed bindings on existing connections, and transport retirement at the drain deadline; scope is exact-host HTTPS on 443 with HTTP/1.1 and inline header credentials ([#2176](https://github.com/opensandbox-group/OpenSandbox/pull/2176))
- Acknowledge live Policy revisions before publication: the Policy API and automatic `deny.always`/`allow.always` reloads now commit in order — optional file persistence, nft application, exact receiver acknowledgement or reconciled readback, then proxy publication and API success — with fail-closed recovery when the receiver fails after nft has taken effect, and 30-second drain deadlines on policy-only updates that never extend across later revisions ([#2182](https://github.com/opensandbox-group/OpenSandbox/pull/2182))
- Add experimental HTTP/2 admission and bounded drain: snapshots bind atomically to actual client stream IDs at requestheaders, post-fence requests get `REFUSED_STREAM` before reaching origin, GOAWAY waits for outstanding output obligations while expiry uses abnormal closure, and a once-only mitmdump exit fallback covers unverifiable cleanup ownership; the adapter is pinned to CPython 3.10/3.11 and rejects configured chained upstream proxies ([#2201](https://github.com/opensandbox-group/OpenSandbox/pull/2201))
- Contain runtime recovery faults in the sidecar network namespace: an unexpected MITM exit now triggers an independent, read-back-verified IPv4/IPv6 fence pinned to the original namespace for the process lifetime — blocking all input/output/forward traffic including existing connections and loopback — with `QuarantineConfirmed` reported only after real rule readback; normal successful mutations never introduce a fence ([#2172](https://github.com/opensandbox-group/OpenSandbox/pull/2172))
- Persist experimental revision policy files atomically: writes go through a same-directory temporary inode with fsync + rename + directory fsync, restores put back the exact previous bytes (or synchronized absence), and unsupported storage — file bind mounts, read-only or symlinked paths, hardlinks, unsafe ownership, unsupported SELinux/IMA metadata — is rejected before the original is touched; operators must provide an existing egress-owned, exclusively written directory mount ([#2162](https://github.com/opensandbox-group/OpenSandbox/pull/2162))
- Classify nft apply effects before policy recovery: local construction, pre-start cancellation, and start failures are Unchanged (state preserved, retry allowed after restore), successful execution is Committed, and every post-start or unclassified error is Unknown — closing writer admission and latching recovery before best-effort restoration; the missing-table fallback retains both errors and never downgrades an Unknown result ([#2167](https://github.com/opensandbox-group/OpenSandbox/pull/2167))

### 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 per-replica write coalescing, Lua monotonic-max updates so out-of-order writes never regress the newest observation, and pipeline batching; 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 budget (overflow/timeouts answer 503 + `Retry-After` while the restore continues, never canceled), and stale-route re-entry replayed on a fresh route only when safely replayable; with both flags unset, behavior is byte-identical to today ([#2156](https://github.com/opensandbox-group/OpenSandbox/pull/2156))
- Complete OSEP-0024 wake hardening: post-forward stale-route replay is restricted to idempotent methods with reproducible bodies (a forwarded `POST` is never silently re-executed); the pre-forward wake-timeout answer carries the OSEP-defined marker (`503` + `Retry-After: 1` + `OpenSandbox-Wake: expired` + a `sandbox_wake_timeout` body naming the sandbox), stripped from every upstream response so a sandbox cannot forge provenance; a separate restore permit (`--wake-restore-max`, held to flight terminal) bounds concurrent restores independently of the parking lot; and wake now requires the per-sandbox `lifecycle.idlePolicy` opt-in read strictly from the Sandbox CR annotation — missing or malformed means no policy and today's permanent 503, never an implicit opt-in; `gateway.wake.*` chart values wire the flags and add read-only RBAC only when enabled ([#2180](https://github.com/opensandbox-group/OpenSandbox/pull/2180))

## Fast Sandbox

- Make sandbox deletion reliable: deletion now sends a fenced Fastlet delete before any runtime-absence check, so a missing registry entry, 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, and the cleanup finalizer is persisted at CR creation ([#2214](https://github.com/opensandbox-group/OpenSandbox/pull/2214))
- Make create placement recover faster: after proven rejections FastPath refreshes its candidate list within a bounded budget (excluding Fastlets already tried) and waits up to 2 s for assignment status to catch up before switching, and a process-local create tracker stops a deleted or replaced Fastlet pod from blocking the new assignment's projection or racing pod-loss recovery — the first-create happy path is unchanged ([#2214](https://github.com/opensandbox-group/OpenSandbox/pull/2214))
- Batch guest NAT updates: new guest-VM slots prepare dedicated DNAT/SNAT chains and restore replaces only those chains in one `iptables-restore --noflush` batch, so reapplying the same guest address no longer accumulates rules, with a new `fast_sandbox_network_guest_apply_stage_latency_seconds` metric separating the apply stages ([#2214](https://github.com/opensandbox-group/OpenSandbox/pull/2214))

## Misc

- Console: land the first slice of a new optional web console — a FastAPI BFF with httpOnly sessions and tenant/admin login proxying the lifecycle `/sandboxes` API (with a `runtimeSummary` on list/get), plus a React SPA covering tenant overview, sandbox list/create/detail, renew/delete, and the endpoint; first of five planned console PRs, with the admin UI to follow ([#2129](https://github.com/opensandbox-group/OpenSandbox/pull/2129))
- CLI: coerce config-file values to their declared types so a numeric `api_key` no longer silently resolves to null (breaking every command with a `ValidationError`) and a numeric `defaults.timeout` no longer crashes `sandbox create` ([#2169](https://github.com/opensandbox-group/OpenSandbox/pull/2169)); keep output writable on non-UTF-8 streams — GBK/cp1252/C-locale users could not run any `osb` command because emoji rendering raised `UnicodeEncodeError`, often after the work was already done; streams now escape unencodable characters and YAML output is re-dumped escaped to stay valid ([#2187](https://github.com/opensandbox-group/OpenSandbox/pull/2187)); write installed skills where each tool actually reads them — the `claude` target now writes `.claude/skills/<name>/SKILL.md` (the previous flat file was silently never loaded), and global `cline`/`copilot` targets write `~/.cline/rules/opensandbox-<name>.md` and `~/.copilot/copilot-instructions.md`; previously installed inert files are not migrated ([#2194](https://github.com/opensandbox-group/OpenSandbox/pull/2194) [#2208](https://github.com/opensandbox-group/OpenSandbox/pull/2208))
- Fast-sandbox integration environment: drive the verify stages through the published `osb` CLI instead of raw `curl` (template build, sandbox create/kill, endpoint fetch, pause/resume, snapshot create/get/list/delete/restore, renew), with `server_api` kept only for calls the CLI does not expose (networkpolicy replace PUT, RFC 7396 merge-patch, the snapshot re-entry code fence, gateway probes) ([#2171](https://github.com/opensandbox-group/OpenSandbox/pull/2171)); add an opt-in `IDLE_PAUSE=1` profile that deploys ephemeral Redis, enables the `[automation.idle_pause]` sweeper and gateway activity/wake values, and verifies the full auto-pause → wake-on-access loop on `up`, plus an SDK e2e suite and a nightly KVM workflow job ([#2202](https://github.com/opensandbox-group/OpenSandbox/pull/2202))
- Docs: accept and document OSEP-0024 — the proposal itself ([#2152](https://github.com/opensandbox-group/OpenSandbox/pull/2152)) plus an architecture page and user guide covering enablement, the `idlePolicy` field reference, the activity-probe contract, SDK wake-retry behavior, and an interactive diagram ([#2217](https://github.com/opensandbox-group/OpenSandbox/pull/2217)); unify component telemetry metrics from seven scattered locations into one reference at `docs/guides/telemetry.md` ([#2205](https://github.com/opensandbox-group/OpenSandbox/pull/2205)); refresh the network architecture pages with restyled SVG diagrams, a condensed egress page, and the network-isolation guide relocation ([#2210](https://github.com/opensandbox-group/OpenSandbox/pull/2210)); align CONTRIBUTING with the code (Go 1.25, Gin — not Beego) ([#2175](https://github.com/opensandbox-group/OpenSandbox/pull/2175)), dedupe `AGENTS.md` rules and state the architecture-vs-guides placement rule once ([#2211](https://github.com/opensandbox-group/OpenSandbox/pull/2211)), and fix the "shared Fastlet egress profile" misnomer to "shared egress process per Fastlet pod" with a note that gRPC ends at FastPath ([#2225](https://github.com/opensandbox-group/OpenSandbox/pull/2225))
- Tests/CI: align the node-agent log-collection e2e assertion with execd's preserved trailing newline ([#2164](https://github.com/opensandbox-group/OpenSandbox/pull/2164)); make the umbrella release workflow dispatch-only by dropping the weekly scheduled dry-run ([#2223](https://github.com/opensandbox-group/OpenSandbox/pull/2223))
- release: bump platform version to `1.2.0-rc.1` across charts and image references

## Upgrade & Compatibility

- Behavior change: BatchSandbox pods now default to `restartPolicy: Never` (implicit `Always` → `Never`) — deployments that relied on the implicit default and want restarts must set `restartPolicy: Always` explicitly in their `batchsandbox_template_file` (a startup warning will note the trade-off) ([#2128](https://github.com/opensandbox-group/OpenSandbox/pull/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](https://github.com/opensandbox-group/OpenSandbox/pull/2154))
- Behavior change: execd CPU/memory metrics report sandbox-scoped values when the sandbox cgroup has limits configured (previously host-wide); `GET /metrics` field semantics and OTel metric names are unchanged, but dashboards will shift ([#2200](https://github.com/opensandbox-group/OpenSandbox/pull/2200))
- Behavior change: `networkPolicy` is rejected for Docker network modes `none`/`pasta`/`slirp4netns` — previously accepted silently for `none` ([#2203](https://github.com/opensandbox-group/OpenSandbox/pull/2203))
- Images: `opensandbox/<component>:release-1.2.0-rc.1` (all three registries)
- Packages: unchanged — SDK/CLI artifacts publish at the `1.2.0` stable release
- 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 ❤️

- @Dev-next-gen
- @GodBlf
- @JasonBuildAI
- @Jinzhengxu
- @Jun516
- @Pangjiping
- @acodercat
- @comqx
- @hpliStartAgain
- @kittimzhe
- @mengdehong
- @ranjianghong526-hash
- @ratishjain12
- @zw11591-sketch

## Artifacts

- Images: `opensandbox/<component>:release-1.2.0-rc.1` (Docker Hub / GHCR / ACR)

## Installation

```bash
# 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-rc.1
helm dependency build manifests/charts/opensandbox  # package file:// sub-charts (not committed)
# the default render needs required values (server env / api key) and the
# ingress gateway — see docs/deployment before applying
helm template ./manifests/charts/opensandbox | kubectl apply -f -
# or point your GitOps platform (Argo / Flux) at the repo path + tag
```

Verify what you installed against the BOM: see
[Release Verification](https://open-sandbox.ai/community/release-verification).

Related Repositories