* feat(cli): add --env flag to sandbox create and exec
Add `--env KEY=VALUE` (repeatable) to `openshell sandbox create` and
`openshell sandbox exec`, wiring user-specified environment variables
into the existing `SandboxSpec.environment` and
`ExecSandboxRequest.environment` protobuf fields which were previously
only accessible via the SDK/gRPC API.
Fix a bug where user-specified environment variables set via
`SandboxSpec.environment` were silently dropped inside sandbox SSH
sessions. The SSH handler's `apply_child_env()` calls `env_clear()` to
strip supervisor-internal secrets before spawning user shells, but this
also wiped container-level env vars including user-specified ones. This
was introduced in 5fd4885a (2026-02-26, "feat(sandbox): VS Code
Remote-SSH support with platform detection fix") which added
`env_clear()` to fix a VS Code platform detection timeout caused by
leaked supervisor env vars.
The fix introduces an `OPENSHELL_USER_ENVIRONMENT` sidecar env var:
compute drivers (Docker, Podman, Kubernetes, VM) serialize the
user-specified environment as JSON into this variable at container
creation time, the sandbox supervisor deserializes it at startup, and
the SSH handler re-injects the values into child processes after
`env_clear()`. User-specified vars are applied at lowest priority so
proxy, TLS, and provider credentials still take precedence.
Signed-off-by: Russell Bryant <russell.bryant@gmail.com>
* fix(sandbox): address PR review feedback for env var injection
- VM driver: restructure build_guest_environment to apply user env
before required driver vars, preventing user-specified values from
overriding OPENSHELL_ENDPOINT, PATH, and other critical vars.
Matches the pattern already used by Docker/Podman/Kubernetes.
- All drivers: insert OPENSHELL_USER_ENVIRONMENT sidecar after
environment.extend(user_env) so a user-supplied key of the same
name cannot clobber the JSON payload the supervisor parses.
- Podman driver: fix template/spec env precedence — template env is
now applied first with spec overwriting, matching the other drivers
(spec is user-specified and should win over image defaults).
- CLI: add parse_env_pairs() that rejects OPENSHELL_* prefixed keys
with a clear error message. Both sandbox create and exec call sites
updated to use it.
- SSH handler: filter out OPENSHELL_* keys from user_environment
before injecting into child processes.
- Docs: add "Set Environment Variables" section to manage-sandboxes.mdx
documenting --env for both create and exec, per-command override
semantics, and the OPENSHELL_* reservation.
- Tests: add sandbox_create_env_rejects_reserved_prefix test.
- Fix rustfmt formatting in lib.rs, container.rs, driver.rs.
* fix(server): reject OPENSHELL_* env keys at API boundary
Add server-side validation to reject environment variable keys starting
with OPENSHELL_ in SandboxSpec.environment, SandboxTemplate.environment,
and ExecSandboxRequest.environment. This closes the gap where SDK/gRPC
callers (bypassing the CLI) could inject reserved keys that affect
supervisor-internal state.
The CLI already rejects these keys via parse_env_pairs, but the API
boundary is the authoritative enforcement point since all clients
(CLI, SDK, direct gRPC) pass through it.
* fix(server): validate env key names and control chars at API boundary
Add POSIX env key format validation (^[A-Za-z_][A-Za-z0-9_]*$) and
control character rejection for SandboxSpec.environment,
SandboxTemplate.environment, and ExecSandboxRequest.environment at the
gRPC validation layer. This catches invalid keys like "1BAD" or
"BAD-NAME" and values containing newlines at the API boundary rather
than letting them surface as driver-specific container/pod failures.
Also add matching CLI-side validation in parse_env_pairs so users get
immediate feedback. Update docs to say "OpenShell rejects" rather than
"the CLI rejects" since enforcement is now at both boundaries.
* fix(server): allow exec helper keys and add env size limit
Split exec env validation so OPENSHELL_PYFUNC_B64 (used by the Python
SDK's exec_python()) passes through while runtime-clobberable keys
like OPENSHELL_SANDBOX_ID are still rejected. Create-time validation
remains strict — all OPENSHELL_* keys are blocked.
Add a 256 KiB total serialized-size limit on create-time environment
maps. The drivers serialize the full map into a single
OPENSHELL_USER_ENVIRONMENT env var; capping the input at the API
boundary prevents driver/runtime-specific startup failures from
oversized env blocks.
Add template environment control-character test and exec helper key
acceptance test.
---------
Signed-off-by: Russell Bryant <russell.bryant@gmail.com>
openshell-driver-docker
Docker-backed compute driver for local OpenShell gateways.
The driver manages sandbox containers through the local Docker daemon with the
bollard client. It is intended for developer environments where Docker is
already available and running Kubernetes would be unnecessary.
Runtime Model
The gateway runs as a host process. The Docker driver creates one container per
sandbox and starts the openshell-sandbox supervisor inside that container. The
supervisor then creates the nested sandbox namespace for the agent process.
Docker containers join an OpenShell-managed bridge network. The driver injects
host.openshell.internal and host.docker.internal so supervisors have stable
names for reaching the gateway host. On Docker Desktop, Colima, Rancher
Desktop, OrbStack, and macOS-hosted gateways, those names use Docker's
host-gateway alias. On native Linux Docker, the gateway also binds the bridge
gateway IP so containers can call back to the host process.
Container Contract
The driver-controlled container settings are part of the sandbox security contract:
| Setting | Purpose |
|---|---|
user = "0" |
The supervisor needs root inside the container to prepare namespaces, mounts, Landlock, and seccomp. |
network_mode = openshell |
Places the supervisor on the managed Docker bridge network. |
cap_add |
Grants supervisor-only capabilities required for namespace setup and process inspection. |
apparmor=unconfined |
Avoids Docker's default profile blocking required mount operations. |
restart_policy = unless-stopped |
Keeps managed sandboxes resumable across daemon or gateway restarts. |
PidsLimit |
Enforces the sandbox PID budget at the Docker cgroup layer. Set [openshell.drivers.docker].sandbox_pids_limit = 0 to inherit the Docker/runtime default. |
| CDI GPU request | Uses the sandbox gpu_device value when set; otherwise requests all NVIDIA GPUs when the sandbox spec asks for GPU support and daemon CDI support is detected. |
The agent child process does not retain these supervisor privileges.
Supervisor Binary Resolution
The Docker driver bind-mounts a host-side Linux openshell-sandbox binary into
each sandbox container. Resolution order is:
supervisor_binin[openshell.drivers.docker].- A sibling
openshell-sandboxnext to the runningopenshell-gatewaybinary. - A local Linux cargo target build for the Docker daemon architecture.
supervisor_imagein[openshell.drivers.docker], or the release-matched default supervisor image, extracting/openshell-sandbox.
Release and Docker-image gateway builds bake the matching supervisor image tag
into the binary at compile time. The default Docker supervisor image is not
:latest unless a custom build explicitly sets that tag.
Callback and TLS
OPENSHELL_ENDPOINT is injected from the gateway's configured gRPC endpoint.
When no endpoint is configured, the driver uses
host.openshell.internal:<gateway-port> with the appropriate HTTP or HTTPS
scheme. Set host_gateway_ip only when the host has an explicit, locally
assigned address that containers should use for callbacks; package-managed
macOS gateways should leave it unset.
For HTTPS endpoints, the server certificate must include the endpoint host as a subject alternative name. Docker sandboxes also need the client TLS bundle mounted into the container and exposed with:
OPENSHELL_TLS_CAOPENSHELL_TLS_CERTOPENSHELL_TLS_KEY
HTTP endpoints reject TLS material because the supervisor would not use it.
Environment Ownership
The driver merges template environment and sandbox spec environment first, then overwrites security-critical keys:
OPENSHELL_ENDPOINTOPENSHELL_SANDBOX_IDOPENSHELL_SANDBOXOPENSHELL_SSH_SOCKET_PATHOPENSHELL_SANDBOX_COMMAND- TLS path variables when HTTPS is enabled
Do not allow sandbox images or templates to override these values.