What's New
⚠️ Breaking Changes
OPENSANDBOX_EGRESS_SANDBOX_IDis now server-injected and rejected in request env — the server unconditionally sets the egress sidecar's sandbox-id attribution variable in both Docker and Kubernetes runtimes, and removed it from the env allowlist. Requests that pass it inenvnow get a 400 instead of a user-spoofable value. #1381
✨ Features
-
Sandbox lifecycle hooks (
preStart/periodic), OSEP-0020 —CreateSandboxRequest.lifecycleis accepted, validated (reserved transports, duplicate periodic names, pool-allocation combinations), and transported to Kubernetes workloads via the reservedOPEN_SANDBOX_LIFECYCLEenv for execd to execute. Docker explicitly rejects lifecycle requests; PATCH and other hook types are out of scope. Ships with aligned Java/Kotlin, Python, and TypeScript SDK models. #1588 #1589 -
preStart.timeoutSecondslimit raised from 300s to 3 hours — server and OpenAPI limits now allow up to 10800s for pre-start hooks; the periodic limit stays at 300s. The execd-side product policy bound is removed, keeping policy changes server-only. #1637 -
QEMU VMState pause/resume for runc-based sandboxes — opt-in capture of guest memory/vCPU/device state via QMP migration; rootfs and compressed VMState are stored as separate OCI images keyed by immutable SHA-256 digests. Supports pooled BatchSandbox pause, detachment, and standalone restoration while preserving guest memory, writable disk, boot ID, and counter continuity. Rootfs-only snapshots remain backward compatible. #1584
-
Pool capacity exhaustion is now machine-identifiable — the controller publishes a
PoolAllocationPending/PoolCapacityExhaustedcondition; the server waits a bounded acquisition window (newkubernetes.pool_acquisition_timeout_seconds, default 30s) and then returns HTTP 429 withKUBERNETES::POOL_CAPACITY_EXHAUSTEDandRetry-After: 5instead of the generic 504 readiness timeout. Older controllers safely fall back to the existing behavior. #1581 -
Pool allocation summary in lifecycle responses —
getSandboxnow surfaces an optionalSandbox.allocationblock (mode: pool,poolRef,state: allocated) for confirmed Kubernetes pool allocations, emitted only for concrete non-wildcard pool references with matching allocation evidence. No pod/namespace/IP data is exposed. #1481 -
Generic HTTP request metrics via OTLP — a thin ASGI middleware exports
server.http.request.duration(ms) withhttp_method, matchedhttp_route(orunknownfor unrouted/auth-failed requests), andhttp_status_codeattributes, complementing the existing create-duration histogram. Raw paths, IDs, keys, and bodies are never attributes; no-op when[otel]is disabled. #1561 -
Stable diagnostics API with scopes — diagnostics now honor the public inline descriptor contract:
logs: container|allandevents: runtime|lifecycle|all, with 400DIAGNOSTICS_SCOPE_UNSUPPORTEDfor unknown scopes and explicit warnings for incomplete best-effort scopes. Deprecated no-scopeplain-text responses still work with aDeprecationheader. #1553 -
fast-sandbox fleets backend groundwork (OSEP-0007) — a FastPath v2 gRPC client wraps all FastPathService RPCs with typed error mapping (
FastPathNotFound→ 404, etc.), and a create/status mapping layer converts the public contract to fast-sandbox semantics, rejecting unsupported fields (volumes,platform,resourceRequests,credentialProxy,snapshotId, etc.) with clear errors. Not yet wired as an active runtime. #1558 #1559 -
Secure-access signing keys can come from a Kubernetes Secret — the server accepts
OPENSANDBOX_SECURE_ACCESS_KEYS/OPENSANDBOX_SECURE_ACCESS_ACTIVE_KEYenv overrides (mirroring the API-key override, gateway mode required), and the Helm chart addsserver.gateway.secureAccess.existingSecret(mutually exclusive with plaintextkeys, enforced at render time) so GitOps flows never commit key material. #1445 -
Docker runtime
sandbox_env/sandbox_binds— new[docker]fields apply fleet-wide environment variables and host bind mounts to every sandbox container (requestenvwins oversandbox_env; config binds precede request-derived binds), giving the Docker runtime the operator-side hook the K8s pod template has. #1303 -
Egress sidecar readiness timeout configurable — new
[egress] timeout_seconds(default 30s, non-positive values rejected) lets Docker deployments tune how long the server waits for the sidecar health endpoint. #1522 -
Tenant namespaces validated at startup (OSEP-0014) — the server fail-fast verifies every tenant namespace exists and is accessible before serving traffic, aggregating all missing/inaccessible namespaces into a single fatal error instead of failing only at request time. OSEP-0004/0005/0014 marked implemented. #1389
🔒 Security
- CVE-remediating dependency bumps —
google.golang.org/grpc→ v1.82.1 across kubernetes/ingress/execd/egress/internal (xDS RBAC and HTTP/2 fixes),postcss→ 8.5.11 (arbitrary file read via sourceMappingURL). #1385
🐛 Bug Fixes
-
Docker endpoint lookups ~108x faster — container lookup now goes through the deterministic
sandbox-<id>name withopensandbox.io/idlabel validation, falling back to the label scan only when needed. Local benchmark: 6.07s → 56ms for execd+egress endpoint resolution. #1400 -
Informer cache expires when the watch stalls —
has_syncednow means "cache is currently live", not "an initial list finished once"; a stalled or half-open watch (e.g. after a control-plane upgrade) degrades to direct API reads instead of serving frozen status forever, which previously caused every create to 504 withPOD_READY_TIMEOUT. Bound derived from existing knobs (resync_period + watch_timeout); no new config. #1534 -
Proxy connection leak on aborted responses — when a downstream client disconnected before response-body streaming started, the backend httpx response was never released, gradually exhausting the shared pool until
httpcore.PoolTimeout. The proxy now owns the backend response for the full ASGI lifecycle, shieldsaclose()from cancellation, and releases on setup failure. Stress test: 1000/1000 disconnects cleaned up. #1488 -
Unique, stable proxy operation IDs — the 20 proxied HTTP operations previously emitted 4 process-dependent duplicate operation IDs (16 OpenAPI warnings); each method now gets its own deterministic ID with identical runtime dispatch, auth, and 405 behavior. Generated clients should be regenerated. #1634
-
PVC auto-create no longer crashes with a malformed 500 — the PVC owner-reference patch went through an unsupported
_content_typepath that raisedApiTypeError(leaving PVCs without owner refs) andSandboxErrorCodes.INTERNAL_ERRORwas undefined, masking the real error. Both fixed: patch uses the supported strategic-mergecall_apipath; error code defined. #1538 #1447 -
Template container
securityContextnow reaches pods — the deep-merge previously replaced the template's containers wholesale, silently dropping container-level securityContext. Template keys now supplement (never override) runtime values, matching the existing volumeMounts philosophy. #1564 -
Root-relative backend redirects stay under the proxy route —
Location: /loginfrom a sandbox backend is rebased to/v1/sandboxes/{id}/proxy/{port}/login; absolute, network-path, and query-only references are left untouched. #1516 -
Server-proxy endpoints drop the ingress routing header —
OpenSandbox-Ingress-To(case-insensitive) is removed fromuse_server_proxy=trueresponses while secure-access and other endpoint headers are preserved. #1503 -
No duplicate
Date/Serverheaders on proxied responses — the proxy preserves an originDatewhile suppressing Uvicorn's default (and the backendServerheader), emitting exactly oneDate. #1471 -
Invalid-resume error no longer leaks controller internals — the 409 message for a Running (internally Succeed) sandbox now says "state Running" instead of "phase Succeed"; HTTP status and error code unchanged. #1586
-
Non-finite CPU limits rejected —
nan, infinite, and overflowed values are rejected before Docker NanoCpus conversion instead of being silently accepted. #1568 -
Mixed read-only PVC subPath mounts allowed — the PVC source is set read-only only when every mount of the claim is read-only; the service-level rejection that blocked legitimate mixed-policy PVC creation is removed. #1440
-
Agent-sandbox server-proxy endpoints resolve — internal endpoint resolution now delegates to the workload provider and reads agent-sandbox pod addresses from
status.podIPsinstead of the BatchSandbox-only annotation, and brackets IPv6 literals before appending the port. #1424 -
Version reporting fixed everywhere —
/openapi.json, installed package metadata, and the chartappVersionall reported stale0.1.0; the server now reads its version from package metadata at runtime, builds injectSETUPTOOLS_SCM_PRETEND_VERSION, and the release script bumpsChart.yamlappVersion. #1439
📦 Misc
-
execd v1.0.22 ships OSEP-0018 init-mode hardening — execd as sandbox init (PID 1) with pre-exec hardening floor (env strip, bounding set, no_new_privs, identity drop, ambient caps, seccomp) and Kubernetes hardening e2e + PTY/bwrap coverage. #1474 #1555
-
Isolated-session fail-closed workload lifecycle gate — the server distributes a native Bubblewrap lifecycle gate to
/opt/opensandboxfor both runtimes; workloads stay blocked until execd marks them ready. First phase only — capability admission and session networking remain disabled. #1388 -
Egress transparent MITM can intercept extra TCP ports — experimental
OPENSANDBOX_EGRESS_MITMPROXY_EXTRA_PORTSappends ports to the always-on 80/443 with fail-closed validation (multiport cap 15); credential binding still applies to canonical ports only. #1379 -
Auto-bumps —
egressv1.1.5 → v1.1.7 (#1391 #1498 #1600);execdv1.0.22 (#1501).
👥 Contributors
Thanks to these contributors ❤️
- @aGallea
- @cwj2001
- @Federaffo
- @fengcone
- @gegemeimingzi
- @GodBlf
- @Gujiassh
- @hittyt
- @hpliStartAgain
- @jianpingpei
- @Joey777210
- @luffy0x
- @mameikagou
- @mpu
- @Pangjiping
- @ruirui6946
- @tea-artist
- @y4ney
- PyPI: opensandbox-server==0.2.3
- Docker Hub: opensandbox/server:v0.2.3
- Aliyun Registry: sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/server:v0.2.3
- GHCR: ghcr.io/opensandbox-group/server:v0.2.3