* feat(vm): support corporate HTTP forward proxy egress for microVM sandboxes The corporate forward proxy machinery from #1792 is driver-agnostic and already merged: openshell-supervisor-network implements CONNECT chaining, NO_PROXY matching, credentials, https:// proxies and corporate CA trust, and openshell-sandbox exposes it as six argv-only flags. Podman gained the driver half in #2245/#2512 and Kubernetes in #2633; the VM driver had none of it, so VM sandboxes on proxy-only networks could not reach any destination requiring the proxy even when policy allowed it. The blocking piece was not proxy logic but delivery: the VM guest init script runs as PID 1 and execs a fixed supervisor command line, and libkrun's krun_set_exec receives an empty argv, so there was no channel for driver-owned supervisor arguments. The supervisor's proxy flags deliberately have no environment fallback, and build_guest_environment merges user-supplied environment, so the guest env is not a safe transport either. Add a driver-authored argument file, mirroring the existing init.d manifest: the driver writes /opt/openshell/supervisor-args into the overlay upperdir on every launch and the guest reads it verbatim, one argument per line, appending it to every supervisor exec. It is written even when empty, which is what makes the channel unforgeable -- the upperdir always shadows the read-only image layer, so an image can neither supply its own arguments nor disable the operator's by omitting the file. Because both launch backends exec the same init script, this covers libkrun and QEMU without touching either. A microVM has no bind mounts or container secrets, so the credential and CA bundle are staged into the per-sandbox overlay the way the gateway JWT already is: credential root-only at 0600, CA at 0644, both rewritten every launch so a removed setting clears prior material, and both deleted with the sandbox state directory. This places the credential at rest in the overlay image on the gateway host, which differs from the Podman secret model and is documented as an explicit security consideration. Validation is fail-closed and shared: a new openshell_core::driver_utils::validate_upstream_proxy_settings holds the pairing rules the Podman driver established, and both the gateway and the driver call it so an invalid table names the offending key instead of surfacing as an opaque driver-readiness timeout. Guest egress leaves through gvproxy, so a proxy on the gateway host's loopback is reachable only through host.openshell.internal; the guest to gateway callback is unaffected. Closes #3088 Signed-off-by: Philippe Martin <phmartin@redhat.com> * fix(vm): bound the proxy CA read and scope the host-loopback recipe Two review findings on the corporate forward proxy support for microVM sandboxes. The driver read the operator's proxy_ca_bundle with an unbounded fs::read and accepted it on a substring match for the PEM BEGIN CERTIFICATE marker. A special file such as /dev/zero therefore grew driver memory without bound on every authorized sandbox create, and a PEM block holding invalid DER passed the host check but contributes no trust anchor in the guest, so every supervisor would fail after boot with an error attributed to the sandbox rather than to the setting. Move the read into openshell-core as read_upstream_proxy_ca_bundle_file: it reuses the credential reader's bounded-read path (non-regular files rejected on fstat, size capped, read bounded even if the file grows), then requires at least one anchor that RootCertStore::add_parsable_certificates accepts. The supervisor's own reader now delegates to it, so host acceptance and guest acceptance are the same function and cannot drift. The published host-loopback recipe was written for libkrun only. gvproxy NATs host.openshell.internal to the gateway host's 127.0.0.1, but GPU sandboxes run on the QEMU/TAP backend where that name resolves to the TAP host address and the driver's own nftables input chain accepts only the gateway port from the guest — no proxy on the gateway host is reachable there at any bind address, so an operator following the generic recipe lost all proxy-required egress while configuration validation succeeded. Scope the recipe to libkrun in every reference and reject a gateway-host proxy URL when a launch plan resolves to QEMU, naming the reason, instead of booting a sandbox whose policy-approved CONNECTs all time out. Signed-off-by: Philippe Martin <phmartin@redhat.com> * fix(vm): match the QEMU proxy preflight to the selected TAP host The gateway-host proxy guard added for the QEMU/TAP backend classified the wrong set of addresses in both directions. It ran at the top of configure_qemu_launch_plan, before the subnet allocation that settles plan.host_ip, so it could not compare against the address the guest actually reaches the host on. An operator pointing https_proxy at the sandbox's own TAP host address, such as 10.0.128.1, passed the check, and the driver's nftables input chain — which accepts only the gateway port from the guest — then dropped every policy-approved CONNECT, which is exactly the silent timeout the guard exists to prevent. In the other direction it rejected 192.168.127.254 unconditionally. That address is special only to libkrun/gvproxy; on QEMU/TAP it is an ordinary address that may be routable through the guest's masqueraded egress, so the guard refused a working configuration. Run the check after the launch plan's network allocation, on both the freshly-allocated and already-complete paths, and compare IP literals with that sandbox's selected TAP host. Loopback literals, localhost, and the documented host aliases that write_host_gateway_aliases seeds to the TAP host still classify as the gateway host, and the failure names the address. The gvproxy host-loopback constant returns to being a documentation anchor. Signed-off-by: Philippe Martin <phmartin@redhat.com> --------- Signed-off-by: Philippe Martin <phmartin@redhat.com>
OpenShell Architecture
OpenShell runs autonomous AI agents in sandboxed environments with explicit policy, credential, identity, and network boundaries. The target architecture is built around three stable runtime components: the CLI, the Gateway, and the Supervisor.
The CLI, SDK, and TUI provide user-facing access. The gateway is the authenticated control plane: it owns API access, durable state, policy and settings delivery, provider and inference configuration, and relay coordination. The supervisor runs inside every sandbox workload and is the local security boundary. It launches the agent as a restricted child process and enforces policy where process identity, filesystem access, network egress, and runtime credentials are visible.
Infrastructure-specific work sits behind integration boundaries. Compute, credentials, control-plane identity, and sandbox identity each have a driver or adapter boundary so OpenShell can integrate with native runtimes, secret stores, identity providers, and workload identity systems without moving those concerns into the core gateway or sandbox model.
flowchart TB
subgraph USER["User Interfaces"]
CLI["CLI"]
SDK["SDK"]
TUI["TUI"]
end
subgraph CP["Control Plane"]
GW["Gateway core"]
DB[("Shared persistence")]
COMPUTE["Compute"]
CREDS["Credentials"]
CPIDENT["Control-plane identity"]
SIDENT["Sandbox identity"]
CDRV["Compute driver"]
CRDRV["Credentials driver"]
CPIDRV["Control-plane identity driver"]
SIDRV["Sandbox identity driver"]
end
subgraph INFRA["Integrated Infrastructure"]
RUNTIME["Docker / Podman / Kubernetes / VM"]
SECRETSTORE["Eg: Keychain / Secret Service / Vault / Kubernetes Secrets"]
IDP["Eg: mTLS / OIDC / Local identity"]
WORKLOADID["Eg: SPIFFE / Gateway-issued workload identity"]
end
subgraph DP["Sandbox Data Plane"]
SUP["Supervisor"]
PROXY["Policy proxy"]
ROUTER["Inference router"]
POLICY["OPA policy engine"]
AGENT["Restricted agent process"]
end
CLI -->|"gRPC / HTTP"| GW
SDK -->|"gRPC / HTTP"| GW
TUI -->|"gRPC / HTTP"| GW
GW --> DB
GW --> COMPUTE
GW --> CREDS
GW --> CPIDENT
GW --> SIDENT
COMPUTE -->|"gRPC / UDS"| CDRV
CREDS -->|"gRPC / UDS"| CRDRV
CPIDENT -->|"gRPC / UDS"| CPIDRV
SIDENT -->|"gRPC / UDS"| SIDRV
CDRV --> RUNTIME
CRDRV --> SECRETSTORE
CPIDRV --> IDP
SIDRV --> WORKLOADID
RUNTIME -->|"provisions workload"| SUP
SUP -->|"outbound control, config, logs, relay"| GW
SUP -->|"spawn + restrict"| AGENT
AGENT -->|"all ordinary egress"| PROXY
PROXY -->|"evaluate"| POLICY
PROXY -->|"allowed traffic"| EXT["External services"]
PROXY -->|"inference.local"| ROUTER
ROUTER -->|"managed inference"| MODEL["Inference backends"]
Core Boundaries
| Component | Boundary |
|---|---|
| CLI, SDK, TUI | User-facing management surfaces. They talk to the gateway and do not need to know which infrastructure drivers are active. |
| Gateway | Authenticated control plane, API server, durable state, policy and settings delivery, provider and inference config, supervisor session ownership, and relay coordination. |
| Compute subsystem | Sandbox lifecycle semantics: creation, deletion, watching, reconciliation, and state transitions. Platform provisioning details belong to the compute driver. |
| Credentials subsystem | Logical provider and credential resolution. Secret storage and platform-native credential access belong to credentials drivers. |
| Control-plane identity | Authentication and authorization for users, operators, and API clients. External identity verification belongs to identity drivers. |
| Sandbox identity | Workload identity for supervisors and sandbox-to-sandbox authorization. Identity issuance or verification belongs to sandbox identity drivers. |
| Supervisor | Sandbox-local security boundary. It prepares isolation, fetches config, injects credentials, runs relay endpoints, starts the proxy, and launches restricted agent processes. |
| Policy proxy | Mandatory egress path for agent traffic. It enforces destination, binary identity, SSRF, TLS/L7, credential injection, and inference interception rules. |
| Inference router | Sandbox-local forwarding for https://inference.local to configured model backends. |
Integrating with the Ecosystem
OpenShell should integrate with infrastructure ecosystems instead of replacing them. The core value is safe, policy-enforced agent execution. Runtimes, schedulers, secret stores, identity providers, workload identity systems, image pipelines, storage, and GPU or device exposure should remain owned by the platforms that already provide them.
The gateway owns OpenShell control-plane semantics: sandbox state, lifecycle ordering, policy and settings resolution, credential mapping, authorization, inference configuration, and relay coordination. Drivers translate those semantics into platform-native operations. They should stay thin, preserve native behavior by default, and report platform lifecycle events back through the shared contracts.
The supervisor owns OpenShell sandbox semantics. Filesystem policy, process privilege reduction, network proxying, inference interception, credential injection, security logging, and gateway relay behavior should remain consistent across runtimes.
This keeps OpenShell usable in local single-player setups, Kubernetes deployments, VM-backed sandboxes, and future third-party environments. A new integration should make OpenShell feel like a well-behaved member of that ecosystem.
Gateways and Sandboxes
The gateway and sandbox split control-plane authority from runtime enforcement. The gateway owns durable platform state: sandboxes, policy revisions, runtime settings, provider records, inference configuration, session records, and authorization decisions. A sandbox owns the local execution boundary: process identity, filesystem access, network egress, credential injection, local logs, and the agent child process.
The relationship is supervisor initiated. Each sandbox supervisor connects outbound to a known gateway endpoint, authenticates as a sandbox workload, and keeps a live session open for control traffic and relays. This avoids requiring every compute driver to solve gateway-to-sandbox reachability through pod IPs, bridge networks, port mappings, NAT traversal, or bespoke tunnels. The common runtime requirement is narrower: the supervisor must be able to reach the gateway.
The compute-driver capability contract identifies whether a driver reports
runtime readiness. Most drivers use the supervisor session model above. A
driver that sets driver_reports_runtime_readiness may self-report readiness
without a supervisor session. Every driver receives the canonical create-time
policy in DriverSandboxSpec; drivers without a supervisor use the existing
sandbox configuration API for later revisions. The Windows MXC driver reports
its own readiness and does not expose interactive connect or governed egress.
The gateway delivers desired state; the sandbox applies it locally. Policy, settings, credentials, and inference routes flow from the gateway to the supervisor. The supervisor validates and applies what can change at runtime, keeps last-known-good config when refresh fails, and leaves static isolation controls in place until the sandbox is recreated.
Live operations use the same authenticated gateway-supervisor relationship. Config refresh, policy updates, credential delivery, log push, connect, exec, file sync, and relay setup are multiplexed over supervisor sessions. If a session drops, the sandbox may keep running, but live operations fail or become unreachable until the supervisor reconnects and reconciles state.
Architecture Docs
Architecture docs are short subsystem overviews. User-facing how-to content
lives in docs/. Implementation notes that only matter to one crate belong in
that crate's README.md.
| Document | Purpose |
|---|---|
| Gateway | Gateway control plane, auth, APIs, persistence, settings, and relay coordination. |
| Sandbox | Sandbox supervisor, child process isolation, proxy, credentials, inference, connect, and logs. |
| Sandbox Limits | Sandbox supervisor and egress safety ceilings, ownership rules, current enforcement, and known gaps. |
| Security Policy | Policy model, enforcement layers, policy updates, policy advisor, and security logging. |
| Compute Runtimes | Docker, Podman, Kubernetes, VM, sandbox images, and runtime-specific responsibilities. |
| Build | Build artifacts, CI/E2E, docs site validation, and release packaging. |
| Google Vertex AI Provider | Implementation reference for the google-vertex-ai provider, from CLI through gateway to sandbox. |
| Windows MSVC Build | Build-only native Windows MSVC lane (x64/ARM64) and unsupported-runtime behavior on Windows. |
rfc/ vs architecture/
Broad design proposals start as GitHub issues. If maintainers decide a proposal needs broad consensus, they assign an RFC number from the issue and the RFC lives in rfc/. Once an RFC is adopted, appropriate details should be written back to architecture docs.
architecture/ serves as the canonical reference for OpenShell's design and architecture.
rfc serves to help facilitate discussion and ensure the few changes that need that level of review are appropriately designed. These are useful for understanding the context in which certain architecture designs were made.