* feat(sandbox): validate configuration before workload activation Closes #3145 Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(sandbox): bound startup failures and preserve activation history Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(sandbox): enforce deadlines on startup RPC attempts Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(sandbox): isolate provider auto-create policy fixture Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(sandbox): remove unnecessary fixture string delimiters Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(sandbox): capture startup logs before ephemeral cleanup Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(sandbox): preserve credential revocation after rebase Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(server): retain provider revision helper for endpoint reports Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(server): import provider object trait in production Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(supervisor): preserve admission across boundary extraction Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(sandbox): use existing boundary discovery imports Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(sandbox): distinguish workload and supervisor containers Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(server): reconcile admission schema with timestamp migration Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(server): reconcile admission with typed deletion schema Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(server): reconcile admission with mutation request IDs Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(supervisor): adapt local startup fixture to admission state Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(supervisor): deduplicate startup quarantine diagnostics Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(tui): show configuration blockers in sandbox notes Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * feat(sandbox): expire provisioning repair attempts after five minutes Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(supervisor): reconcile admission with provider readiness Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(tui): separate configuration summaries from full diagnostics Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(tui): shorten invalid configuration note Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(server): refresh schema inventory after rebase Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> --------- Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
openshell-driver-docker
Docker-backed compute driver for local and remote OpenShell gateways.
The driver uses bollard to manage sandbox resources through the configured
Docker API socket. When socket_path is unset, it selects the first standard
local socket that responds to an API ping. An explicitly selected Docker driver
falls back to /var/run/docker.sock when no candidate responds.
When the gateway configures [openshell.gateway.otlp], the in-process driver
exports spans to the same OTLP/gRPC collector as
openshell-driver-docker. The standalone driver accepts
OPENSHELL_OTLP_ENDPOINT, continues W3C trace context from gateway RPC
metadata, and flushes spans during graceful shutdown.
Runtime Model
The driver creates two containers for each sandbox:
openshell-sandboxis PID 1 in the workload container. It owns the workload process tree, seccomp notification broker, mandatory Landlock baseline, binary identity, exec/signal/wait/PTY operations, and loopback forwarding.openshell-supervisorruns in a separate companion container. It owns the gateway session, policy engine, credentials, interception CA, SSH relay, L7 inspection, DNS policy, and external upstream connections.
Both containers are non-root, request no capabilities, and set no-new-privileges. A shared named volume carries the authenticated Unix socket and sandbox bootstrap material. A second supervisor-only volume carries the supervisor JWT and gateway client credentials and is never mounted into the workload.
The workload uses network_mode=none. Its seccomp user-notification broker
mediates every supported TCP and DNS operation, attributes it to the calling
binary, and sends the request across the private channel. The supervisor
authorizes the request before it opens an upstream connection. Docker's absent
workload network is the mandatory outer fence if mediation fails or is
bypassed. Only the trusted supervisor companion joins the driver-owned bridge,
where it originates approved egress and can resolve other services on that
network.
The driver copies trusted runtime bytes from the configured supervisor image through the Docker archive API. No workload launch depends on a host bind mount or a tool supplied by the workload image, so the same path works with local, remote, and VM-backed Docker daemons.
Identity and Workspace
Before creating the workload, the driver pins the image ID and reads its
passwd/group databases through a stopped metadata container. It resolves the
admitted policy identity, or the image Config.User fallback, into one exact
non-root UID, primary GID, and supplementary-group set. Docker launches
openshell-sandbox with that identity, and the sandbox uses the same identity
for every canonical and exec process. UID or GID zero and unresolved symbolic
identities are rejected.
An absolute OCI working directory becomes the workspace. An empty, root (/),
or explicit /sandbox declaration uses /sandbox. Any other workdir must
already exist without symlink components. The resolved identity must be able to
traverse every parent and write and enter the workdir; OpenShell does not
change its ownership or mode.
Image VOLUME declarations and user mounts must not cover the workdir, one of
its parents, or the reserved /.openshell runtime/channel tree. OpenShell asks
the kernel to validate access under the final identity, so POSIX ACL and host
LSM decisions remain authoritative.
Container Contract
| Setting | Purpose |
|---|---|
Exact non-root user and group_add |
Gives sandbox and workload the same immutable UID/GID/group identity required for capability-free observation. |
cap_drop = ALL, no cap_add, no-new-privileges |
Prevents either container from acquiring Linux capabilities. |
| Docker default seccomp and AppArmor profiles | Retains runtime hardening; startup confirmation fails closed if nested seccomp notification is unavailable. |
network_mode = none on the workload |
Removes direct external routes. |
| Driver-owned bridge on the supervisor | Lets the trusted supervisor originate approved gateway and upstream connections and use Docker service discovery. |
restart_policy = no |
Keeps canonical main-process exit terminal. |
PidsLimit |
Applies the configured sandbox PID budget. Omit sandbox_pids_limit to use OpenShell's default. Explicit zero is invalid. |
| Private named volumes | One carries the authenticated sandbox/supervisor channel. The other is mounted only into the supervisor and contains its JWT and private gateway credentials. |
In-memory /run/openshell-supervisor-ca tmpfs |
Holds only the public supervisor CA certificate and trust bundle without making all of /run writable. |
| CDI GPU request | Assigns the exact validated CDI devices requested by driver config or count-based selection. |
Stop, Start, and Delete
Stop terminates the supervisor companion and stops the workload container without removing it. Docker retains the workload writable layer and attached volumes. Start stages a fresh sandbox bootstrap bundle, restarts that workload, and creates a new supervisor companion. A durably stopped sandbox stays stopped across gateway restarts.
Delete force-removes both containers, the driver-owned runtime volumes, and the host-private runtime descriptor. Missing or altered descriptor and channel resources fail closed; the driver does not run an older combined-supervisor layout.
Driver Config Mounts
The gateway forwards the docker block from --driver-config-json. Supported
mount types are:
bind: an absolute daemon-host path, allowed only when[openshell.drivers.docker].enable_bind_mounts = true.volume: an existing named volume. The driver never creates or removes a user-supplied volume. Bind-backed local volumes requireenable_bind_mounts = true.tmpfs: an in-memory filesystem with optional size and mode.
Host bind mounts are disabled by default because they expose daemon-host paths to sandbox requests. User bind and volume mounts are read-only by default. Targets must be absolute, normalized paths and cannot overlap the workspace root or OpenShell control paths.
Example:
docker volume create openshell-work
openshell sandbox create \
--driver-config-json '{"docker":{"mounts":[{"type":"volume","source":"openshell-work","target":"/sandbox/work"}]}}' \
-- claude
Runtime Image
sandbox_runtime_image contains the statically linked musl
/openshell-sandbox binary. The driver extracts that binary as bytes and
stages it into the stopped workload. supervisor_image contains the
dynamically linked glibc /openshell-supervisor binary that runs in the
host-networked supervisor container. Release and gateway image builds bake
matching image tags into the binary.
Callback and TLS
OPENSHELL_ENDPOINT and gateway authentication material are injected only into
the supervisor companion. The workload never receives the sandbox JWT, gateway
client TLS key, policy authority, or interception CA private key.
When no endpoint is configured, the driver derives
host.openshell.internal:<gateway-port>. Native Linux uses the managed bridge
gateway. Docker Desktop and compatible VM-backed daemons use Docker's
host-gateway route. A configured HTTPS server certificate must include the
endpoint host in its subject alternative names.
The driver pins a concrete managed-bridge address in the sandbox descriptor.
For Docker's special host-gateway route, it leaves the address unpinned so
the supervisor resolves the driver-injected alias in its own container. This
avoids treating the Docker VM's loopback as the desktop host.
The supervisor owns these security-critical variables:
OPENSHELL_ENDPOINTOPENSHELL_SANDBOX_IDOPENSHELL_SANDBOXOPENSHELL_SANDBOX_TOKEN_FILEOPENSHELL_SSH_SOCKET_PATHOPENSHELL_MAIN_PROCESS_SPEC- TLS path variables when HTTPS is enabled
Template and sandbox environment is encoded in the protected bootstrap and exposed only to workload children. Workload input cannot override security-critical supervisor variables.