Index
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "NVIDIA OpenShell Developer Guide"
description: "OpenShell is the safe, private runtime for autonomous AI agents. Run agents in sandboxed environments that protect your data, credentials, and infrastructure."
keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Security, Privacy, Inference Routing"
position: 1
---
import { BadgeLinks } from "./_components/BadgeLinks";
import { CommandTerminal } from "./_components/CommandTerminal";
<BadgeLinks
badges={[
{
href: "https://github.com/NVIDIA/OpenShell",
src: "https://img.shields.io/badge/github-repo-green?logo=github",
alt: "GitHub",
},
{
href: "https://github.com/NVIDIA/OpenShell/blob/main/LICENSE",
src: "https://img.shields.io/badge/License-Apache_2.0-blue",
alt: "License",
},
{
href: "https://pypi.org/project/openshell/",
src: "https://img.shields.io/badge/PyPI-openshell-orange?logo=pypi",
alt: "PyPI",
},
]}
/>
NVIDIA OpenShell is the safe, private runtime for autonomous AI agents. It provides sandboxed execution environments
that protect your data, credentials, and infrastructure. Agents run with exactly the permissions they need and
nothing more, governed by declarative policies that prevent unauthorized file access, data exfiltration, and
uncontrolled network activity.
Get Started
Install OpenShell and create your first sandbox in two commands.
<llms-ignore>
<CommandTerminal command="curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh" />
</llms-ignore>
<llms-only>
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
openshell sandbox create -- claude</llms-only>
Refer to the Quickstart for more details.
---
Explore
<div className="explore-cards">
<Cards>
<Card title="About OpenShell" href="/about/overview">
Learn about OpenShell and its capabilities.
<Badge intent="tip" minimal outlined>Concept</Badge>
</Card>
<Card title="Quickstart" href="/get-started/quickstart">
Install OpenShell and create your first sandbox in two commands.
<Badge intent="tip" minimal outlined>Tutorial</Badge>
</Card>
<Card title="Tutorials" href="/get-started/tutorials">
Hands-on walkthroughs from first sandbox to custom policies.
<Badge intent="tip" minimal outlined>Concept</Badge>
</Card>
<Card title="Gateways and Sandboxes" href="/sandboxes/manage-gateways">
Deploy gateways, create sandboxes, configure policies, providers, and community images for your AI agents.
<Badge intent="tip" minimal outlined>Concept</Badge>
</Card>
<Card title="Inference Routing" href="/sandboxes/inference-routing">
Keep inference traffic private by routing API calls to local or self-hosted backends.
<Badge intent="tip" minimal outlined>Concept</Badge>
</Card>
<Card title="Observability" href="/observability">
Understand sandbox logs, access them with the CLI and TUI, and export OCSF JSON records.
<Badge intent="tip" minimal outlined>How-To</Badge>
</Card>
<Card title="Reference" href="/reference/default-policy">
Policy schema, environment variables, and default policy details.
<Badge intent="tip" minimal outlined>Reference</Badge>
</Card>
<Card title="Security Best Practices" href="/security/best-practices">
Every configurable security control, its default, and the risk of changing it.
<Badge intent="tip" minimal outlined>Concept</Badge>
</Card>
</Cards>
</div>
---
<Warning title="Notice and Disclaimer">
This software automatically retrieves, accesses or interacts with external
materials. Those retrieved materials are not distributed with this software
and are governed solely by separate terms, conditions and licenses. You are
solely responsible for finding, reviewing and complying with all applicable
terms, conditions, and licenses, and for verifying the security, integrity and
suitability of any retrieved materials for your specific use case. This
software is provided "AS IS", without warranty of any kind. The author makes
no representations or warranties regarding any retrieved materials, and
assumes no liability for any losses, damages, liabilities or legal
consequences from your use or inability to use this software or any retrieved
materials. Use this software and the retrieved materials at your own risk.
</Warning>
---
About/Container Gateway
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Running the Gateway as a Container"
sidebar-title: "Container Gateway"
description: "Run the OpenShell gateway using docker run or docker-compose without the installer."
keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Docker, Podman, docker-compose, container, immutable OS, bootc, rpm-ostree"
position: 4
---
Use this approach when you want to run the OpenShell gateway as a container instead of installing it with the system package manager. This is useful on immutable OS distributions (Fedora CoreOS, bootc-based images, Silverblue) where the standard installer is not appropriate, or anywhere you prefer a container-first workflow.
The gateway image is published at ghcr.io/nvidia/openshell/gateway.
Prerequisites for the Docker Driver
When the gateway runs as a container and creates Docker-backed sandboxes, the gateway container
communicates with the host Docker daemon via the mounted socket. This requires three things beyond
a basic docker run:
1. Docker socket access. The gateway process must be able to read and write the Docker socket.
Add the docker group (or the GID of /var/run/docker.sock) so the socket is accessible
without running as root.
2. gRPC endpoint. Sandbox containers call back to the gateway over the OPENSHELL_GRPC_ENDPOINT
address. The Docker driver substitutes host.openshell.internal as the host and the gateway's
own bind port as the port — only the scheme (http or https) is preserved. Use
http://host.openshell.internal:8080 when TLS is disabled and https://host.openshell.internal:8080
when mTLS is enabled. The docker driver automatically binds the gateway to the bridge network
interface so sandbox containers can reach it — you do not need to expose the port on 0.0.0.0.
3. Supervisor binary on the host. The gateway bind-mounts the openshell-sandbox supervisor
binary into each sandbox container. Because bind-mount paths are resolved by the host Docker
daemon (not inside the gateway container), the binary must exist at a path on the host
filesystem and be mounted at the same absolute path inside the gateway container. That way
the path the gateway records internally matches what Docker can find on the host when it
creates sandbox containers.
Quick Start
Extract the supervisor binary to the host once, then start the gateway:
mkdir -p ~/openshell/supervisor
docker create --name tmp-supervisor ghcr.io/nvidia/openshell/supervisor:latest
docker cp tmp-supervisor:/openshell-sandbox ~/openshell/supervisor/openshell-sandbox
docker rm tmp-supervisor
chmod +x ~/openshell/supervisor/openshell-sandboxStart the gateway:
docker run -d \
--name openshell-gateway \
--restart unless-stopped \
--group-add docker \
-p 127.0.0.1:8080:8080 \
-v openshell-state:/var/openshell \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ~/openshell/supervisor/openshell-sandbox:~/openshell/supervisor/openshell-sandbox:ro \
-e OPENSHELL_DRIVERS=docker \
-e OPENSHELL_GRPC_ENDPOINT=http://host.openshell.internal:8080 \
-e OPENSHELL_DOCKER_SUPERVISOR_BIN=~/openshell/supervisor/openshell-sandbox \
-e OPENSHELL_DB_URL=sqlite:/var/openshell/openshell.db \
-e OPENSHELL_DISABLE_TLS=true \
ghcr.io/nvidia/openshell/gateway:latestThe volume mount uses ~/openshell/supervisor/openshell-sandbox for both the host and container
paths. The shell expands ~ in both halves before passing the argument to Docker, so both sides
resolve to the same absolute path (e.g., /home/user/openshell/supervisor/openshell-sandbox).
This satisfies the same-path requirement so the host Docker daemon can find the binary when
creating sandbox containers.
Register the gateway with the CLI. If running on the same machine, use --local:
openshell gateway add http://127.0.0.1:8080 --local --name localIf registering from a different machine on the same network, use the host IP and --remote:
openshell gateway add http://HOST_IP:8080 --remote --name remoteConfirm the CLI can reach the gateway:
openshell status<Warning>
Disabling TLS removes authentication. This example binds to 127.0.0.1 so only local
connections are accepted. To accept remote connections, enable mTLS or restrict access with
a firewall rule.
</Warning>
Full mTLS Setup
To run the gateway with mutual TLS, generate the PKI bundle first, then start the gateway with the cert paths configured.
Bootstrap the PKI into a local state directory:
mkdir -p ~/.local/state/openshell/tlsdocker run --rm \
-v "$HOME/.local/state/openshell:/home/openshell/.local/state/openshell" \
-v "$HOME/.config/openshell:/home/openshell/.config/openshell" \
ghcr.io/nvidia/openshell/gateway:latest \
generate-certs \
--output-dir /home/openshell/.local/state/openshell/tls \
--server-san host.openshell.internal
This writes the server and client certificates under ~/.local/state/openshell/tls/, writes sandbox JWT signing keys under ~/.local/state/openshell/tls/jwt/, and copies the client bundle to ~/.config/openshell/gateways/openshell/mtls/ so the CLI picks it up automatically.
Start the gateway with mTLS enabled:
docker run -d \
--name openshell-gateway \
--restart unless-stopped \
--group-add docker \
-p 127.0.0.1:8080:8080 \
-v "$HOME/.local/state/openshell:/home/openshell/.local/state/openshell" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ~/openshell/supervisor/openshell-sandbox:~/openshell/supervisor/openshell-sandbox:ro \
-e OPENSHELL_DRIVERS=docker \
-e OPENSHELL_GRPC_ENDPOINT=https://127.0.0.1:8080 \
-e OPENSHELL_DOCKER_SUPERVISOR_BIN=~/openshell/supervisor/openshell-sandbox \
-e OPENSHELL_DB_URL=sqlite:/home/openshell/.local/state/openshell/openshell.db \
-e OPENSHELL_LOCAL_TLS_DIR=/home/openshell/.local/state/openshell/tls \
-e OPENSHELL_TLS_CERT=/home/openshell/.local/state/openshell/tls/server/tls.crt \
-e OPENSHELL_TLS_KEY=/home/openshell/.local/state/openshell/tls/server/tls.key \
-e OPENSHELL_TLS_CLIENT_CA=/home/openshell/.local/state/openshell/tls/ca.crt \
-e OPENSHELL_ENABLE_MTLS_AUTH=true \
-e OPENSHELL_DOCKER_TLS_CA=/home/openshell/.local/state/openshell/tls/ca.crt \
-e OPENSHELL_DOCKER_TLS_CERT=/home/openshell/.local/state/openshell/tls/client/tls.crt \
-e OPENSHELL_DOCKER_TLS_KEY=/home/openshell/.local/state/openshell/tls/client/tls.key \
ghcr.io/nvidia/openshell/gateway:latestRegister the gateway with mTLS:
openshell gateway add https://127.0.0.1:8080 --local --name localDocker Compose
The deploy/docker/ directory in
the repository contains a production-ready Compose setup with full inline documentation:
| File | Purpose |
|---|---|
| docker-compose.yml | Gateway service, volumes, and environment variables |
| gateway.toml | TOML configuration mounted into the container |
Clone or copy those files, then start the gateway:
docker compose -f deploy/docker/docker-compose.yml up -dRegister the gateway with the CLI. If registering from the same machine:
openshell gateway add http://127.0.0.1:8080 --local --name localIf registering from a different machine on the same network, replace HOST_IP with the
machine's LAN address:
openshell gateway add http://HOST_IP:8080 --remote --name remoteUsing Podman
Replace docker with podman in the commands above. Mount the Podman socket instead of the Docker socket and set the driver to podman:
podman run -d \
--name openshell-gateway \
-p 127.0.0.1:8080:8080 \
-v openshell-state:/var/openshell \
-v "$XDG_RUNTIME_DIR/podman/podman.sock:/var/run/podman.sock" \
-e OPENSHELL_DRIVERS=podman \
-e OPENSHELL_PODMAN_SOCKET=/var/run/podman.sock \
-e OPENSHELL_DB_URL=sqlite:/var/openshell/openshell.db \
-e OPENSHELL_DISABLE_TLS=true \
ghcr.io/nvidia/openshell/gateway:latestNext Steps
- To create your first sandbox, refer to the Quickstart.
- To control what the agent can access, refer to Policies.
- For environment variable reference, refer to Sandbox Compute Drivers.
---
About/How It Works
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "How OpenShell Works"
sidebar-title: "How It Works"
description: "Understand the OpenShell architecture, runtime boundaries, gateways, sandboxes, and ecosystem integration points."
keywords: "Generative AI, Cybersecurity, AI Agents, Architecture, Gateway, Sandbox, Inference Routing"
position: 2
---
OpenShell 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
control plane: it owns API access, 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 UI["User interfaces"]
CLI["CLI"]
SDK["SDK"]
TUI["TUI"]
end subgraph CP["Control plane"]
GW["Gateway"]
DB[("Entity persistence")]
DRIVERS["Compute, credentials, and identity drivers"]
end
subgraph INFRA["Integrated infrastructure"]
RUNTIME["Docker, Podman, Kubernetes, or VM"]
SECRETSTORE["Secret stores"]
IDP["Identity providers"]
WORKLOADID["Workload identity"]
end
subgraph DP["Sandbox data plane"]
SUP["Supervisor"]
AGENT["Restricted agent process"]
PROXY["Policy proxy"]
POLICY["OPA policy engine"]
ROUTER["Inference router"]
end
CLI -->|"gRPC / HTTP"| GW
SDK -->|"gRPC / HTTP"| GW
TUI -->|"gRPC / HTTP"| GW
GW --> DB
GW --> DRIVERS
DRIVERS --> RUNTIME
DRIVERS --> SECRETSTORE
DRIVERS --> IDP
DRIVERS --> WORKLOADID
RUNTIME -->|"provisions workload"| SUP
SUP -->|"control, config, logs, relay"| GW
SUP -->|"spawn and restrict"| AGENT
AGENT -->|"ordinary egress"| PROXY
PROXY -->|"evaluate"| POLICY
PROXY -->|"allowed traffic"| EXT["External services"]
PROXY -->|"inference.local"| ROUTER
ROUTER -->|"managed inference"| MODEL["Inference backends"]
Deployment Models
OpenShell can run on a single local machine or in a remote Kubernetes cluster.
The CLI workflow stays the same: users point the CLI, SDK, or TUI at a gateway,
and the gateway provisions sandboxes through its configured compute driver.
| Deployment | How it works | Best for |
|---|---|---|
| Local machine | The gateway runs on the user's workstation or a nearby development host and creates sandboxes with Docker, Podman, or a VM runtime. The supervisor inside each sandbox connects back to that local gateway. | Individual development, local agent experiments, and private workstation workflows. |
| Remote Kubernetes cluster | The gateway runs as a cluster service and creates sandbox pods in the configured namespace. Supervisors connect outbound to the gateway endpoint, so clients do not need direct pod access. | Shared teams, centrally managed policy, remote compute, GPUs, and production-like environments. |
This deployment split keeps the runtime model consistent. Local deployments use
the host's container or VM runtime as the integrated infrastructure. Kubernetes
deployments use the cluster scheduler, networking, secrets, identity, and GPU
device plugins without changing the gateway and sandbox contract.
Core Components
| Component | Boundary |
|---|---|
| Sandboxes | Data-plane workloads that run the supervisor, launch restricted agent processes, apply local isolation, push logs, and maintain the gateway session. |
| Gateways | Authenticated control plane that owns API access, durable state, sandbox lifecycle, settings delivery, authorization, and relay coordination. |
| Providers | Credential and provider records that map logical agent needs to platform or user-managed secrets without exposing raw credentials to the agent process. |
| Policies | Declarative controls for filesystem access, process identity, network egress, L7 rules, credential injection, and runtime policy updates. |
| Inference Routing | Managed https://inference.local path that routes model traffic to configured backends while keeping provider credentials outside the sandbox. |
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 custom tunnels.
The gateway delivers desired state. The supervisor applies it locally, keeps last-known-good config when refresh fails, and leaves static isolation controls in place until the sandbox is recreated. Live operations such as config refresh, policy updates, credential delivery, log push, connect, exec, file sync, and relay setup use the same authenticated gateway-supervisor relationship.
Supervisor Protection Layers
The supervisor is the sandbox-local enforcement component. It starts before the
agent process, prepares the sandbox runtime, fetches gateway configuration, and
then launches the agent under the active policy.
| Protection layer | Supervisor responsibility |
|---|---|
| Process | Drops privileges, applies process identity rules, disables privilege escalation paths, and starts the agent as a restricted child process. |
| Filesystem | Applies filesystem policy before the agent starts so undeclared paths are inaccessible and declared paths are read-only or read-write as configured. |
| Network | Routes ordinary egress through the policy proxy so destination, port, binary identity, and L7 request rules can be evaluated before traffic leaves the sandbox. |
| Credentials | Receives credential material from the gateway and injects it only through configured policy paths or request-time proxy rules. |
| Inference | Intercepts https://inference.local and forwards model traffic through the configured inference route instead of exposing provider credentials to the agent. |
| Observability | Emits local security and lifecycle logs, pushes sandbox logs to the gateway, and keeps relay endpoints available for connect, exec, and file transfer operations. |
Static controls such as filesystem and process isolation are established at
sandbox start and require sandbox recreation to change. Dynamic controls such as
network policy, credential delivery, and inference routing can refresh over the
live gateway-supervisor session.
Ecosystem Integration
OpenShell integrates with infrastructure ecosystems instead of replacing them. Runtimes, schedulers, secret stores, identity providers, workload identity systems, image pipelines, storage, and GPU or device exposure remain owned by the platforms that 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.
The supervisor owns OpenShell sandbox semantics. Filesystem policy, process privilege reduction, network proxying, inference interception, credential injection, security logging, and gateway relay behavior stay consistent across Docker, Podman, Kubernetes, VM-backed sandboxes, and future integrations.
---
About/Installation
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Installation"
sidebar-title: "Installation"
description: "Install OpenShell, choose a compute driver, and connect to a gateway."
keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Installation, Setup, Gateway, Docker, Podman, MicroVM, Kubernetes"
position: 3
---
Install OpenShell
Install OpenShell with a single command:
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | shThe script detects your operating system and installs the OpenShell CLI and gateway with your native package manager. It then starts the local gateway server so you can begin creating sandboxes.
You can also download release artifacts directly from the OpenShell GitHub Releases page.
Use openshell status to confirm the CLI can reach the gateway.
Supported Compute Drivers
OpenShell supports several local compute drivers. Package-managed gateways leave the driver unset by default so the gateway can auto-detect an available driver. Set compute_drivers in the gateway TOML when you need to pin a specific driver.
| Compute Driver | How It Is Configured | System Requirements |
|---|---|---|
| Podman | The gateway is configured to create rootless Podman containers through the Podman API socket. | Linux with Podman 5.x, cgroups v2, rootless networking, and an active Podman user socket. |
| Docker | The gateway is configured to create containers through Docker Desktop or Docker Engine. | Docker Desktop or Docker Engine 28.0 or later on the gateway host. |
| MicroVM | The gateway is configured to create VM-backed sandboxes. | Host virtualization support. MicroVM uses Hypervisor.framework on macOS, KVM on Linux, and QEMU for GPU-backed sandboxes on Linux. |
For detailed driver behavior, refer to Sandbox Compute Drivers. For gateway and sandbox operations, refer to Gateways and Sandboxes.
macOS
On macOS, the install script uses Homebrew. The Homebrew package installs the openshell CLI, the gateway binary, and a Homebrew-managed gateway service.
The Homebrew service uses the gateway's built-in 127.0.0.1:17670 listener and generates a local mTLS bundle on install. The installer registers https://localhost:17670 with the CLI so TLS uses a DNS name covered by the generated certificate. The formula creates a Homebrew prefix config, such as /opt/homebrew/var/openshell/gateway.toml, without overriding bind_address. Docker Desktop and Podman Machine reuse the primary listener for sandbox callbacks when they can reach it. The gateway reads ~/.config/openshell/gateway.toml instead when that file exists. Homebrew preserves user-edited prefix and user configs during upgrades; it removes the IPv6 bind only from an unchanged config generated by the affected formula.
The CLI reads the client bundle from ~/.config/openshell/gateways/openshell/mtls/.
The installer starts the service for you. Use Homebrew service commands when you need to inspect, restart, or stop the gateway service:
brew services list
brew services restart openshellLinux
On Fedora and RHEL, the install script uses RPM packages. The RPM installs the openshell CLI, the openshell-gateway daemon, and a systemd user service.
On Debian and Ubuntu, the install script uses a Debian package. The Debian package installs the openshell CLI, the openshell-gateway daemon, VM sandbox support, and a systemd user service.
Linux packages require glibc 2.28 or newer. The installer checks libc before downloading packages and exits with an error on older glibc versions, Alpine, musl-based distributions, or unknown libc environments.
The Linux user service listens on https://127.0.0.1:17670, starts from built-in defaults, and generates a local mTLS bundle before the gateway starts. Create ~/.config/openshell/gateway.toml only when you need to override those defaults.
The CLI reads the client bundle from ~/.config/openshell/gateways/openshell/mtls/.
The installer starts the service for you. Use systemd user commands when you need to inspect, restart, or stop the gateway service:
systemctl --user status openshell-gateway
systemctl --user restart openshell-gateway
journalctl --user -u openshell-gateway -fTo keep the user service running after logout, enable linger:
sudo loginctl enable-linger $USERSnap
Install the OpenShell snap from the Snap Store:
sudo snap install openshellThe snap defines two apps: the openshell CLI and the openshell.gateway
systemd service. The gateway listens on https://127.0.0.1:17670 and
stores its database at $SNAP_COMMON/gateway.db (typically/var/snap/openshell/common/gateway.db). Create $SNAP_COMMON/gateway.toml
when you need to override gateway settings.
The snap CLI stores per-user config, data, and state under $SNAP_USER_COMMON,
typically ~/snap/openshell/common. Gateway registrations live under$SNAP_USER_COMMON/.config/openshell/gateways/ instead of~/.config/openshell/gateways/.
Snap store installs
When installing from the Snap Store, snapd automatically connects the home,network, and network-bind plugs. The docker plug still
requires manual connection:
sudo snap connect openshell:docker docker:docker-daemonThe snap declares default-provider: docker on the Docker plug so snapd will
offer to install the Docker snap, but the connection itself must be made
manually.
Locally built snap packages
When installing a locally built .snap file, no plugs are connected by default:
sudo snap install ./openshell_*.snap --dangerous
sudo snap connect openshell:home
sudo snap connect openshell:network
sudo snap connect openshell:network-bind
sudo snap connect openshell:docker docker:docker-daemon
sudo snap connect openshell:log-observe
sudo snap connect openshell:system-observeThe log-observe and system-observe plugs are needed for the gateway service
to read logs and inspect system processes. The docker plug requires thedocker:docker-daemon slot from the Docker snap and does not work with
system-installed Docker.
Gateway service
The gateway runs as a snap daemon with refresh-mode: endure, meaning snapd
will not restart it during snap refreshes. This prevents the gateway from
killing active sandbox sessions mid-refresh. Restart the service manually after
a snap refresh when you need the updated binary:
sudo systemctl restart snap.openshell.gatewayKubernetes
Kubernetes deployments use the OpenShell Helm chart. For step-by-step installation, refer to Kubernetes Setup. For chart values and packaging details, refer to the Helm chart README.
Next Steps
- To create your first sandbox, refer to the Quickstart.
- To run the gateway as a container without the installer, refer to Running the Gateway as a Container.
- To register, select, and inspect gateways, refer to Gateways.
- To supply API keys or tokens, refer to Manage Providers.
- To control what the agent can access, refer to Policies.
---
About/Overview
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Overview of NVIDIA OpenShell"
sidebar-title: "Overview"
description: "OpenShell is the safe, private runtime for autonomous AI agents. Run agents in sandboxed environments that protect your data, credentials, and infrastructure."
keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Security, Privacy, Inference Routing"
position: 1
---
NVIDIA OpenShell is an open-source runtime for executing autonomous AI agents in sandboxed environments with kernel-level isolation. It combines sandbox runtime controls and a declarative YAML policy so teams can run agents without giving them unrestricted access to local files, credentials, and external networks.
Why OpenShell Exists
AI agents are most useful when they can read files, install packages, call APIs, and use credentials. That same access can create material risk. OpenShell is designed for this tradeoff: preserve agent capability while enforcing explicit controls over what the agent can access.
Common Risks and Controls
The table below summarizes common failure modes and how OpenShell mitigates them.
| Threat | Without controls | With OpenShell |
|---|---|---|
| Data exfiltration | Agent uploads source code or internal files to unauthorized endpoints. | Network policies allow only approved destinations; other outbound traffic is denied. |
| Credential theft | Agent reads local secrets such as SSH keys or cloud credentials. | Filesystem restrictions (Landlock) confine access to declared paths only. |
| Unauthorized API usage | Agent sends prompts or data to unapproved model providers. | Privacy routing and network policies control where inference traffic can go. |
| Privilege escalation | Agent attempts sudo, setuid paths, or dangerous syscall behavior. | Unprivileged process identity and seccomp restrictions block escalation paths. |
Protection Layers at a Glance
OpenShell applies defense in depth across the following policy domains.
| Layer | What it protects | When it applies |
|---|---|---|
| Filesystem | Prevents reads/writes outside allowed paths. | Locked at sandbox creation. |
| Network | Blocks unauthorized outbound connections. | Hot-reloadable at runtime. |
| Process | Blocks privilege escalation and dangerous syscalls. | Locked at sandbox creation. |
| Inference | Reroutes model API calls to controlled backends. | Hot-reloadable at runtime. |
For details, refer to Customize Sandbox Policies and Default Policy.
Common Use Cases
OpenShell supports a range of agent deployment patterns.
| Use Case | Description |
|-----------------------------|----------------------------------------------------------------------------------------------------------|
| Secure coding agents | Run Claude Code, OpenCode, Codex, or GitHub Copilot CLI with constrained file and network access. |
| Private enterprise development | Route inference to self-hosted or private backends while keeping sensitive context under your control. |
| Compliance and audit | Treat policy YAML as version-controlled security controls that can be reviewed and audited. |
| Reusable environments | Use community sandbox images or bring your own containerized runtime. |
Next Steps
Explore these topics to go deeper:
- To understand the runtime architecture, refer to How OpenShell Works.
- To install the CLI and create your first sandbox, refer to the Quickstart.
- To learn how OpenShell enforces policy controls across protection layers, refer to Customize Sandbox Policies.
---
About/Release Notes
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "NVIDIA OpenShell Release Notes"
sidebar-title: "Release Notes"
description: "Track the latest changes and improvements to NVIDIA OpenShell."
keywords: "Generative AI, Cybersecurity, Release Notes, Changelog, AI Agents"
position: 6
---
NVIDIA OpenShell follows a frequent release cadence. Use the following GitHub resources directly.
| Resource | Description |
|---|---|
| Releases | Versioned release notes and downloadable assets. |
| Release comparison | Diff between any two tags or branches. |
| Merged pull requests | Individual changes with review discussion. |
| Commit history | Full commit log on main. |
---
About/Supported Agents
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Supported Agents"
description: "AI agent frameworks and runtimes compatible with OpenShell sandboxes."
keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Claude, Codex, Cursor"
position: 5
---
The following table summarizes the agents that run in OpenShell sandboxes. Most agent sandbox images are maintained in the OpenShell Community repository. Agents in the base image are auto-configured when passed as the trailing command to
openshell sandbox create.| Agent | Source | Default Policy | Notes |
|---|---|---|---|
| Claude Code | base | Full coverage | Works out of the box. Requires ANTHROPIC_API_KEY for direct Anthropic access, or use inference.local with a configured provider (e.g. Vertex AI). |
| OpenCode | base | Partial coverage | Pre-installed. Use ANTHROPIC_BASE_URL="https://inference.local/v1" with a configured provider. Add opencode.ai endpoint and OpenCode binary paths to the policy for full functionality. |
| Codex | base | No coverage | Pre-installed. Requires a custom policy with OpenAI endpoints and Codex binary paths. Requires OPENAI_API_KEY. |
| GitHub Copilot CLI | base | Full coverage | Pre-installed. Works out of the box. Requires GITHUB_TOKEN or COPILOT_GITHUB_TOKEN. |
| OpenClaw | NemoClaw | Blueprint-managed | Run OpenClaw more securely inside NVIDIA OpenShell with managed inference using NemoClaw. |
| Hermes Agent | NemoClaw | Blueprint-managed | Run Hermes Agent more securely inside NVIDIA OpenShell with managed inference using NemoClaw. |
| Ollama | ollama | Bundled | Run cloud and local models. Includes Claude Code, Codex, and OpenCode. Launch with openshell sandbox create --from ollama. |
| Pi | pi | Bundled | Comes with Pi pre-installed. Launch with openshell sandbox create --from pi. |
For base image details and --from usage, refer to Sandboxes.
For a complete support matrix, refer to the Support Matrix page.
---
Extensibility/Gateway Interceptors
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Gateway Interceptors"
sidebar-title: "Gateway Interceptors"
description: "Extend OpenShell gateway operations with deployment-specific governance and business logic."
keywords: "Generative AI, Cybersecurity, AI Agents, Gateway Interceptors, Extensibility, Governance"
---
Gateway interceptors let operators add deployment-specific governance to OpenShell control-plane operations without modifying the gateway. An external gRPC service can modify or validate selected API writes before the gateway handles them, then observe successful responses after commit.
See the governance interceptor example for a complete service that vends provider profiles, applies a signed policy to new sandboxes, and rejects attempts to weaken that policy.
Choose Gateway Interceptors
Use a gateway interceptor when an external service needs to govern gateway API operations. For example, an interceptor can:
- Apply an approved policy to every new sandbox.
- Reject provider or policy changes that violate organizational rules.
- Enforce tenant quotas or naming conventions.
- Observe committed operations for an audit or inventory service.
- Vend an authoritative or composed provider profile catalog.
Gateway interceptors do not replace gateway persistence, authentication, authorization, policy safety checks, or driver validation. The gateway remains the system of record and validates an operation after interceptor modification.
How Interception Works
The gateway runs interceptors after authentication and before dispatching a request to its handler:
authenticate → decode and omit secrets → modify_operation → validate → gateway handler → post_commit
| Phase | Input | Capabilities |
| ------------------ | ------------------------------------------- | ------------------------------------------------ |
| modify_operation | Proposed request | Allow, deny, or return RFC 6902 JSON patches. |
| validate | Modified request and optional current state | Allow or deny. |
| post_commit | Successful gateway response | Observe the response and attach log annotations. |
Only explicitly allowlisted unary mutation RPCs are interceptable. The gateway converts an operation to its protobuf JSON representation before evaluation. It applies patches atomically, validates the result against the RPC's protobuf schema, and converts the operation back to protobuf before the handler receives it.
The gateway runs built-in operation and driver validation after modify_operation. A patched operation cannot bypass gateway-owned invariants.
post_commit is observational. It cannot deny or modify an operation that the gateway has already committed.
Implement an Interceptor Service
An interceptor implements the openshell.gateway_interceptor.v1.GatewayInterceptor gRPC service defined in proto/gateway_interceptor.proto:
- Describe declares the service's bindings and capabilities.
- Evaluate handles one selected operation phase.
- SnapshotProviderProfiles optionally returns a provider profile catalog.
Each InterceptorEvaluation identifies the configured interceptor, manifest binding, public OpenShell service and method, authenticated principal, and active phase. The phase determines whether the payload contains a proposed operation, optional current state, or committed response.
The interceptor returns an InterceptorResult with an allow or deny decision, an optional denial status and reason, JSON patches during modify_operation, and non-secret log annotations.
Declare and Authorize Bindings
The interceptor declares bindings in its InterceptorManifest returned by Describe. Each binding selects one public OpenShell RPC and one or more phases. The gateway validates these declarations at startup against its compiled protobuf descriptors and explicit interceptable RPC allowlist.
The operator chooses how the manifest and gateway configuration combine with binding_policy:
| Policy | Behavior |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| dynamic | Enables valid manifest bindings. Gateway configuration may narrow or disable them. This is the compatibility default and emits a startup warning. |
| allowlist | Enables only operator-configured RPCs and phases. Extra manifest bindings are ignored and logged. |
| exact | Requires the configured RPCs and phases to match the manifest exactly. |
Use allowlist or exact when interceptor authority is part of a security boundary. These modes select bindings by public RPC rather than manifest binding ID, so renaming a binding does not change its authority.
Register an Interceptor Service
Start the interceptor before the gateway, then register it in gateway TOML:
[[openshell.gateway.interceptors]]
name = "policy-governance"
grpc_endpoint = "https://governance.example:18081"
tls_ca_cert_path = "/etc/openshell/governance-ca.pem"
audience = "urn:example:governance"
order = 10
failure_policy = "fail_closed"
binding_policy = "allowlist"
timeout = "500ms"
max_response_bytes = 1048576
max_patches = 32[[openshell.gateway.interceptors.bindings]]
rpc = "openshell.v1.OpenShell/CreateSandbox"
phases = ["modify_operation", "validate"]
[[openshell.gateway.interceptors.bindings]]
rpc = "openshell.v1.OpenShell/UpdateConfig"
phases = ["validate"]
The gateway supports http://, https://, and unix:// interceptor endpoints. When gateway JWT signing is configured, authenticated network interceptors use https://; Unix sockets remain available for local integrations. HTTPS uses platform trust roots unless tls_ca_cert_path supplies a private CA, and normal hostname verification remains enabled. The gateway calls Describe and builds an immutable execution plan during startup. An unavailable service, invalid manifest, missing credential, or unauthorized configured binding prevents the gateway from starting.
The gateway attaches a short-lived EdDSA bearer token to Describe, Evaluate, and provider-profile snapshot calls. The token uses the configured audience (defaulting to urn:openshell:extension:interceptor:<name>) and caller_kind: gateway.
Return your expected audience in the expected_audience field of your Describe manifest. After authenticated Describe succeeds, the gateway compares the advertised value with its operator-configured audience and refuses to start when they differ. This is a post-authentication consistency assertion, not audience discovery: a strict verifier may reject an incorrect audience before returning the manifest, in which case startup reports an authentication failure. Leave the field empty to skip the consistency check.
Provision the trusted gateway URL, expected gateway ID, and public key or JWKS through the deployment. This operator-provisioned key material is the authoritative cold-start trust anchor. The expected issuer is exactly openshell-gateway:<gateway_id>; fetching JWKS does not establish that identity by itself. After initial trust is established, GET /.well-known/openid-configuration and its jwks_uri provide steady-state key refresh and operational convenience. The document is OIDC-shaped rather than OIDC-compliant because issuer is the gateway identity rather than the serving URL; compare iss against the configured value and fetch updates only over authenticated TLS at the trusted gateway URL. Pin alg to EdDSA, require typ to be exactly openshell-ext+jwt, and validate kid, signature, expected issuer, exact audience, positive expiry, and caller kind.
Set allow_insecure_transport = true on an interceptor to keep a plaintext http:// endpoint working with no credential attached. The gateway logs a warning naming the interceptor at every startup, and the service cannot distinguish the gateway from any other client that can reach it.
Registration is static. Restart the gateway after adding, removing, or changing an interceptor. See Gateway Configuration for the complete field reference.
Select RPCs and Phases
Start with the invariant the interceptor must preserve, then identify every gateway RPC that can establish or weaken it. For example, an interceptor that owns sandbox policy can use modify_operation on CreateSandbox to apply an approved initial policy and validate on UpdateConfig to reject unauthorized changes.
The gateway maintains the canonical interceptable route allowlist. Read the interceptor manifest and gateway startup diagnostics when selecting bindings. Unknown, streaming, read-only, and non-allowlisted RPCs cannot be intercepted.
Choose a phase by intent:
- Use modify_operation to apply defaults or controlled changes.
- Use validate to enforce a rule without changing the request.
- Use post_commit to notify or audit an external system after success.
Mutate Operations Safely
Operations and committed responses use protobuf JSON field names and shapes. Only modify_operation accepts RFC 6902 JSON patches.
The gateway applies all patches returned by one binding as an atomic candidate. It then encodes that candidate as the RPC's protobuf request type and decodes it back to canonical protobuf JSON. If any patch or the resulting operation is invalid, the gateway discards the complete candidate and applies the binding's failure policy. Later bindings see only schema-valid operations that the handler can receive.
Fields marked secret in the protobuf schema are recursively omitted from interceptor requests and committed responses. An interceptor cannot patch an omitted field, use one as a patch source, or replace a containing object. Gateway handlers retain the complete operation and continue to receive secret fields that were omitted from the interceptor view.
Configure Failure Behavior
Failure policy controls what happens when the gateway cannot obtain or apply a valid result. Failures include timeouts, transport errors, invalid responses, response-size violations, invalid phase behavior, and patch-limit violations.
| Policy | Behavior |
| ------------- | -------------------------------------------------------------------------------------------------- |
| fail_closed | Rejects the API operation before handler dispatch. |
| fail_open | Skips the failed result, continues with the previous valid operation, and emits warning telemetry. |
A valid deny result during modify_operation or validate always rejects the operation. It is not an interceptor failure and does not follow the failure policy.
Bindings that include post_commit must resolve to fail_open. The gateway rejects fail-closed post-commit configuration at startup because an observer cannot revoke an already committed response. A post-commit observation or evaluation failure is logged and counted without replacing the successful gateway response.
Use fail_open only when bypassing an unavailable or invalid interceptor preserves the intended governance boundary.
Vend Provider Profiles
An interceptor can advertise provider_profiles = true in its manifest and implement SnapshotProviderProfiles. The RPC returns a ProviderProfileSnapshot. Add that interceptor to provider_profile_sources to include its profiles in the gateway's effective catalog.
Select only the interceptor to make its catalog authoritative:
[openshell.gateway]
provider_profile_sources = [
{ type = "interceptor", name = "provider-governance" },
]Include { type = "builtin" } or { type = "user" } entries to compose interceptor profiles with the built-in or user-managed sources. Duplicate normalized profile IDs fail instead of overriding one source with another.
The gateway validates snapshot structure and provider profile semantics. It treats the configured interceptor as a trusted source and does not verify interceptor-defined signature, hash, or key annotations.
Operate and Observe Interceptors
Plan interceptor deployment around these boundaries:
- Start every configured service before the gateway.
- Restart the gateway after changing registrations or bindings.
- Keep fail-closed services available whenever the gateway accepts writes.
- Treat the endpoint and its operator configuration as part of the gateway trust boundary.
- Do not include secrets in denial reasons or log annotations.
The gateway emits structured evaluation logs containing the interceptor name, binding ID, RPC, phase, decision, patch count, and interceptor-provided log annotations. Metrics record evaluation decisions, latency, patch application, fail-open and fail-closed outcomes, and post-commit observation failures.
Current Limitations
- Only explicitly allowlisted unary write RPCs are interceptable. New gateway RPCs are non-interceptable until added to the allowlist.
- current_state is available only in the validate contract. The gateway does not yet populate it with method-specific state.
- Registration changes require a gateway restart.
- mTLS client authentication, service health checks, runtime registration, and overlapping signing-key rotation are not available.
- Extension tokens and sandbox-to-gateway tokens are signed by the same key, separated by audience and typ. The extension credential path cannot yet be rotated or revoked independently of sandbox admission.
- Interceptors cannot receive or mutate protobuf fields marked secret.
---
Extensibility/Supervisor Middleware
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Supervisor Middleware"
sidebar-title: "Supervisor Middleware"
description: "Configure and operate built-in and operator-run middleware for sandbox HTTP requests and WebSocket messages."
keywords: "Generative AI, Cybersecurity, AI Agents, Supervisor Middleware, Extensibility, Request Filtering"
---
Supervisor middleware adds ordered processing stages to allowed HTTP and WebSocket egress. Middleware runs after network and L7 policy admit traffic and before OpenShell injects provider credentials. A stage can allow or deny an HTTP request or client WebSocket text message, replace its payload, add approved HTTP headers, and report audit-safe findings.
Middleware selection is independent of the network policy rule that admitted the request. OpenShell matches middleware by destination host, so the same middleware applies consistently across broad, specific, user-authored, and provider-derived network policies.
Request Flow
For each inspected HTTP request, the supervisor:
1. Evaluates network and L7 policy.
2. Selects middleware whose host selectors match the admitted destination.
3. Buffers the request body using the largest body limit in the selected chain.
4. Runs matching middleware by ascending order. Policy validation rejects duplicate order values.
5. Re-checks body-aware protocol policy (GraphQL, JSON-RPC, MCP) after each stage that replaces the body. Every middleware receives a payload the policy admits, and a transformation cannot smuggle a denied or unparseable operation to a later stage or the upstream.
6. Applies allowed transformations, injects provider credentials, and forwards the request.
For an RFC 6455 upgrade over ws:// or wss://, the supervisor first finds every host-matched attachment, then selects only implementations that advertise WEBSOCKET_MESSAGE/PRE_CREDENTIALS. It opens one ordered, phase-specific EvaluateWebSocketSession stream per selected stage. OpenShell sends WebSocketSessionEvent values, while the service returns WebSocketSessionEventResult values only for preflight and message events; session start and end are notifications. Future upstream-to-client inspection uses the same RPC with PRE_RETURN; an implementation that advertises both phases receives two independent streams for the WebSocket session. An attachment without the selected binding can still inspect the HTTP upgrade request when it advertises the HTTP binding, but it is not a failed WebSocket stage. OpenShell allows post-upgrade traffic and emits an informational binding_not_selected coverage event for that attachment.
1. A preflight before the upgrade is sent upstream. The stage chooses INSPECT, voluntary SKIP, or authoritative DENY and may return a bounded diagnostic reason, stable reason code, findings, and metadata. OpenShell runs selected preflights concurrently; any DENY rejects the upgrade regardless of on_error.
2. A session-start event after the upstream accepts the upgrade, including the negotiated subprotocol.
3. Complete client-to-upstream text messages in sequence order. OpenShell reassembles fragmented messages and decompresses negotiated permessage-deflate messages before evaluation.
4. A best-effort session-end event when the stage stream remains writable. OpenShell attempts at most one terminal event for each opened stream, including streams opened during a preflight that rejects the upgrade before session start.
The protobuf represents each logical message with a text or binary payload variant. Text uses the protobuf string type, so invalid UTF-8 cannot enter the middleware contract. Results use an optional matching replacement variant: absence preserves the input, while presence represents a replacement even when its content is empty. OpenShell rejects attempts to change the message type. Allowed replacements are re-framed, re-compressed when required, and forwarded. Binary messages, control frames, and upstream-to-client traffic remain uninspected. Binary messages pass through under both on_error modes. For each active selected stage, OpenShell emits an informational unsupported_message_type coverage event and advances the session-global sequence; the next text message can therefore reach the stage with a valid sequence gap.
The network supervisor reserves process-wide assembly capacity before buffering every parsed WebSocket text message, even when no middleware is selected. At most 32 assemblies run while 64 additional callers wait without buffering payload bytes. When both bounds are full, OpenShell closes the WebSocket with code 1013 before reading the new message payload. A text message may contain at most 4,096 fragments, must make input progress within 30 seconds, and must finish assembly within 2 minutes. Forwarding the completed text frame must finish within another 2 minutes. The assembly budget lasts for the supervisor process lifetime, so policy reloads do not reset its capacity.
Active middleware sessions additionally reserve shared middleware capacity before buffering WebSocket text, and HTTP middleware reserves the same capacity before buffering request bodies; at most 32 evaluations run and 64 additional unbuffered callers wait for capacity. When both middleware bounds are full, OpenShell sheds an HTTP request with 503 Service Unavailable before reading its body. Persistent middleware streams use a separate process-wide budget of 32 sessions. WebSocket session admission does not wait: if the budget is full, OpenShell applies each selected config's on_error behavior before opening a stream.
Because each transformed body is re-checked before the next stage runs, a middleware hook always receives a request that satisfies the sandbox policy. A stage whose output the policy rejects stops the chain; under enforcement: audit the rejection is logged and the request proceeds.
If post-transformation policy evaluation itself fails, OpenShell denies the request and emits a high-severity detection finding. This failure is separate from middleware on_error because the middleware completed successfully; the sandbox policy could not validate its output.
Middleware receives the request before credential injection. Operator-run services cannot inspect OpenShell-managed credentials. Middleware-visible request headers are delivered in wire order and repeated header names are preserved as separate entries. OpenShell filters credential, routing, framing, and hop-by-hop headers before invoking middleware. It rejects malformed request headers and unsupported transfer-coding sequences before middleware or policy dispatch. Headers named by a request's Connection field are omitted from middleware input and removed before forwarding, except for the validated WebSocket upgrade pair.
Choose a Middleware Type
| Type | Registration | Payload limit | Deployment |
| --- | --- | --- | --- |
| Built-in | None | Defined by OpenShell | Runs inside the supervisor |
| Operator-run service | Required in gateway TOML | Set by the operator, up to the service capability | Runs as a separate service reachable by the gateway and supervisors |
openshell/regex is an example built-in middleware. It replaces only simple, self-contained token patterns in UTF-8 HTTP bodies and client WebSocket text messages; the initial pattern recognizes sk- tokens. It does not infer values from keyword assignments such as JSON password fields. This best-effort text transformation is not parser-aware and does not guarantee that it will detect or fully remove sensitive values. Its config accepts one field, mode: redact, which is also the default when the field is omitted. Unknown config fields and non-string values are rejected at policy validation. Custom expressions are not configurable yet.
Operator-run services expose bindings for supported operation and phase pairs. A binding is identified by its operation and phase. V1 supports HttpRequest/pre_credentials and WebSocketMessage/pre_credentials; a service may expose either or both. Policies attach the complete middleware by its operator-owned gateway registration name.
Register a Middleware Service
Start an operator-run service before starting the gateway, then add a registration to the local gateway TOML:
[[openshell.supervisor.middleware]]
name = "local-content-guard"
grpc_endpoint = "https://content-guard.example:50051"
tls_ca_cert_path = "/etc/openshell/content-guard-ca.pem"
audience = "urn:example:content-guard"
max_payload_bytes = 262144
timeout = "500ms"| Field | Description |
| --- | --- |
| name | Operator-owned registration name used by policy attachments and diagnostics. Names must be unique, and openshell/ is reserved for built-ins. |
| grpc_endpoint | Service address reachable from both the gateway and sandbox supervisors. Authenticated extensions use TLS https://. |
| tls_ca_cert_path | Optional PEM trust roots for a private HTTPS service. Custom roots replace platform roots and retain hostname verification. |
| audience | Exact audience expected by the service. Defaults to urn:openshell:extension:middleware:<name>. |
| allow_insecure_transport | Opt this registration out of extension authentication, permitting a plaintext http:// endpoint with no bearer credential. Defaults to false. Development and trusted-network deployments only. |
| max_payload_bytes | Shared operator limit applied to inspectable logical payloads across every binding exposed by the service, up to the 4 MiB platform maximum. It caps HTTP bodies and complete WebSocket text messages. |
| timeout | Optional service-wide RPC timeout using an integer with an ms or s suffix. Defaults to 500ms; valid values range from 10ms through 30s. |
Each binding returned by Describe may advertise a shorter timeout using the same syntax and bounds. The operator-configured service timeout is a ceiling: OpenShell uses the smaller of the binding and service values. An omitted binding timeout inherits the service setting, and an omitted service setting uses the 500 ms platform default. OpenShell rejects an invalid timeout before accepting the manifest. The operator-configured service timeout applies to Describe and ValidateConfig. The effective binding timeout applies only to EvaluateHttpRequest, WebSocket preflight, and each WebSocket message. WebSocket streams have no connection-wide deadline.
The gateway connects to every registered service and verifies its capabilities before accepting traffic. Gateway startup fails when a service is unavailable, reports an invalid capability, or exposes more than one binding for the same operation and phase. The manifest name is diagnostic metadata and does not need to match the operator registration name. Operator-run registration names cannot claim the reserved openshell/ namespace.
Registration is static. Restart the gateway after adding, removing, or changing a service. See Gateway Configuration for the complete gateway TOML context.
Authenticate OpenShell Callers
When gateway JWT signing is configured, OpenShell attaches a short-lived EdDSA bearer token to every remote middleware RPC. Gateway calls use caller_kind: gateway; sandbox supervisor calls use caller_kind: supervisor and include the sandbox ID. Supervisors request credentials by registration name through RefreshSandboxToken. The gateway derives the audience from operator-owned configuration and authorizes each name against the sandbox's effective policy.
Return your expected audience in the expected_audience field of your Describe manifest. After authenticated Describe succeeds, OpenShell compares the advertised value with its operator-configured audience and refuses to start when they differ. This is a post-authentication consistency assertion, not audience discovery: a strict verifier may reject an incorrect audience before returning the manifest, in which case startup reports an authentication failure. Leave the field empty to skip the consistency check.
Provision the trusted gateway URL, expected gateway ID, and public key or JWKS through the deployment. This operator-provisioned key material is the authoritative cold-start trust anchor. The expected issuer is exactly openshell-gateway:<gateway_id>; fetching JWKS does not establish that identity by itself. After initial trust is established, GET /.well-known/openid-configuration and its jwks_uri provide steady-state key refresh and operational convenience. The document is OIDC-shaped rather than OIDC-compliant: issuer is the gateway identity, not the URL serving the document, so compare iss against the configured value and fetch updates only over authenticated TLS at the trusted gateway URL.
Cache keys by kid. Validate, at minimum:
- typ is exactly openshell-ext+jwt. Extension tokens and sandbox-to-gateway bootstrap tokens share a signing key and differ only in audience; this header is a second, independent discriminator.
- alg is pinned to EdDSA. Never select the algorithm from the token.
- Signature, expected issuer, exact audience, and positive expiry.
- caller_kind, and the sandbox identity when your service scopes behavior per sandbox.
A sandbox-to-gateway JWT is not an extension credential even though both token types use the same signing key.
Each token carries a unique jti that identifies that token instance for correlation and future explicit revocation. OpenShell reuses a token across calls until rotation and does not track jti, so rejecting a repeated jti would reject legitimate requests. Per-request replay resistance requires a request nonce or signature, channel binding, or another proof-of-possession mechanism.
Run Without Extension Authentication
Set allow_insecure_transport = true on a registration to keep a plaintext http:// endpoint working. OpenShell then attaches no credential to that service, supervisors do not request one, and the gateway refuses to mint one if asked. The gateway logs a warning naming the registration at every startup.
The service cannot distinguish OpenShell from any other client that can reach it. Use this only where the network already provides that guarantee, and prefer https:// everywhere else.
Apply Middleware with Policy
Add middleware configs to the top-level network_middlewares map. Each key is the policy-local config name:
network_middlewares:
regex-redactor:
name: Redact API tokens
middleware: openshell/regex
order: 10
config:
mode: redact
on_error: fail_closed
endpoints:
include: ["*.example.com"]
exclude: ["trusted.example.com"]Each config has a stable policy-local identity from its map key, an optional human-readable name that defaults to that key, a built-in or operator-owned registration name in middleware, an integer order, implementation-owned config, failure behavior, and host selectors. The optional name does not replace the map key for attachment or future keyed updates. A policy accepts at most 10 middleware configs.
include selects destination hosts. exclude takes precedence and removes hosts from that selection. Each config accepts at most 32 combined include and exclude patterns. Matching is case-insensitive and uses the same exact-host and DNS glob behavior as network policy endpoints: matches exactly one DNS label, matches one or more labels, and intra-label patterns like -api.example.com work. Brace alternates such as {prod,staging} are rejected at validation; list each host pattern separately.
Matching configs run once each by ascending order; lower values run first. Order values must be unique across the complete policy, even when endpoint selectors do not overlap. The default order is 0, so policies with multiple configs normally set explicit values. Different map keys may attach the same middleware and run as separate stages. Map keys are structurally unique. Runtime selection defensively rejects chains with more than 10 stages.
See Policy Schema for the complete field reference.
Configure Failure Behavior
on_error controls what happens after an operation binding is selected and middleware is unavailable, rejects its configuration, returns an invalid result, or exceeds the selected binding's payload limit. It does not turn an unadvertised operation or an unsupported WebSocket message class into a middleware failure.
| Value | Behavior |
| --- | --- |
| fail_closed | Denies the HTTP request or closes the WebSocket when the stage fails. This is the default. |
| fail_open | Skips the failed HTTP stage. For a broken WebSocket stage stream, disables that stage for the rest of the connection and continues the remaining chain. |
Use fail_open only when bypassing the middleware preserves the intended security policy. OpenShell emits a detection finding when a failed stage is bypassed and a separate state-change finding when a WebSocket stage is disabled for the session.
Capability coverage is separate from failure handling. A host-matched HTTP-only attachment does not join the WebSocket chain, regardless of on_error. Binary messages are outside the V1 text-message binding and pass through even when a selected stage is fail_closed. OpenShell records both states as informational coverage events so operators do not mistake pass-through traffic for inspected traffic. If a deployment requires all WebSocket message classes to be inspected, V1 cannot express that requirement.
An explicit deny decision always stops the chain and denies the request or WebSocket upgrade, regardless of on_error. A WebSocket preflight DENY is a successful policy decision, not a middleware failure; OpenShell rejects the upgrade before upstream contact and ends each still-writable stream opened by a successful preflight decision with MIDDLEWARE_DENIAL. The HTTP response uses error: middleware_denied, identifies the policy-local middleware config, and omits policy-advisor remediation because the network and L7 allow rules already matched. OpenShell never copies the free-form middleware reason into the response or security logs. HTTP results, WebSocket preflight decisions, and WebSocket message results can instead return an optional stable reason_code: 1–64 bytes, starting with a lowercase ASCII letter and containing only lowercase ASCII letters, digits, and underscores. Invalid codes make the result a middleware failure governed by on_error. Preflight findings and metadata use the same bounds and audit-safe handling as message results.
{
"error": "middleware_denied",
"detail": "Request rejected by configured middleware",
"policy": "api-policy",
"middleware": "prototype-content-guard",
"reason_code": "content_match"
}A failed fail_closed stage uses error: middleware_failed and a platform-owned detail. It also omits rule_missing, next_steps, and agent_guidance: the failure did not result from a missing network or L7 policy rule, and changing policy cannot repair it. Runtime diagnostic text is available only through sanitized operator telemetry.
Middleware decisions are enforced regardless of the endpoint's enforcement mode. enforcement: audit applies to an endpoint's network and L7 policy rules and does not bypass middleware: a middleware deny, or a failed fail_closed stage, blocks the request even on an audit endpoint. A middleware service that needs to observe traffic without blocking should return an allow decision with findings, which OpenShell emits as detection findings.
Set Payload Limits
Every middleware binding declares the largest logical payload or replacement it supports through max_payload_bytes. For HTTP_REQUEST, that payload is one request body. For WEBSOCKET_MESSAGE, it is one complete message rather than the whole session.
- Built-in middleware uses its OpenShell-defined limit.
- Each operator-run registration sets one max_payload_bytes ceiling no higher than any binding's advertised max_payload_bytes capability.
- A selected chain buffers using its largest stage limit, so every stage that can process the body receives it.
- The same per-stage limit applies to request bodies and replacement bodies.
The gateway rejects a registration whose operator limit exceeds the service capability or the 4 MiB platform maximum instead of silently clamping it. OpenShell also bounds the non-payload protobuf components: 64 KiB for service config, 4 KiB for request context, 32 KiB for the target, and 128 request header lines totaling at most 64 KiB encoded. Results allow a 4 KiB discarded free-form reason, a 64-byte validated reason code, 64 header mutations totaling at most 64 KiB encoded, 32 findings of at most 4 KiB encoded each, and 64 metadata entries totaling at most 32 KiB. Middleware gRPC servers should configure request and response message limits to at least 4 MiB plus 293 KiB so every platform-valid envelope fits.
At request time, exceeding a selected stage's limit is a middleware failure for that stage alone and follows that config's on_error behavior; other stages in the chain still run against their own limits. OpenShell can apply fail_open to an oversized Content-Length before consuming body bytes. A chunked body can cross the limit only after bytes have been consumed, so OpenShell denies that request because it cannot safely resume the original stream.
For a WebSocket binding, max_payload_bytes covers complete client text messages and replacements. Exceeding a selected stage's effective text-message limit follows that stage's on_error. The 4 MiB parsed-text platform cap and other protocol-safety limits are independent of middleware failure policy. Binary messages are not delivered to middleware, so the operator ceiling does not become a binary relay limit; individual raw binary frames retain the 16 MiB relay-safety bound. Oversized parsed text closes the connection with code 1009; invalid UTF-8 uses 1007; protocol errors use 1002; middleware or policy denials use 1008; and policy reload uses 1012.
Mutate Request Headers
A middleware result can return ordered header mutations before OpenShell injects credentials. A write mutation adds a value when the case-insensitive header name is absent and selects one behavior when it is already present:
- append adds another field value.
- overwrite removes every existing value before adding the new value.
- skip leaves existing values unchanged.
A remove mutation removes every value for a case-insensitive header name. OpenShell applies each successful stage's mutations before invoking the next middleware, so later stages observe the accumulated header state.
Header writes must use the x-openshell-middleware- prefix. Removes may target other middleware-visible request headers. Protected credential, routing, framing, and hop-by-hop headers are always rejected. Header values must not contain control characters.
OpenShell validates and applies each stage's mutations atomically. An invalid operation discards every mutation from that stage and follows its on_error behavior. Built-in failures can name the offending header. Operator-run failures use a platform-owned error code so request-derived header text cannot reach logs or denied responses.
Operate Middleware Services
Plan startup and updates around these boundaries:
- Start registered services before the gateway. The gateway validates every registration during startup.
- Keep service endpoints reachable from both the gateway and sandbox supervisors. The supervisors call operator-run services directly on the request path.
- Restart the gateway after changing registrations.
- Keep required services available before creating or updating policies. The gateway validates implementation-owned config before persisting a policy.
- Treat fail_open as an explicit availability-over-enforcement decision.
When the effective sandbox configuration changes, a running supervisor validates the new service registry before installing it. If the reload fails, the supervisor keeps its last-known-good registry and emits a configuration failure event.
Observe Middleware
Middleware activity is emitted through OpenShell's OCSF logging:
- Each invocation records its policy-local config name, attached middleware name, decision, transformation state, and failure state.
- A denied invocation records a platform-owned reason derived from the policy-local config name and optional validated reason code. OpenShell does not record service-provided free-form reason text.
- A bypass under fail_open emits a detection finding.
- A required stage that fails closed emits a high-severity detection finding.
- A host-matched attachment without a WebSocket binding emits an informational binding_not_selected coverage event.
- A binary message encountered by an active WebSocket stage emits an informational unsupported_message_type coverage event with message type, sequence, and byte count. It is not reported as an invocation or failure.
- Built-in findings include their type, label, and aggregate count. Operator-run findings use the operator-owned registration name and a platform label plus the aggregate count; OpenShell does not log service-provided finding text or diagnostic metadata. A stage can return at most 32 findings. Exceeding the per-stage cap is an invalid response handled through on_error. A maximum 10-stage chain retains and emits up to 320 findings without silently dropping findings from later stages.
- Registry reload success and failure are emitted as configuration state changes.
See Logging for log access and OCSF JSON Export for structured export.
Current Limitations
- Middleware applies only through operation bindings advertised by each implementation. For protocols that have no supported middleware operation at all, such as HTTP/2 prior knowledge or non-HTTP TCP, the existing uninspectable-traffic gate denies a host match containing fail_closed and relays an all-fail_open match with a detection finding.
- The typed operation and phase pairs are HTTP_REQUEST/PRE_CREDENTIALS and WEBSOCKET_MESSAGE/PRE_CREDENTIALS.
- A host match does not imply every advertised operation: an HTTP-only attachment can inspect the upgrade GET, then post-upgrade traffic passes with binding_not_selected coverage.
- The V1 WebSocket binding inspects complete client text messages only. Binary messages pass with unsupported_message_type coverage for active stages; control frames and upstream-to-client messages remain outside the middleware operation.
- Selection uses destination host include and exclude patterns.
- A fail-closed middleware cannot cover tls: skip endpoints because OpenShell cannot inspect that traffic. An all-fail_open match may cover the endpoint; OpenShell bypasses the middleware and emits a detection finding.
- Operator-run services use TLS https:// when gateway JWT signing is enabled, unless the registration sets allow_insecure_transport. Certificates must chain to the configured custom CA or platform roots, and the endpoint hostname must match.
- Extension tokens and sandbox-to-gateway tokens are signed by the same key. They are separated by audience and by typ, but the extension credential path cannot yet be rotated or revoked independently of sandbox admission.
- OpenShell does not track or revoke jti; bearer tokens can be replayed until expiry. Per-request replay resistance requires proof of possession or request binding.
- mTLS client authentication, health checks, runtime registration, and overlapping signing-key rotation are not available.
---
Get Started/Tutorials/Docker Compose
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Run the Gateway with Docker Compose"
sidebar-title: "Docker Compose Setup"
slug: "get-started/tutorials/docker-compose"
description: "Run the OpenShell gateway as a Docker Compose service and create agent sandboxes."
keywords: "Generative AI, Docker Compose, Gateway, Sandbox, OpenClaw, Docker, Installation"
---
This tutorial shows how to run the OpenShell gateway as a Docker Compose service on a Linux host or on a machine running Docker Desktop (Windows or macOS).
After completing this tutorial you have:
- An OpenShell gateway running as a Compose service.
- The openshell CLI registered against that gateway.
- An AI provider configured with your API key.
- A running agent sandbox.
Prerequisites
- Docker Desktop (Windows or macOS) or Docker Engine with the Compose plugin (Linux).
- The openshell CLI installed on your workstation. See Install the CLI below.
- Port 8080 available on the host.
Compose files
The Compose configuration lives at deploy/docker/ in the repository.
| File | Purpose |
|---|---|
| docker-compose.yml | Gateway service, volumes, and environment variables |
| gateway.toml | TOML reference for release builds with config-file support |
Port note
The Docker compute driver injects host.openshell.internal:<gateway-port> into every sandbox container as its callback address. The gateway listens on port 8080 inside the container, so port 8080 must be published at the same number on the Docker host. Publishing it as a different host port (for example 18080:8080) causes sandbox containers to call back to the wrong port and remain stuck in the Provisioning phase.
If port 8080 is taken, change OPENSHELL_SERVER_PORT and update the port mapping to <your-port>:8080, then set OPENSHELL_PORT=<your-port> in an .env file.
Data directory
The gateway extracts the openshell-sandbox supervisor binary from ghcr.io/nvidia/openshell/supervisor:latest on first start and caches it at:
/var/lib/openshell/openshell/docker-supervisor/<digest>/openshell-sandboxThis path is used as a bind-mount source when Docker creates sandbox containers.
Docker resolves bind-mount sources against the host filesystem, not the container filesystem, so the data directory must be bind-mounted at the same absolute path in both the host and the container.
The Compose file uses /var/lib/openshell for this purpose and sets create_host_path: true so Docker creates it on first run.
Start the gateway
cd deploy/docker
docker compose up -dVerify the gateway is healthy:
curl -sf http://localhost:8080/healthzInstall the CLI
Binary (recommended — macOS / Linux / WSL):
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | shFrom PyPI (any platform with uv):
uv tool install -U openshell<Note>
On Windows without WSL, install the CLI inside a WSL 2 distribution (for example AlmaLinux or Ubuntu) and run all openshell commands from that distribution.
</Note>
Register the gateway
Run this once after the gateway starts:
openshell gateway add http://localhost:8080 --name openshell-dockerVerify the connection:
openshell statusThe output should show Status: Connected.
Configure an AI provider
Set your API key as an environment variable and create a provider:
<Tabs>
<Tab title="Anthropic (Claude)">
ANTHROPIC_API_KEY=sk-ant-... \
openshell provider create --name anthropic --type anthropic --from-existing</Tab>
<Tab title="OpenAI">
OPENAI_API_KEY=sk-... \
openshell provider create --name openai --type openai --from-existing</Tab>
</Tabs>
Confirm the provider was stored:
openshell provider listPre-pull sandbox images (optional)
Sandbox images are pulled automatically on first use, but the initial pull can take several minutes for large images. Pre-pull to avoid long waits at sandbox creation time:
Base image — includes Claude Code, OpenCode, Codex, and Copilot
docker pull ghcr.io/nvidia/openshell-community/sandboxes/base:latestCreate a sandbox
<Tabs>
<Tab title="OpenClaw">
OpenClaw runs inside OpenShell through NemoClaw, which manages the sandbox image, inference routing, and security policies.
Follow the NemoClaw Quickstart to set up an OpenClaw sandbox with managed inference.
</Tab>
<Tab title="Claude Code">
openshell sandbox create -- claude</Tab>
<Tab title="OpenCode">
openshell sandbox create -- opencode</Tab>
</Tabs>
Wait for the phase to change from Provisioning to Ready:
openshell sandbox listThen connect:
openshell sandbox connect <sandbox-name>Manage the gateway
| Command | Purpose |
|---|---|
| docker compose up -d | Start or restart the gateway |
| docker compose down | Stop the gateway and remove the container |
| docker compose logs -f | Tail gateway logs |
| docker compose pull | Pull a new gateway image version |
Linux notes
On Linux, host.docker.internal and host.openshell.internal are not automatically resolvable from containers. Add the following under the gateway service in docker-compose.yml:
extra_hosts:
- "host.docker.internal:host-gateway"
- "host.openshell.internal:host-gateway"Next steps
- First Network Policy — apply L7 policies to your sandbox.
- GitHub Push Access — grant a sandbox scoped GitHub access.
---
Get Started/Tutorials/First Network Policy
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Write Your First Sandbox Network Policy"
sidebar-title: "First Network Policy"
slug: "get-started/tutorials/first-network-policy"
description: "Learn how OpenShell network policies work by creating a sandbox, observing default-deny in action, and applying a fine-grained L7 read-only rule."
keywords: "Generative AI, Cybersecurity, Tutorial, Policy, Network Policy, Sandbox, Security"
---
This tutorial shows how OpenShell's network policy system works in under five minutes. You create a sandbox, watch a request get blocked by the default-deny policy, apply a fine-grained L7 rule, and verify that reads are allowed while writes are blocked, all without restarting anything.
After completing this tutorial, you understand:
- How default-deny networking blocks all outbound traffic from a sandbox.
- How to apply a network policy that grants read-only access to a specific API.
- How L7 enforcement distinguishes between HTTP methods such as GET and POST on the same endpoint.
- How to inspect deny logs for a complete audit trail.
Prerequisites
- A working OpenShell installation. Complete the Quickstart before proceeding.
- Docker Desktop running on your machine.
<Tip>
To run every step of this tutorial, you can also use the automated demo script at the examples/sandbox-policy-quickstart directory in the NVIDIA OpenShell repository. It runs the full walkthrough in under a minute but without any user interaction.
bash examples/sandbox-policy-quickstart/demo.sh</Tip>
<Steps toc={true}>
Create a Sandbox
Start by creating a sandbox with no network policies. This gives you a clean environment to observe default-deny behavior.
openshell sandbox create --name demo --no-auto-providers--no-auto-providers skips the provider setup prompt since this tutorial uses curl instead of an AI agent.
You land in an interactive shell inside the sandbox:
sandbox@demo:~$Try to Reach the GitHub API
With no network policy in place, every outbound connection is blocked. Test this by making a simple API call from inside the sandbox:
curl -s https://api.github.com/zenhttps://api.github.com/zen is a lightweight, unauthenticated GitHub REST endpoint that returns a random aphorism on each call. It requires no tokens or parameters, which makes it a convenient smoke-test target for verifying outbound HTTPS connectivity.
The request fails. By default, all outbound network traffic is denied. The sandbox proxy intercepted the HTTPS CONNECT request to api.github.com:443 and rejected it because no network policy authorizes curl to reach that host.
curl: (56) Received HTTP code 403 from proxy after CONNECTExit the sandbox. Sandboxes are kept running by default, so you can reconnect later. Use --no-keep at creation time if you want the sandbox deleted after exit:
exitCheck the Deny Log
Every denied connection produces a structured log entry. Query the sandbox logs from your host to confirm the denial and inspect the reason.
openshell logs demo --since 5mYou see a line like:
action=deny dst_host=api.github.com dst_port=443 binary=/usr/bin/curl deny_reason="no matching network policy"Every denied connection is logged with the destination, the binary that attempted it, and the reason. Nothing gets out silently.
Apply a Read-Only GitHub API Policy
To allow the sandbox to reach the GitHub API, define a network policy that grants read-only access. The policy specifies which host, port, binary, and HTTP methods are permitted. Create a file called github_readonly.yaml with the following content:
version: 1filesystem_policy:
include_workdir: true
read_only: [/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log]
read_write: [/tmp, /dev/null]
landlock:
compatibility: best_effort
network_policies:
github_api:
name: github-api-readonly
endpoints:
- host: api.github.com
port: 443
protocol: rest
enforcement: enforce
access: read-only
binaries:
- { path: /usr/bin/curl }
The filesystem_policy and landlock sections preserve the default sandbox settings, while process identity is omitted so the active compute driver can select it. These sections are required because policy set replaces the entire policy. The network_policies section is the key part: curl can make GET, HEAD, and OPTIONS requests to api.github.com over HTTPS. Everything else is denied. The proxy auto-detects TLS on HTTPS endpoints and terminates it to inspect each HTTP request and enforce the read-only access preset at the method level.
Apply it:
openshell policy set demo --policy github_readonly.yaml --wait--wait blocks until the sandbox confirms the new policy is loaded. No restart required. Policies are hot-reloaded.
<Tip>
This tutorial uses curl and read-only access to keep things simple. When building policies for real workloads:
- To scope the policy to an agent, replace the binaries section with your agent's binary, such as /usr/local/bin/claude, instead of curl.
- To grant write access, change access: read-only to read-write or add explicit rules for specific paths. Refer to the Policy Schema.
- To allow additional endpoints, stack multiple policies in the same file for PyPI, npm, or your internal APIs. Refer to Policies for examples.
</Tip>
Verify If GET Requests Are Allowed
The policy is now active. Reconnect to the sandbox and retry the same request to confirm that read access works.
openshell sandbox connect demoRetry the same request:
curl -s https://api.github.com/zenAnything added dilutes everything else.It works. The read-only preset allows GET requests through.
Try a Write
The read-only preset allows GET but blocks mutating methods like POST, PUT, and DELETE. Test this by sending a POST request to the GitHub API while still inside the sandbox:
curl -s -X POST https://api.github.com/repos/octocat/hello-world/issues \
-H "Content-Type: application/json" \
-d '{"title":"oops"}'{"error":"policy_denied","policy":"github-api-readonly","detail":"POST /repos/octocat/hello-world/issues not permitted by policy"}The CONNECT request succeeded because api.github.com is allowed, but the L7 proxy inspected the HTTP method and returned 403. POST is not in the read-only preset. An agent with this policy can read code from GitHub but cannot create issues, push commits, or modify anything.
Exit the sandbox:
exitCheck the L7 Deny Log
L7 denials are logged separately from connection-level denials. The log entry includes the exact HTTP method and path that the proxy rejected.
openshell logs demo --level warn --since 5ml7_decision=deny dst_host=api.github.com l7_action=POST l7_target=/repos/octocat/hello-world/issues l7_deny_reason="POST /repos/octocat/hello-world/issues not permitted by policy"The log captures the exact HTTP method, path, and deny reason. In production, pipe these logs to your SIEM for a complete audit trail of every request your agent makes.
<Tip>
To log violations without blocking requests, set enforcement: audit instead of enforcement: enforce in the policy. This is useful for building a policy iteratively: deploy in audit mode, review the logs, and switch to enforce when the rules are correct.
</Tip>
Clean Up
Delete the sandbox to free resources. This stops all processes and purges any injected credentials.
openshell sandbox delete demo<Tip>
To run this entire walkthrough non-interactively, use the automated demo script:
bash examples/sandbox-policy-quickstart/demo.sh</Tip>
</Steps>
Next Steps
- To walk through a full policy iteration with Claude Code, including diagnosing denials and applying fixes from outside the sandbox, refer to GitHub Sandbox.
---
Get Started/Tutorials/Github Sandbox
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Grant GitHub Push Access to a Sandboxed Agent"
sidebar-title: "GitHub Push Access"
slug: "get-started/tutorials/github-sandbox"
description: "Learn the iterative policy workflow by launching a sandbox, diagnosing a GitHub access denial, and applying a custom policy to fix it."
keywords: "Generative AI, Cybersecurity, Tutorial, GitHub, Sandbox, Policy, Claude Code"
---
This tutorial walks through an iterative sandbox policy workflow. You launch a sandbox, ask Claude Code to push code to GitHub, and observe the default network policy denying the request.
You then diagnose the denial from your machine and from inside the sandbox, apply a policy update, and verify that the policy update to the sandbox takes effect.
After completing this tutorial, you have:
- A running sandbox with Claude Code that can push to a GitHub repository.
- A custom network policy that grants GitHub access for a specific repository.
- Experience with the policy iteration workflow: fail, diagnose, update, verify.
<Note>
This tutorial shows example prompts and responses from Claude Code. The exact wording you see might vary between sessions. Use the examples as a guide for the type of interaction, not as expected output.
</Note>
Prerequisites
This tutorial requires the following:
- A working OpenShell installation. Complete the Quickstart before proceeding.
- A GitHub personal access token (PAT) with repo scope. Generate one from the GitHub personal access token settings page by selecting Generate new token (classic) and enabling the repo scope.
- An Anthropic account with access to Claude Code. OpenShell provides the sandbox runtime, not the agent. You must authenticate with your own account.
- A GitHub repository you own to use as the push target. A scratch repository is sufficient. You can create one with a README if needed.
This tutorial uses two terminals to demonstrate the iterative policy workflow:
- Terminal 1: The sandbox terminal. You create the sandbox in this terminal by running openshell sandbox create and interact with Claude Code inside it.
- Terminal 2: A terminal outside the sandbox on your machine. You use this terminal for viewing the sandbox logs with openshell term and applying an updated policy with openshell policy set.
Each section below indicates which terminal to use.
<Steps toc={true}>
Set Up a Sandbox with Your GitHub Token
Depending on whether you start a new sandbox or use an existing sandbox, choose the appropriate tab and follow the instructions.
<Tabs>
<Tab title="Starting a new sandbox">
In terminal 2, create a new sandbox with Claude Code. The default policy is applied automatically, which allows read-only access to GitHub.
Create a credential provider that injects your GitHub token into the sandbox automatically. The provider reads GITHUB_TOKEN from your host environment and sets it as an environment variable inside the sandbox:
GITHUB_TOKEN=<your-token>
openshell provider create --name my-github --type github --from-existing
openshell sandbox create --provider my-github -- claudeopenshell sandbox create keeps the sandbox running after Claude Code exits, so you can apply policy updates later without recreating the environment. Add --no-keep if you want the sandbox deleted automatically instead.
Claude Code starts inside the sandbox. It prints an authentication link. Open it in your browser, sign in to your Anthropic account, and return to the terminal. When prompted, trust the /sandbox workspace to allow Claude Code to read and write files.
</Tab>
<Tab title="Using an existing sandbox">
In terminal 1, connect to a sandbox that is already running and set your GitHub token as an environment variable:
openshell sandbox connect <sandbox-name>
export GITHUB_TOKEN=<your-token>To find the name of running sandboxes, run openshell sandbox list in terminal 2.
</Tab>
</Tabs>
Push Code to GitHub
In terminal 1, ask Claude Code to write a simple script and push it to your repository. Replace <org> with your GitHub organization or username and <repo> with your repository name.
`` The push fails. Claude reports an error, but the failure is not an authentication problem. The default sandbox policy permits read-only access to GitHub and blocks write operations, so the proxy denies the push before the request reaches the GitHub server. In this section, you diagnose the denial from your machine and from inside the sandbox. In terminal 2, launch the OpenShell terminal: In terminal 1, ask Claude Code to check the sandbox logs for denied requests: <Accordion title="Response" defaultOpen={true}> Both perspectives confirm the same thing: the proxy is doing its job. The default policy is designed to be restrictive. To allow GitHub pushes, you need to update the network policy. Copy the deny reason from Claude's response. You paste it into an agent running on your machine in the next step. In terminal 2, paste the deny reason from the previous step into your coding agent on your machine, such as Claude Code or Cursor, and ask it to recommend a policy update. The deny reason gives the agent the context it needs to generate the correct policy rules. After pasting the following prompt sample, properly provide the GitHub organization and repository names of the repository you are pushing to.md title="Prompt" wordWrap showLineNumbers={false}hello_world.py
Write a script and push it to https://github.com/<org>/<repo>.https://github.com/<org>/<repo>Claude recognizes that it needs GitHub credentials. It asks how you want to authenticate. Provide your GitHub personal access token by pasting it into the conversation. Claude configures authentication and attempts the push.Diagnose the Denial
View the Logs from Your Machine
openshell termThe dashboard shows sandbox status and a live stream of policy decisions. Look for entries with l7_decision=deny. Select a deny entry to see the full detail:
l7_action: PUT
l7_target: /repos/<org>/<repo>/contents/hello_world.py
l7_decision: deny
dst_host: api.github.com
dst_port: 443
l7_protocol: rest
policy: github_rest_api
l7_deny_reason: PUT /repos/<org>/<repo>/contents/hello_world.py not permitted by policyThe log shows that the sandbox proxy intercepted an outbound PUT request to api.github.com and denied it. The github_rest_api policy allows read operations (GET) but blocks write operations (PUT, POST, DELETE) to the GitHub API. A similar denial appears for github.com if Claude attempted a git push over HTTPS.Ask Claude Code to Check the Sandbox Logs
Check the sandbox logs for any denied network requests. What is blocking the push?Claude reads the deny entries and identifies the root cause. It explains that the failure is a sandbox network policy restriction, not a token permissions issue. For example, the following is a possible response:github_rest_api
The sandbox runs a proxy that enforces policies on outbound traffic.
The policy allows GET requests (used to read the file)
but blocks PUT/write requests to GitHub. This is a sandbox-level restriction,
not a token issue. No matter what token you provide, pushes through the API
are blocked until you update the policy.
</Accordion>Update the Policy from Your Machine
Based on the following deny reasons, recommend a sandbox policy update that allows GitHub pushes to , and save to /tmp/sandbox-policy-update.yaml:
The filesystem_policy 1. Inspects the deny reasons. Refer to the following policy example to compare with the generated policy before applying it. Confirm that the policy grants only the access you expect. In this case, <Accordion title="Full reference policy"> The following YAML shows a complete policy that extends the default policy with GitHub access for a single repository. Replace The | Block | Endpoint | Behavior | The remaining blocks ( For details on policy block structure, refer to Policies. After you have reviewed the generated policy, apply it to the running sandbox: In terminal 1, ask Claude Code to retry the push: When you are finished, delete the sandbox to free gateway compute resources: The following resources cover related topics in greater depth: - To add per-repository access levels (read-write vs read-only) or restrict to specific API methods, refer to the Policy Schema Reference. --- --- Hands-on walkthroughs that teach OpenShell concepts by building real configurations. Each tutorial builds on the previous one, starting with core sandbox mechanics and progressing to production workflows. <Cards> <Card title="First Network Policy" href="/get-started/tutorials/first-network-policy"> Create a sandbox, observe default-deny networking, apply a read-only L7 policy, and inspect audit logs. No AI agent required. <Card title="GitHub Push Access" href="/get-started/tutorials/github-sandbox"> Launch Claude Code in a sandbox, diagnose a policy denial, and iterate on a custom GitHub policy from outside the sandbox. <Card title="Microsoft Graph Provider Refresh" href="/get-started/tutorials/microsoft-graph-provider-refresh"> Configure a Providers v2 Microsoft Graph provider with gateway-managed OAuth2 refresh-token rotation. <Card title="Inference with Ollama" href="/get-started/tutorials/inference-ollama"> Route inference through Ollama using cloud-hosted or local models, and verify it from a sandbox. <Card title="Local Inference with LM Studio" href="/get-started/tutorials/local-inference-lmstudio"> Route inference to a local LM Studio server using the OpenAI-compatible or Anthropic-compatible APIs. <Card title="Docker Compose Setup" href="/get-started/tutorials/docker-compose"> Run the OpenShell gateway as a Docker Compose service and create agent sandboxes including OpenClaw. </Cards> --- --- This tutorial covers two ways of running Ollama with OpenShell: 1. Ollama sandbox. This is the recommended way to run Ollama. A self-contained sandbox with Ollama, Claude Code, and Codex pre-installed. One command starts it. After completing this tutorial, you know how to: - Launch the Ollama community sandbox for a batteries-included experience. - A working OpenShell installation. Complete the Quickstart before proceeding. The Ollama community sandbox bundles Ollama, Claude Code, OpenCode, and Codex into a single image. Ollama starts automatically when the sandbox launches. <Steps toc={true}> Chat with a local model | Use case | Model | Notes | <Note> Agentic workflows (Claude Code, Codex, OpenCode) rely on tool calling. The following models have reliable tool calling support: Qwen 3.5, Nemotron-3-Super, GLM-5, and Kimi-K2.5. Check the Ollama model library for the latest models. To update Ollama inside a running sandbox: Use this approach when you want a single Ollama instance on the gateway host, shared across multiple sandboxes through <Note> </Note> <Steps toc={true}> Install Ollama on the gateway host: In a second terminal, pull a model: Create an OpenAI-compatible provider pointing at the host Ollama: </Steps> Common issues and fixes: - Ollama not reachable from sandbox: Ollama must be bound to Useful commands: - To learn more about managed inference, refer to Inference Routing. --- --- This tutorial describes how to configure OpenShell to route inference requests to a local LM Studio server. <Note> </Note> This tutorial covers: - Expose a local inference server to OpenShell sandboxes. First, complete OpenShell installation and follow the Quickstart. Install the LM Studio app. Make sure that your LM Studio is running in the same environment as your gateway. If you prefer to work without having to keep the LM Studio app open, download llmster (headless LM Studio) with the following command: <Tabs> <Tab title="Windows"> And start llmster: Start the LM Studio local server from the Developer tab, and verify the OpenAI-compatible endpoint is enabled. LM Studio listens to If you use the GUI, go to the Developer Tab, select Server Settings, then enable Serve on Local Network. If you use llmster in headless mode, run In the LM Studio app, head to the Model Search tab to download a small model like Qwen3.5 2B. In the terminal, use the following command to download and load the model: Choose the provider type that matches the client protocol you want to route through <Tabs> Add LM Studio as an OpenAI-compatible provider through </Tab> <Tab title="Anthropic-compatible"> Add a provider that points to LM Studio's Anthropic-compatible </Tab> </Tabs> Set the managed inference route for the active gateway: <div className="boxed-tabs"> <Tabs> </Tab> <Tab title="Anthropic-compatible"> </Tab> </Tabs> </div> The active Confirm the saved config: Run a simple request through <Tabs> <Tab title="Anthropic-compatible"> </Tabs> </Steps> If setup fails, check these first: - LM Studio local server is running and reachable from the gateway host Useful commands: - To learn more about using the LM Studio CLI, refer to LM Studio docs --- --- Use Providers v2 to keep Microsoft Graph access tokens short lived while sandboxes receive a stable After completing this tutorial, you have: - A custom Microsoft Graph mail provider profile. <Note> - A working OpenShell installation with an active gateway. Complete the Quickstart before proceeding. | Variable | Value | <Warning> <Steps toc={true}> Enable provider profile policy composition on the active gateway: Create and landlock sections are static. They are read once at sandbox creation and cannot be changed by a hot reload. They are included here for completeness so the file is self-contained. Process identity is omitted so the active compute driver can select it, and only the network_policies section takes effect when you apply this to a running sandbox.The following steps outline the expected process done by the agent:github_git
2. Writes an updated policy that adds and github_api blocks that grant write access to your repository./tmp/sandbox-policy-update.yaml
3. Saves the policy to .git pushReview the Generated Policy
operations and GitHub REST API access scoped to a single repository.<org> with your GitHub organization or username and <repo> with your repository name.filesystem_policy and landlock sections are static. OpenShell reads them at sandbox creation, and a hot reload cannot change them. They are included here for completeness so the file is self-contained. Process identity is omitted so the active compute driver can select it, and only the network_policies section takes effect when you apply this to a running sandbox.
/ Detailed source-code truncated for AI context efficiency. /The following table summarizes the two GitHub-specific blocks:github_git
|---|---|---|
| | github.com:443 | Git Smart HTTP protocol. The proxy auto-detects and terminates TLS to inspect requests. Permits info/refs (clone/fetch), git-upload-pack (fetch data), and git-receive-pack (push) for the specified repository. Denies all operations on unlisted repositories. |github_api
| | api.github.com:443 | REST API. The proxy auto-detects and terminates TLS to inspect requests. Permits all HTTP methods for the specified repository and GraphQL queries. Denies API access to unlisted repositories. |claude_code, nvidia_inference, pypi, vscode) are identical to the default policy. The default policy's github_ssh_over_https and github_rest_api blocks are replaced by the github_git and github_api blocks above, which grant write access to the specified repository. Sandbox behavior outside of GitHub operations is unchanged.
</Accordion>Apply the Policy
openshell policy set <sandbox-name> --policy /tmp/sandbox-policy-update.yaml --waitNetwork policies are hot-reloadable. The --wait flag blocks until the policy engine confirms the new revision loaded, and the update takes effect immediately without restarting the sandbox or reconnecting Claude Code.Retry the Push
The sandbox policy has been updated. Try pushing to the repository again.The push completes successfully. The openshell term dashboard now shows l7_decision=allow entries for api.github.com and github.com where it previously showed denials.Clean Up
openshell sandbox delete <sandbox-name></Steps>ollama launchNext Steps
- To learn the full policy iteration workflow (pull, edit, push, verify), refer to Policies.
- To inject credentials automatically instead of pasting tokens, refer to Manage ProvidersGet Started/Tutorials/Index
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Tutorials"
slug: "get-started/tutorials"
description: "Step-by-step walkthroughs for OpenShell, from first sandbox to production-ready policies."
keywords: "Generative AI, Cybersecurity, Tutorial, Sandbox, Policy"
position: 1
---
</Card>
</Card>
</Card>
</Card>
</Card>
</Card>Get Started/Tutorials/Inference Ollama
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Run Local Inference with Ollama"
sidebar-title: "Inference with Ollama"
slug: "get-started/tutorials/inference-ollama"
description: "Run local and cloud models inside an OpenShell sandbox using the Ollama community sandbox, or route sandbox requests to a host-level Ollama server."
keywords: "Generative AI, Cybersecurity, Tutorial, Inference Routing, Ollama, Local Inference, Sandbox"
---
2. Host-level Ollama. This is an alternative way to run Ollama. Run Ollama on the gateway host and route sandbox inference to it. Use this option when you want a single Ollama instance shared across multiple sandboxes.
- Use to start coding agents inside a sandbox.inference.local
- Expose a host-level Ollama server to sandboxes through .Prerequisites
Option A: Ollama Community Sandbox (Recommended)
Create the Sandbox
openshell sandbox create --from ollamaThis pulls the community sandbox image, applies the bundled policy, and drops you into a shell with Ollama running.Chat with a Model
ollama run qwen3.5Or a cloud model
ollama run kimi-k2.5:cloudOr use ollama launch to start a coding agent with Ollama as the model backend:
ollama launch claude
ollama launch codex
ollama launch opencodeFor CI/CD and automated workflows, ollama launch supports a headless mode:
ollama launch claude --yes --model qwen3.5</Steps>qwen3.5:0.8bModel Recommendations
|---|---|---|
| Smoke test | | Fast, lightweight, good for verifying setup |qwen3.5
| Coding and reasoning | | Strong tool calling support for agentic workflows |nemotron-3-super
| Complex tasks | | 122B parameter model, needs 48GB+ VRAM |qwen3.5:cloud
| No local GPU | | Runs on Ollama's cloud infrastructure, no ollama pull required |:cloud
Cloud models use the tag suffix and do not require local hardware.
openshell sandbox create --from ollama</Note>Tool Calling
Updating Ollama
update-ollamaOr auto-update on every sandbox start:
openshell sandbox create --from ollama -e OLLAMA_UPDATE=1inference.localOption B: Host-Level Ollama
.
This approach uses Ollama because it is easy to install and run locally, but you can substitute other inference engines such as vLLM, SGLang, TRT-LLM, and NVIDIA NIM by changing the startup command, base URL, and model name.Install and Start Ollama
curl -fsSL https://ollama.com/install.sh | shStart Ollama on all interfaces so it is reachable from sandboxes:
OLLAMA_HOST=0.0.0.0:11434 ollama serve<Tip>Error: listen tcp 0.0.0.0:11434: bind: address already in use
If you see , Ollama is already running as a system service. Stop it first:
systemctl stop ollama
OLLAMA_HOST=0.0.0.0:11434 ollama serve</Tip>Pull a Model
ollama run qwen3.5:0.8bType /bye to exit the interactive session. The model stays loaded.Create a Provider
openshell provider create \
--name ollama \
--type openai \
--credential OPENAI_API_KEY=empty \
--config OPENAI_BASE_URL=http://host.openshell.internal:11434/v1OpenShell injects host.openshell.internal so sandboxes and the gateway can reach the host machine. You can also use the host's LAN IP.Set Inference Routing
openshell inference set --provider ollama --model qwen3.5:0.8bConfirm:
openshell inference getVerify from a Sandbox
openshell sandbox create -- \
curl https://inference.local/v1/chat/completions \
--json '{"messages":[{"role":"user","content":"hello"}],"max_tokens":10}'The response should be JSON from the model.0.0.0.0Troubleshooting
, not 127.0.0.1. This applies to host-level Ollama only; the community sandbox handles this automatically.OPENAI_BASE_URL
- wrong: Use http://host.openshell.internal:11434/v1, not localhost or 127.0.0.1.ollama ps
- Model not found: Run to confirm the model is loaded. Run ollama pull <model> if needed.https://inference.local
- HTTPS instead of HTTP: Code inside sandboxes must call , not http://.
- AMD GPU driver issues: Ollama v0.18+ requires ROCm 7 drivers for AMD GPUs. Update your drivers if you see GPU detection failures.
openshell status
openshell inference get
openshell provider get ollamaNext Steps
- To configure a different self-hosted backend, refer to Inference Routing.
- To learn how sandbox containers are selected, refer to Sandboxes.Get Started/Tutorials/Local Inference Lmstudio
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Route Local Inference Requests to LM Studio"
sidebar-title: "Local Inference with LM Studio"
slug: "get-started/tutorials/local-inference-lmstudio"
description: "Configure inference.local to route sandbox requests to a local LM Studio server running on the gateway host."
keywords: "Generative AI, Cybersecurity, Tutorial, Inference Routing, LM Studio, Local Inference, Sandbox"
---
The LM Studio server provides easy setup with both OpenAI and Anthropic compatible endpoints.
- Verify end-to-end inference from inside a sandbox.Prerequisites
<Tab title="Linux/Mac">
curl -fsSL https://lmstudio.ai/install.sh | bash</Tab>
irm https://lmstudio.ai/install.ps1 | iex</Tab>
</Tabs>
lms daemon up<Steps toc={true}>127.0.0.1:1234Start LM Studio Local Server
by default. For use with OpenShell, configure LM Studio to listen on all interfaces (0.0.0.0).lms server start --bind 0.0.0.0.Test with a small model
lms get qwen/qwen3.5-2b
lms load qwen/qwen3.5-2binference.localAdd LM Studio as a provider
.host.openshell.internal
<Tab title="OpenAI-compatible">:
openshell provider create \
--name lmstudio \
--type openai \
--credential OPENAI_API_KEY=lmstudio \
--config OPENAI_BASE_URL=http://host.openshell.internal:1234/v1Use this provider for clients that send OpenAI-compatible requests such as POST /v1/chat/completions or POST /v1/responses.POST /v1/messages endpoint:
openshell provider create \
--name lmstudio-anthropic \
--type anthropic \
--credential ANTHROPIC_API_KEY=lmstudio \
--config ANTHROPIC_BASE_URL=http://host.openshell.internal:1234Use this provider for Anthropic-compatible POST /v1/messages requests.Configure LM Studio as the local inference provider
<Tab title="OpenAI-compatible">
openshell inference set --provider lmstudio --model qwen/qwen3.5-2bIf the command succeeds, OpenShell has verified that the upstream is reachable and accepts the expected OpenAI-compatible request shape.
openshell inference set --provider lmstudio-anthropic --model qwen/qwen3.5-2bIf the command succeeds, OpenShell has verified that the upstream is reachable and accepts the expected Anthropic-compatible request shape.inference.local route is gateway-scoped, so only one provider and model pair is active at a time. Re-run openshell inference set whenever you want to switch between OpenAI-compatible and Anthropic-compatible clients.
openshell inference getYou should see either Provider: lmstudio or Provider: lmstudio-anthropic, along with Model: qwen/qwen3.5-2b.https://inference.localVerify from Inside a Sandbox
:
<Tab title="OpenAI-compatible">
openshell sandbox create -- \
curl https://inference.local/v1/chat/completions \
--json '{"messages":[{"role":"user","content":"hello"}],"max_tokens":10}'
openshell sandbox create -- \
curl https://inference.local/v1/responses \
--json '{
"instructions": "You are a helpful assistant.",
"input": "hello",
"max_output_tokens": 10
}'</Tab>
openshell sandbox create -- \
curl https://inference.local/v1/messages \
--json '{"messages":[{"role":"user","content":"hello"}],"max_tokens":10}'</Tab>OPENAI_BASE_URLTroubleshooting
- uses http://host.openshell.internal:1234/v1 when you use an openai providerANTHROPIC_BASE_URL
- uses http://host.openshell.internal:1234 when you use an anthropic provider
- The gateway and LM Studio run on the same machine or a reachable network path
- The configured model name matches the model exposed by LM Studio
openshell status
openshell inference get
openshell provider get lmstudio
openshell provider get lmstudio-anthropicMS_GRAPH_ACCESS_TOKENNext Steps
- To learn more about managed inference, refer to Inference Routing.
- To configure a different self-hosted backend, refer to Inference Routing.Get Started/Tutorials/Microsoft Graph Provider Refresh
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Refresh Microsoft Graph Credentials with Providers v2"
sidebar-title: "Microsoft Graph Provider Refresh"
slug: "get-started/tutorials/microsoft-graph-provider-refresh"
description: "Configure a Providers v2 Microsoft Graph profile with gateway-managed OAuth2 refresh-token rotation."
keywords: "Generative AI, Cybersecurity, Tutorial, Providers, Microsoft Graph, OAuth2, Credential Refresh, Sandbox"
--- placeholder. OpenShell stores the non-injectable refresh material at the gateway, refreshes the Microsoft Graph access token before it expires, updates the provider record, and injects the current credential into newly launched sandbox processes.oauth2-refresh-token
- A provider instance configured with .curl
- A sandbox that can use to read Microsoft Graph mail through provider-owned policy.Mail.Read
This tutorial starts after your OAuth client has already completed the initial Microsoft sign-in flow. It does not publish a token bootstrap script. Use the Microsoft identity platform documentation for the device authorization grant flow or authorization code flow, and use any standards-compliant client that returns an access token, refresh token, and expiry.
</Note>Prerequisites
- A Microsoft Entra app registration that can acquire delegated Microsoft Graph mail access.
- Delegated Microsoft Graph mail permission for the signed-in user. allows reading the signed-in user's mailbox; see the Microsoft Graph permissions reference.MS_TENANT_ID
OAuth material from your initial Microsoft sign-in flow:
|---|---|
| | Microsoft Entra tenant ID, domain, or common. |MS_CLIENT_ID
| | Microsoft Entra application client ID. |MS_GRAPH_ACCESS_TOKEN
| | Current delegated Microsoft Graph access token. |MS_GRAPH_REFRESH_TOKEN
| | Delegated OAuth refresh token. |MS_GRAPH_ACCESS_TOKEN_EXPIRES_AT
| | Absolute expiry for the current access token. |MS_GRAPH_ACCESS_TOKEN_EXPIRES_AT can be an RFC3339 timestamp such as 2026-01-01T00:00:00Z or a Unix epoch millisecond timestamp..env
Do not commit access tokens, refresh tokens, or local files. The commands below pass token material to the gateway; they are not examples of values to store in source control.
</Warning>Enable Providers v2
openshell settings set --global --key providers_v2_enabled --value true --yesmicrosoft-graph-mail.yamlCreate a Microsoft Graph Provider Profile
with this profile:SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
id: microsoft-graph-mail
display_name: Microsoft Graph Mail
description: Delegated Microsoft Graph mail read access
category: messaging
credentials:
- name: graph_access_token
description: Microsoft Graph delegated access token
env_vars: [MS_GRAPH_ACCESS_TOKEN]
required: true
auth_style: bearer
header_name: authorization
refresh:
strategy: oauth2_refresh_token
token_url: https://login.microsoftonline.com/common/oauth2/v2.0/token
scopes: [https://graph.microsoft.com/.default]
refresh_before_seconds: 600
max_lifetime_seconds: 3600
material:
- name: tenant_id
description: Microsoft Entra tenant ID
required: true
- name: client_id
description: Microsoft Entra application client ID
required: true
- name: refresh_token
description: Delegated OAuth refresh token
required: true
secret: true
endpoints:
- host: graph.microsoft.com
port: 443
protocol: rest
access: read-only
enforcement: enforce
binaries:
- /usr/bin/curl
- /usr/local/bin/curl
Lint and import the profile:openshell provider profile lint -f microsoft-graph-mail.yaml
openshell provider profile import -f microsoft-graph-mail.yaml
The profile defines the refresh strategy and Graph network policy. Thetenant_idrefresh material selects the Microsoft token endpoint during gateway-managed refresh.Create the Provider
Create the provider with the current Microsoft Graph access token:
openshell provider create \
--name microsoft-mail \
--type microsoft-graph-mail \
--credential MS_GRAPH_ACCESS_TOKEN="$MS_GRAPH_ACCESS_TOKEN"
The current CLI requires an initial credential at provider creation time. Refresh material is configured separately and is not injected into the sandbox.Configure Refresh
Configure gateway-managed OAuth2 refresh-token rotation:
openshell provider refresh configure microsoft-mail \
--credential-key MS_GRAPH_ACCESS_TOKEN \
--strategy oauth2-refresh-token \
--material tenant_id="$MS_TENANT_ID" \
--material client_id="$MS_CLIENT_ID" \
--material refresh_token="$MS_GRAPH_REFRESH_TOKEN" \
--secret-material-key refresh_token \
--credential-expires-at "$MS_GRAPH_ACCESS_TOKEN_EXPIRES_AT"
--secret-material-key refresh_tokennames the material key to mark as sensitive. It is not the refresh-token value. If Microsoft returns a rotated refresh token during refresh, OpenShell stores the newrefresh_tokenmaterial and marks it secret automatically.Force the first refresh immediately:
openshell provider refresh rotate microsoft-mail \
--credential-key MS_GRAPH_ACCESS_TOKEN
Check refresh status:openshell provider refresh status microsoft-mail \
--credential-key MS_GRAPH_ACCESS_TOKEN
The status output shows refresh state, expiry, next refresh, and last refresh timing. It does not print access-token values or refresh material.Launch a Sandbox
Launch a sandbox with the Microsoft Graph provider attached:
openshell sandbox create \
--name microsoft-graph-mail \
--provider microsoft-mail \
--no-auto-providers \
-- /bin/sh
Provider policy allowscurlto reachgraph.microsoft.com:443. The sandbox process receivesMS_GRAPH_ACCESS_TOKENas an OpenShell placeholder, and the proxy resolves that placeholder to the current gateway-managed access token whencurlsends it in the authorization header.Verify Microsoft Graph Access
Inside the sandbox, list a small page of mailbox messages:
curl -sS \
-H "Authorization: Bearer $MS_GRAPH_ACCESS_TOKEN" \
'https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject&$top=5'
The request uses the Microsoft Graph list messages API. If the token has delegated mail read permission, Microsoft Graph returns message metadata for the signed-in user's mailbox.MS_GRAPH_ACCESS_TOKENUpdate Running Sandboxes
Provider refresh updates the provider record at the gateway. Running sandboxes poll for provider environment revisions, but already-running processes keep the environment they started with.
If you attach this provider to an existing sandbox or update provider credentials after a process has already started, launch a new process inside the sandbox before expecting
to appear in that process environment.</Steps>
---
Get Started/Quickstart
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Quickstart"
description: "Install the OpenShell CLI, connect to a gateway, and create your first sandboxed AI agent."
keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Installation, Quickstart, Gateway, Docker, Kubernetes, Podman"
position: 1
---This page gets you from a reachable OpenShell gateway to a running, policy-enforced sandbox.
Prerequisites
Before you begin, make sure you have:
- A reachable OpenShell gateway.
- At least one compute driver configured for the gateway: Kubernetes, Docker, Podman, or MicroVM.
- The OpenShell CLI installed on your workstation.For a complete list of requirements, refer to Support Matrix.
If you have not chosen a compute driver yet, refer to Installation.Install the OpenShell CLI
Run the install script:
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
The install script uses Homebrew, RPM, or a Debian package based on your machine. It starts the local gateway server after installation.If you prefer uv:
uv tool install -U openshell
After installing the CLI, runopenshell --helpin your terminal to view the full CLI reference./openshell-cli<Tip>
You can also clone the NVIDIA OpenShell GitHub repository and use theskill to load the CLI reference into your agent.
</Tip>Create Your First OpenShell Sandbox
Create a sandbox and launch an agent inside it.
Choose the tab that matches your agent:<Tabs>
<Tab title="Claude Code">Run the following command to create a sandbox with Claude Code:
openshell sandbox create -- claude
The CLI prompts you to create a provider from local credentials.yes
Typeto continue.ANTHROPIC_API_KEY
Ifis set in your environment, the CLI picks it up automatically.ANTHROPIC_API_KEY
If not, you can configure it from inside the sandbox after it launches.<Note>
is an API key from console.anthropic.com, not a subscription token. Subscription users must generate a separate API key.
</Note>
</Tab><Tab title="OpenCode">
Run the following command to create a sandbox with OpenCode:
openshell sandbox create -- opencode
The CLI prompts you to create a provider from local credentials.yes
Typeto continue.OPENAI_API_KEY
IforOPENROUTER_API_KEYis set in your environment, the CLI picks it up automatically.
If not, you can configure it from inside the sandbox after it launches.
</Tab><Tab title="Codex">
Run the following command to create a sandbox with Codex:
openshell sandbox create -- codex
The CLI prompts you to create a provider from local credentials.yes
Typeto continue.OPENAI_API_KEY
Ifis set in your environment, the CLI picks it up automatically.--from
If not, you can configure it from inside the sandbox after it launches.
</Tab><Tab title="Base Sandbox">
Use the
flag to create a sandbox from the base container:
openshell sandbox create --from base
</Tab>openshell-client-tls</Tabs>
---
Kubernetes/Access Control
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Access Control"
sidebar-title: "Access Control"
description: "Configure OIDC user authentication or reverse-proxy auth termination for a Kubernetes-deployed OpenShell gateway."
keywords: "Generative AI, Cybersecurity, Kubernetes, Authentication, mTLS, OIDC, Keycloak, Entra ID, Okta, Gateway Auth"
position: 5
---The OpenShell gateway supports two access-control models for human callers on Kubernetes:
| Model | When to use |
|---|---|
| OIDC (recommended) | Production deployments. Integrates with an existing identity provider, supports role-based access control, and gives each user their own identity without distributing certificates. |
| Reverse-proxy auth termination | An access proxy (Cloudflare Access, ngrok, corporate SSO) authenticates callers in front of the gateway. The gateway trusts the proxy and skips its own client-cert check. |The Helm chart always generates mTLS certificates at install time. The gateway uses them for transport-layer security regardless of which access-control model you choose. The client bundle in the
secret is used internally by sandbox supervisors, not for granting access to individual users.server.providerTokenGrants.spiffe.enabled=trueFor how the CLI resolves gateways and stores credentials, refer to Gateway Authentication.
Sandbox Supervisor Identity
Kubernetes sandbox supervisors authenticate back to the gateway as sandbox workloads. By default, the gateway mints its own sandbox JWTs and Kubernetes sandboxes bootstrap them with a projected ServiceAccount token.
Dynamic provider token grants can use SPIFFE without changing supervisor-to-gateway authentication. Set
to mount the SPIFFE CSI Workload API socket into sandbox pods while keeping the projected ServiceAccount token bootstrap and gateway-minted sandbox JWT path.ClusterSPIFFEIDProvider token grants require a SPIFFE implementation such as SPIRE and a
that assigns per-sandbox IDs from the pod'sopenshell.io/sandbox-idannotation. Provider profiles withtoken_grantmetadata cause the sandbox supervisor to request JWT-SVIDs and exchange them for upstream OAuth2 access tokens.server.oidc.issuerOIDC User Authentication
Set
to enable OIDC. The gateway validates theAuthorization: Bearer <token>header on every request against the issuer's JWKS endpoint.
helm upgrade openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set server.oidc.issuer=https://your-idp.example.com/realms/openshell \
--set server.oidc.audience=openshell-cli
Theaudiencevalue must match the client ID configured in your identity provider for the OpenShell resource server.server.oidc.issuerOIDC values reference
| Value | Default | Purpose |
|---|---|---|
||""| OIDC issuer URL. Empty disables OIDC. |server.oidc.audience
||openshell-cli| Expectedaudclaim in the JWT. |server.oidc.jwksTtl
||3600| JWKS key cache TTL in seconds. |server.oidc.rolesClaim
||""| Dot-separated path to the roles array in JWT claims. |server.oidc.adminRole
||""| Role name that grants admin access. |server.oidc.userRole
||""| Role name that grants standard user access. |server.oidc.scopesClaim
||""| Dot-separated path to the scopes array in JWT claims. |adminRoleAuth-only mode vs. RBAC mode
Leave both
anduserRoleempty to use auth-only mode: any request with a valid JWT from the configured issuer is accepted, but no role distinction is enforced.Set both values to enable RBAC mode, where the gateway checks the role claim and enforces access based on the assigned role:
helm upgrade openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set server.oidc.issuer=https://your-idp.example.com/realms/openshell \
--set server.oidc.audience=openshell-cli \
--set server.oidc.rolesClaim=realm_access.roles \
--set server.oidc.adminRole=openshell-admin \
--set server.oidc.userRole=openshell-user
BothadminRoleanduserRolemust be set, or both must be empty. Setting only one is not supported.realm_access.rolesProvider-specific rolesClaim paths
| Provider | rolesClaim value |
|---|---|
| Keycloak ||roles
| Microsoft Entra ID ||groups
| Okta ||Reverse-Proxy Auth Termination
When an access proxy, such as Cloudflare Access, ngrok, or a corporate SSO gateway, handles authentication in front of the OpenShell gateway, you can explicitly allow unauthenticated user calls at the gateway:
helm upgrade openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set server.auth.allowUnauthenticatedUsers=true
The gateway still serves TLS and sandbox supervisors still authenticate with gateway-minted sandbox JWTs. User-facing CLI/API calls without OIDC or mTLS credentials are accepted as an unauthenticated local developer principal. The proxy is responsible for authenticating callers and forwarding only authorized traffic.To also disable TLS entirely (when the proxy terminates TLS before the request reaches the gateway):
--set server.disableTls=true \
--set server.auth.allowUnauthenticatedUsers=true
<Warning>
Only enable unauthenticated users when the gateway is not reachable from outside a trusted local development environment or the proxy path is fully trusted. Never expose a plaintext, auth-disabled gateway to a public network.
</Warning>Register the gateway with the CLI using the proxy's public URL. The browser-based login flow runs automatically on first use:
openshell gateway add https://gateway.example.com --name production
---kubectl port-forwardKubernetes/Ingress
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Ingress"
sidebar-title: "Ingress"
description: "Expose the OpenShell gateway externally using the Kubernetes Gateway API and a GRPCRoute."
keywords: "Generative AI, Cybersecurity, Kubernetes, Gateway API, Envoy Gateway, GRPCRoute, Ingress, External Access"
position: 4
---By default, the OpenShell gateway is only reachable inside the cluster. To let CLI clients connect without a
, expose the gateway through an ingress.GRPCRouteOpenShell uses the Kubernetes Gateway API for ingress. The chart creates a
that routes inbound gRPC traffic to the gateway pod. You need a Gateway API implementation installed on your cluster to fulfill theGRPCRoute. This page uses Envoy Gateway, which the chart is tested with.Install Envoy Gateway
Envoy Gateway installs the Gateway API CRDs and controller:
helm install eg \
oci://docker.io/envoyproxy/gateway-helm \
--version v1.8.1 \
--namespace envoy-gateway-system \
--create-namespace \
--wait
egCreate the GatewayClass
Create the
GatewayClass that the OpenShell chart references:
kubectl apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
EOF
Verify the GatewayClass is accepted:kubectl get gatewayclass eg
TheACCEPTEDcolumn should showTrue.openshellInstall OpenShell with Gateway API enabled
Enable the GRPCRoute and let the chart create a Gateway resource in the
namespace:
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set grpcRoute.enabled=true \
--set grpcRoute.gateway.create=true \
--set grpcRoute.gateway.className=eg
openshellGet the external address
After the Gateway is provisioned, Envoy Gateway creates a LoadBalancer service in the
namespace. Wait for it to get an external address:
kubectl -n openshell get svc -l gateway.envoyproxy.io/owning-gateway-name=openshell
After theEXTERNAL-IPis assigned, register the gateway with the CLI:
openshell gateway add http://<external-ip> --name production
openshell status
This setup is plaintext end-to-end and is intended for development. For external access, terminate TLS at the gateway as shown below.HTTPS (TLS termination)
Envoy Gateway can terminate TLS at the listener and forward plaintext to the OpenShell gateway pod:
client → HTTPS → Envoy Gateway (terminate TLS) → plaintext → openshell gateway pod
Envoy Gateway only terminates TLS here — it does not perform OIDC. Do not enable an Envoy Gateway OIDCSecurityPolicyin front of the gateway: that flow relies on browser redirects and cookies and cannot work with the OpenShell CLI or headless agents. Instead, the OpenShell gateway validates an OIDC bearer token that the client sends in the gRPCauthorizationmetadata, which Envoy forwards untouched.OPENSHELL_OIDC_CLIENT_SECRETBecause Envoy terminates TLS, the OpenShell gateway never sees a client certificate, so client mTLS cannot provide identity on this path. Use OIDC bearer tokens for client identity instead.
For headless agents and CI, the CLI obtains the token via the OAuth2 client-credentials grant (no browser): set
to the OAuth client secret before runningopenshell gateway add. The client id comes from--oidc-client-id(defaultopenshell-cli); pass it explicitly if your IdP client uses a different id. Interactive human users get the browser-based Authorization Code + PKCE flow by default.kubernetes.io/tlsProvide a TLS certificate
Create a
Secret in theopenshellnamespace with the certificate for your external hostname:
kubectl -n openshell create secret tls openshell-ingress-tls \
--cert=tls.crt --key=tls.key
The Secret may also be issued by cert-manager, or you can reference the chart's existingopenshell-server-tlsSecret if its SANs include the external hostname.Install with HTTPS termination
Enable an HTTPS listener, point it at the Secret, disable gateway-pod TLS so Envoy forwards plaintext, and configure an OIDC issuer for client identity:
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set grpcRoute.enabled=true \
--set grpcRoute.gateway.create=true \
--set grpcRoute.gateway.className=eg \
--set grpcRoute.gateway.listener.protocol=HTTPS \
--set grpcRoute.gateway.listener.port=443 \
--set 'grpcRoute.gateway.listener.tls.certificateRefs[0].name=openshell-ingress-tls' \
--set server.disableTls=true \
--set server.oidc.issuer=https://<issuer> \
--set 'grpcRoute.hostnames[0]=<external-hostname>'
Keep the certificate Secret in the release namespace. Referencing a Secret in another namespace requires aReferenceGrant.Register over HTTPS
openshell gateway add https://<external-hostname> --name production --oidc-issuer https://<issuer>
openshell status
See Authentication for OIDC issuer, audience, and roles configuration.pkiInitJobSSH Relay
Sandbox SSH uses the gateway endpoint registered with the CLI. No separate Helm SSH host or port values are required.
Next Steps
Return to Setup to complete the installation.
---
Kubernetes/Managing Certificates
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Managing Certificates"
sidebar-title: "Managing Certificates"
description: "Configure the OpenShell Helm chart to use cert-manager for mTLS certificate issuance and automatic renewal."
keywords: "Generative AI, Cybersecurity, Kubernetes, cert-manager, PKI, TLS, mTLS, Certificates"
position: 3
---The OpenShell gateway uses mTLS certificates for transport between the gateway and sandbox supervisors. These certificates are not Kubernetes user authentication; configure OIDC or a trusted access proxy for user access. The Helm chart supports two ways to provision and manage the certificate bundle:
| Mode | When to use |
|---|---|
| Built-in(default) | The default path. A pre-install Kubernetes Job generates a self-signed CA and certificates during installation. No additional dependencies. |certManager.enabled=true
| cert-manager | Production deployments that need automatic certificate rotation managed by a running controller. |The rest of this page covers switching to cert-manager. The built-in mode requires no configuration.
<Note>
When, cert-manager owns TLS certificate generation.pkiInitJob.enabled
The chart still runs a JWT-only initialization hook because cert-manager does
not create the sandbox JWT signing Secret required by the gateway. This
cert-manager precedence applies even ifremains true.
</Note>Install cert-manager
Install cert-manager from the OCI registry with CRD support enabled:
helm upgrade --install cert-manager oci://quay.io/jetstack/charts/cert-manager \
--version v1.20.3 \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--wait
Verify the cert-manager pods are running:kubectl -n cert-manager get pods
Install OpenShell with cert-manager PKI
Pass the cert-manager values override when installing or upgrading the chart:
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set certManager.enabled=true
The chart creates a self-signed CA, issues server and client certificates from it, and cert-manager handles renewal before expiry./etc/openshell-jwt
The chart also runs a pre-install hook in JWT-only mode to create the gateway's
sandbox JWT signing Secret. That Secret is separate from the cert-manager TLS
certificate Secrets and is mounted at.certManager.serverIssuerRefUsing a real Issuer for the server certificate
By default, cert-manager issues both the server and client certificates from
a self-signed CA the chart creates — this rotates automatically, but the
server certificate is still not publicly trusted.issuerRef
overrides theon the serverCertificateresource to point at aIssuer
realorClusterIssuerinstead, for example an ACME issuer:
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set certManager.enabled=true \
--set certManager.serverIssuerRef.name=letsencrypt-prod \
--set certManager.serverIssuerRef.kind=ClusterIssuer \
--set certManager.serverDnsNames[0]=openshell.example.com
serverIssuerRefDual certificate architecture
When
is set, the chart creates two server certificates:openshell-server-tls1. Internal certificate (
): signed by the chart CA*.svc.cluster.local
with internal SANs (,localhost, etc.).openshell-server-external-tls
2. External certificate (): signed by thecertManager.serverDnsNames
configured issuer (e.g. ACME) with only the hostnames from
.certManager.serverDnsNamesThe gateway uses SNI to select which certificate to present:
supervisors connect via internal service names and receive the internal
certificate (verified against the chart CA they already trust), while CLI
users connecting through a Route or ingress use the external hostname and
receive the ACME certificate. This keeps supervisor trust pinned to only
the operator's chart CA — no WebPKI root trust is needed.<Note>
Public CAs such as Let's Encrypt reject certificate requests that include
internal-only names per CA/Browser Forum baseline requirements. The chart
validates this at install time and fails with an actionable error ifcontains internal-only entries whileserverIssuerRefis set.server.grpcEndpointYou do not need to set
to the external hostname.server.grpcEndpoint
Supervisors connect via the internal service name automatically. Settingto an external hostname would cause supervisors toclientCaFromServerTlsSecret=true
receive the ACME certificate (via SNI) which they cannot verify against the
chart CA.
</Note>The default
is correct even whenserverIssuerRefis set: the internal server certificate is always signedca.crt
by the chart CA (the same CA that signs the client certificate), so itsis the right trust anchor for mTLS verification.privilegedNext Steps
Return to Setup to complete the installation. For
exposing the gateway externally on OpenShift with a real certificate, see
OpenShift.---
Kubernetes/Openshift
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "OpenShift"
sidebar-title: "OpenShift"
description: "Install the OpenShell Helm chart on OpenShift, including the SCC binding and chart overrides required by OpenShift's Security Context Constraints."
keywords: "Generative AI, Cybersecurity, Kubernetes, OpenShift, SCC, Security Context Constraints, Helm, Gateway, Installation"
position: 6
---<Warning>
The OpenShift install path is experimental. It currently requires running sandbox pods under theSCC and installing the gateway with TLS disabled. Use only for evaluation on a private network.privileged
</Warning>OpenShift's Security Context Constraints reject the chart's default pod security settings. Installing on OpenShift requires precreating the namespace, granting the
SCC to the sandbox service account, and overriding a few chart values so the cluster admission controller can assign UIDs and FS groups itself.ocOpenShell installs sandbox nftables rules as individual commands. On OpenShift
nodes where optional conntrack or packet log expressions are unavailable, those
optional rules can fail without rolling back the required proxy bypass reject
rules.Prerequisites
- OpenShift 4.x cluster with
configured
- Helm 3.x
- Agent Sandbox controller and CRDs installedInstall
<Steps>
Create the namespace
Pre-create the namespace so the SCC binding can be applied before the chart installs:
oc create ns openshell
openshell-sandboxGrant the privileged SCC to sandbox pods
Sandbox pods run under the
service account in theopenshellnamespace and require theprivilegedSCC:
oc adm policy add-scc-to-user privileged -z openshell-sandbox -n openshell
Install the chart with OpenShift overrides
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set server.disableTls=true \
--set podSecurityContext.fsGroup=null \
--set securityContext.runAsUser=null
| Override | Reason |server.disableTls=true
|---|---|
|| Runs the gateway over plaintext HTTP for simpler evaluation. |podSecurityContext.fsGroup=null
|/securityContext.runAsUser=null| Clear the chart's hardcoded UID and fsGroup so OpenShift's SCC admission can assign them. |Wait for the gateway to be ready
oc -n openshell rollout status statefulset/openshell
If you setworkload.kind=deployment, useoc -n openshell rollout status deployment/openshellinstead.oc port-forward</Steps>
Connect to the gateway
The gateway is now running over plaintext HTTP. Connect with
:
oc -n openshell port-forward svc/openshell 8080:8080
Register the gateway with the CLI:openshell gateway add http://127.0.0.1:8080 --local --name openshift
openshell status
ClusterIssuerProduction: expose externally with a real certificate
The steps above run the gateway over plaintext HTTP for quick evaluation. For
a real deployment, cert-manager can issue the gateway's server certificate
from a real Issuer or ClusterIssuer (for example, an ACME issuer), and an
OpenShift Route with TLS passthrough exposes it externally while the gateway
keeps terminating its own TLS and mTLS.Install cert-manager and configure a working
first — seecertManager.serverIssuerRef
Managing Certificates for thedetails. Configure an OIDC provider as described
in Access Control — remote gateways authenticate
CLI users via OIDC, not mTLS, so the gateway must know the OIDC issuer URL.
Install the chart with:
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set podSecurityContext.fsGroup=null \
--set securityContext.runAsUser=null \
--set server.disableTls=false \
--set certManager.enabled=true \
--set certManager.serverIssuerRef.name=<cluster-issuer-name> \
--set certManager.serverIssuerRef.kind=ClusterIssuer \
--set certManager.serverDnsNames[0]=<external-hostname> \
--set openshiftRoute.enabled=true \
--set openshiftRoute.host=<external-hostname> \
--set server.oidc.issuer=<oidc-issuer-url> \
--set server.oidc.audience=<oidc-audience>
| Override | Reason |certManager.serverIssuerRef
|---|---|
|| Creates a second server certificate from your Issuer or ClusterIssuer for external clients. The gateway uses SNI to present this cert for the external hostname while continuing to present the internal (chart CA) cert to supervisors. The internal certificate'sca.crtis the chart CA that also signed the client cert, so the defaultclientCaFromServerTlsSecret=trueis correct. |openshiftRoute.enabled
|/openshiftRoute.host| Creates an OpenShift Route with TLS passthrough — the router forwards the encrypted connection by SNI without decrypting, so the gateway uses the SNI hostname to select the external certificate. |server.oidc.issuer
|/server.oidc.audience| Configures server-side OIDC validation. Without these, the gateway expects mTLS client certificates and rejects OIDC-only CLI connections. See Access Control. |Register the gateway with the CLI over OIDC. Remote gateways authenticate CLI
users via OIDC, not mTLS — see Access Control:
openshell gateway add https://<external-hostname> \
--name openshift \
--oidc-issuer <issuer-url>
openshell gateway login openshift
server.externalDbSecretNext Steps
- For more on certificate provisioning modes, refer to Managing Certificates.
- To expose the gateway externally through the Kubernetes Gateway API instead of a Route, refer to Ingress.
- To configure OIDC authentication, refer to Access Control.---
Kubernetes/Setup
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Set Up OpenShell on Kubernetes"
sidebar-title: "Setup"
description: "Deploy the OpenShell gateway to a Kubernetes cluster using the official Helm chart from GHCR."
keywords: "Generative AI, Cybersecurity, Kubernetes, Helm, Gateway, Deployment, OCI, GHCR, Installation"
position: 1
---<Warning>
The OpenShell Helm chart is experimental and under active development. Templates, values, and defaults can change between releases. Do not use it in production.
</Warning>Use the Kubernetes deployment when the gateway should run on a shared cluster, in a cloud environment, or as part of team infrastructure. The Helm chart handles PKI bootstrap, RBAC, sandbox namespace setup, and the gateway workload. It uses a StatefulSet by default for the SQLite database, and can render a Deployment when
points at an external database.Prerequisites
Make sure the following are in place before you install.
| Prerequisite | Required | Notes |
|---|---|---|
| Kubernetes 1.29+ with RBAC enabled | Yes | No additional notes. |
| Helm 3.x | Yes | No additional notes. |
| Agent Sandbox controller and CRDs | Yes | Install before the OpenShell chart. Refer to Install Agent Sandbox. |
| cert-manager | No | Refer to Managing Certificates. Use cert-manager only if you prefer it over the built-in PKI job. |
| Kubernetes Gateway API | No | Refer to Ingress. Use it only for external access without port-forwarding. |Install Agent Sandbox
OpenShell uses the Agent Sandbox Kubernetes SIG project to provision sandbox pods. Install the Agent Sandbox controller and its CRDs on your cluster before installing the OpenShell Helm chart.
Apply the latest release manifest:
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/latest/download/manifest.yaml
This creates theagent-sandbox-systemnamespace, installs thesandboxes.agents.x-k8s.ioCRD, and starts the controller.registry.k8s.io/agent-sandbox/agent-sandbox-controller<Note>
Air-gapped clusters: mirror the manifest above and theimage referenced inside it to your internal registry, then point the manifest's image reference at your mirror before applying. You will also need to mirror the OpenShell gateway and sandbox images — see the chart'simage.repositoryvalue for the gateway andserver.sandboxImage/server.supervisorImagefor the sandbox runtime.
</Note>Confirm the controller pod is running before proceeding:
kubectl -n agent-sandbox-system get pods
The controller pod should reachRunningstatus within a few seconds. For cluster-specific setup instructions, including KinD and GKE walkthroughs, refer to the Agent Sandbox getting started guide.SandboxUpgrade Agent Sandbox
OpenShell detects the served Agent Sandbox
API when the Kubernetes gateway first needs it and caches that choice for the gateway process. If you upgrade Agent Sandbox in place, restart the OpenShell gateway after the Agent Sandbox controller and CRD rollout completes so the gateway can detect the served API versions again. Existing sandboxes keep running during the upgrade, and the restarted gateway can continue managing them.Install OpenShell
<Steps>
Create the namespace
kubectl create namespace openshell
<version>Install the chart
Install from the OCI registry on GHCR. Replace
with the chart version you want to install.
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell
To use the latest development build instead of a stable release:helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version 0.0.0-dev \
--namespace openshell
The chart automatically generates PKI secrets on first install using pre-install Helm hooks. No manual secret creation is required.Wait for the gateway to be ready
kubectl -n openshell rollout status statefulset/openshell
If you setworkload.kind=deployment, wait on the Deployment instead:
kubectl -n openshell rollout status deployment/openshell
Connect to the gateway
For local evaluation, use a port-forward:
kubectl -n openshell port-forward svc/openshell 8080:8080
<Warning>
The port-forward is for local evaluation only. For shared environments, expose the gateway through your ingress controller or access proxy. Refer to Ingress for an external access option.
</Warning>Install the TLS client bundle
The chart generates an mTLS bundle for transport security. Kubernetes deployments do not use that bundle as user authentication; configure OIDC or a trusted access proxy as described in Access Control. For local port-forwarded access, copy the generated bundle so the CLI can verify the gateway certificate:
mkdir -p ~/.config/openshell/gateways/k8s/mtls
kubectl -n openshell get secret openshell-client-tls \
-o jsonpath='{.data.ca\.crt}' | base64 -d > ~/.config/openshell/gateways/k8s/mtls/ca.crt
kubectl -n openshell get secret openshell-client-tls \
-o jsonpath='{.data.tls\.crt}' | base64 -d > ~/.config/openshell/gateways/k8s/mtls/tls.crt
kubectl -n openshell get secret openshell-client-tls \
-o jsonpath='{.data.tls\.key}' | base64 -d > ~/.config/openshell/gateways/k8s/mtls/tls.key
The server certificate SANs includelocalhostand127.0.0.1, so hostname verification passes over the port-forward without extra flags.Register with the CLI
In another terminal, register the gateway with the user authentication mode you configured and verify it is reachable. For example, with OIDC:
openshell gateway add https://127.0.0.1:8080 --local --name k8s \
--oidc-issuer https://your-idp.example.com/realms/openshell \
--oidc-client-id openshell-cli
openshell status
</Steps>image.repositoryConfigure Chart Values
The most commonly changed values are:
| Value | Purpose |
|---|---|
|/image.tag| Gateway container image. Defaults toghcr.io/nvidia/openshell/gateway:latest. |replicaCount
|| Number of gateway replicas. Leave at1unless you are explicitly testing multi-replica behavior. |workload.kind
|| Gateway workload controller. Usestatefulsetfor SQLite ordeploymentwithserver.externalDbSecret. |workload.allowMultiReplicaStatefulSet
|| AllowreplicaCount > 1withworkload.kind=statefulset. Prefer Deployment for external database-backed multi-replica gateways. |server.sandboxNamespace
|| Namespace where sandbox pods are created. Defaults to the Helm release namespace when left empty. |server.externalDbSecret
|| Secret containing a PostgreSQL connection URI in theurikey. Use when the database is managed outside the chart. |server.telemetryEnabled
|| Enable anonymous OpenShell telemetry from the gateway and its sandbox supervisors. Set tofalseto opt out. |server.sandboxImage
|| Default sandbox image used when a sandbox does not specify one. |server.sandboxImagePullSecrets
|| Image pull secrets attached to sandbox pods. Referenced Secrets must exist in the sandbox namespace. |server.grpcEndpoint
|| Endpoint that sandbox supervisors use to call back to the gateway. Must be reachable from inside the cluster. |server.appArmorProfile
|| AppArmor profile requested for sandbox agent containers. Defaults toUnconfined. |server.disableTls
|| Run the gateway over plaintext HTTP. Use only behind a trusted transport. |server.auth.allowUnauthenticatedUsers
|| Accept user-facing calls without OIDC or mTLS credentials. Use only for trusted local development or a fully trusted access proxy. |server.enableLoopbackServiceHttp
|| Enable local plaintext HTTP for loopback sandbox service URLs. Defaults totrue. |pkiInitJob.serverDnsNames
|/certManager.serverDnsNames| Additional gateway server DNS SANs. Wildcard SANs also enable sandbox service URLs under that domain. |supervisor.sideloadMethod
|| How the supervisor binary is delivered into sandbox pods. Leave empty to auto-detect based on cluster version: clusters running Kubernetes 1.35 or later useimage-volume(ImageVolume GA in 1.36); older clusters useinit-container. Set explicitly toimage-volumeon Kubernetes 1.33 or 1.34 with the ImageVolume feature gate enabled, or toinit-containerto force the legacy path on any version. |supervisor.topology
|| Sandbox pod topology. Refer to Topology. |supervisor.sidecar.proxyUid
|| Non-root UID used when sidecar process/binary-aware network policy is disabled. The default binary-aware sidecar runs as UID 0 instead. The configured UID must not match the sandbox UID. |upstreamProxy
|| Operator-owned corporate HTTP forward proxy for policy-approved TLS egress. Refer to Configure a Corporate Upstream Proxy. |Use a values file for repeatable deployments:
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--values my-values.yaml
The chart defaultsserver.appArmorProfiletoUnconfinedbecauseserver.appArmorProfile
runtime/default AppArmor profiles can block the supervisor's network namespace
mount setup on AppArmor-enabled nodes. Setto an emptyRuntimeDefault
string to omit the field,to force the runtime default, orLocalhost/<profile-name>when you load and manage a localhost profile on eachkubernetes.io/dockerconfigjson
node.To use private sandbox images, create a
Secret
in the sandbox namespace and reference its name:
kubectl -n openshell create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username="$REGISTRY_USER" \
--docker-password="$REGISTRY_TOKEN"
server:
sandboxImage: registry.example.com/team/openshell-sandbox:latest
sandboxImagePullSecrets:
- name: regcred
user:passConfigure a Corporate Upstream Proxy
Configure a corporate forward proxy when sandbox TLS egress cannot dial the Internet directly. OpenShell evaluates policy and SSRF checks before it opens an HTTP CONNECT tunnel through the proxy. The proxy URL is operator-owned configuration. Sandbox environment variables cannot select, replace, or bypass it.
Create the credential Secret in the sandbox namespace when the proxy requires Basic authentication. The Secret value uses the
form.
kubectl -n openshell create secret generic corporate-proxy-auth \
--from-literal=credentials="$PROXY_USER:$PROXY_PASSWORD"
Add the proxy settings to your Helm values file. Replace the DNS suffixes and CIDRs innoProxywith values for your cluster.noProxybypasses only the corporate proxy. OpenShell policy evaluation still applies.
upstreamProxy:
url: http://proxy.corp.example:8080
noProxy: .svc,.svc.cluster.local,10.96.0.0/12,10.244.0.0/16
authSecret:
name: corporate-proxy-auth
key: credentials
authAllowInsecure: true
supervisor:
topology: sidecar
UseauthAllowInsecure: trueonly when you accept that Basic authentication is cleartext on the connection to anhttp://proxy. The initial release supportshttp://proxy endpoints and TLS CONNECT egress. It does not support HTTPS-to-proxy, custom corporate CA bundles, or forwarding plain HTTP egress through the proxy.sidecarProxy credentials require
topology. It mounts the credential only into the dedicated network supervisor container. OpenShell rejects credential Secrets withcombinedtopology because KubernetesfsGroupvolume permission handling can make a shared credential mount readable by the sandbox group.openshellRBAC
The chart creates the following RBAC resources in the release namespace:
| Resource | Scope | Name |
|---|---|---|
| ServiceAccount | Namespace ||openshell-sandbox
| ServiceAccount | Namespace |(for sandbox pods) |openshell-sandbox
| Role + RoleBinding | Namespace ||openshell-node-reader
| ClusterRole + ClusterRoleBinding | Cluster ||agents.x-k8s.ioThe namespaced Role covers sandbox lifecycle and identity:
| API Group | Resource | Verbs |
|---|---|---|
||sandboxes,sandboxes/status| create, delete, get, list, patch, update, watch |""
||events| get, list, watch |""
||pods| get |authentication.k8s.ioThe ClusterRole grants node inspection and token validation:
| API Group | Resource | Verbs |
|---|---|---|
||tokenreviews| create |""
||nodes| get, list, watch |serviceAccount.create=falseTo use an existing ServiceAccount instead of creating one, set
and supply its name:
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set serviceAccount.create=false \
--set serviceAccount.name=my-existing-sa
The ServiceAccount must already have the Role and ClusterRole bindings described above./healthzProbes
The gateway exposes
for process liveness and/readyzfor dependency-aware readiness on the health port. The Helm chart wires both into Kubernetes probes:startupProbe-
andlivenessProbeuse/healthz.readinessProbe
-uses/readyz, which reflects the latest result of an in-process background database check.combinedNext Steps
- To choose between combined and sidecar sandbox pods, refer to Topology.
- To enable automatic certificate rotation with cert-manager, refer to Managing Certificates.
- To expose the gateway externally without port-forwarding, refer to Ingress.
- To configure OIDC or reverse-proxy authentication, refer to Access Control.
- To create your first sandbox, refer to Manage Sandboxes.---
Kubernetes/Topology
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Kubernetes Sandbox Topology"
sidebar-title: "Topology"
description: "Choose between combined and sidecar supervisor topology for Kubernetes sandbox pods."
keywords: "Generative AI, Cybersecurity, Kubernetes, Sandboxing, Sidecar, Network Policy, RuntimeClass"
position: 2
---Kubernetes sandbox pods can run the OpenShell supervisor in
orsidecartopology. Choose the topology based on which controls you need insidecombined
the pod and how much privilege your cluster allows on the agent container.Choose a Topology
The default
topology preserves the full OpenShell enforcement model.sidecar
Useonly when you accept network-focused enforcement in exchange for acombined
lower-privilege agent container.| Topology | Use when | Main tradeoff |
|---|---|---|
|| You need OpenShell network, filesystem, and process controls in the sandbox workload. | The agent container carries the Linux capabilities the supervisor needs. |sidecar
|| You need the agent container to run as non-root without added Linux capabilities, and network policy is the primary control. | Privilege-dropping and supervisor mount isolation do not run in the agent container. |combinedPrivilege Model
The long-running container permissions differ by topology:
| Topology | Pod or container | UID/GID | Privilege escalation | Capabilities | Result |
|---|---|---|---|---|---|
|| Agent container, which also runs the supervisor | Not forced by topology | Not explicitly disabled by the driver | AddsSYS_ADMIN,NET_ADMIN,SYS_PTRACE, andSYSLOG; addsSETUID,SETGID, andDAC_READ_SEARCHwhen user namespaces are enabled | Full supervisor controls run in the agent container. |sidecar
|| Agent container, process-only supervisor (network-only) |sandbox_uid:sandbox_gid|false| DropsALL| Agent and workload run without added Linux capabilities. |sidecar
|| Network supervisor sidecar, binary-aware mode (default) |0:sandbox_gid|false| DropsALL; addsSYS_PTRACEandDAC_READ_SEARCH| Root sidecar inspects cross-UID workload/procentries. The nftables fence exempts UID 0, so do not inject other root containers into these pods. |sidecar
|| Network supervisor sidecar, endpoint/L7-only mode |proxyUid:sandbox_gid|false| DropsALL| Non-root sidecar enforces endpoint and L7 policy without matchingpolicy.binaries. |combinedShort-lived setup containers still have the permissions needed to prepare the
pod:| Topology | Setup container | UID/GID | Privilege escalation | Capabilities | Purpose |
|---|---|---|---|---|---|
|| Supervisor install init container |0| Not set | Not set | Copies the supervisor binary into the agent container volume. |sidecar
|| Network init container |0|false| DropsALL; addsNET_ADMIN,NET_RAW,CHOWN, andFOWNER| Installs pod-local nftables rules and prepares shared sidecar state. |Combined Topology
Combined topology is the original Kubernetes mode and remains the default. The
agent container starts the OpenShell supervisor, and the supervisor launches the
workload after applying sandbox setup.
flowchart TB
Sandbox["agents.x-k8s.io Sandbox"]
subgraph Pod["Sandbox pod"]
subgraph Agent["agent container"]
Supervisor["OpenShell supervisor<br/>network + process + filesystem"]
Workload["Agent workload"]
end
end
Gateway["OpenShell Gateway"]
External["External services"]
Sandbox --> Pod
Supervisor --> Workload
Supervisor -->|"gateway callback / SSH relay"| Gateway
Supervisor -->|"policy-enforced egress"| External
Combined topology keeps these controls in one supervisor path:- Network endpoint and L7 policy enforcement.
- Filesystem policy enforcement.
- Process and binary identity checks.
- Privilege drop into the sandbox user.
- Gateway relay, SSH sessions, exec, and file sync.
Because the supervisor performs network namespace setup and process/filesystem
controls from the agent container, Kubernetes grants that container elevated
Linux capabilities. Use this mode when you need the complete OpenShell sandbox
contract and your cluster policy permits those capabilities.
Sidecar Topology
Sidecar topology splits the supervisor into a network sidecar and a
low-privilege process supervisor in the agent container.
flowchart TB
Sandbox["agents.x-k8s.io Sandbox"]
subgraph Pod["Sandbox pod"]
Init["network init container<br/>root setup capabilities"]
State["shared state + TLS volumes"]
NetNS["pod network namespace"]
subgraph Agent["agent container"]
ProcessSupervisor["process supervisor<br/>network-only"]
Workload["Agent workload"]
end
NetworkSidecar["network supervisor sidecar<br/>UID 0 by default"]
SshEndpoint["abstract SSH relay socket<br/>peer-PID authenticated"]
end
Gateway["OpenShell Gateway"]
External["External services"]
Sandbox --> Pod
Init -->|"installs nftables rules"| NetNS
ProcessSupervisor --> Workload
Workload -->|"egress redirected on loopback"| NetworkSidecar
NetworkSidecar -->|"gateway session + relays"| Gateway
NetworkSidecar -->|"policy-enforced egress"| External
NetworkSidecar -->|"control socket + proxy TLS"| State
ProcessSupervisor -->|"bootstrap + updates"| State
ProcessSupervisor --> SshEndpoint
NetworkSidecar -->|"SSH relay"| SshEndpoint
NetworkSidecar --- State
The pod contains these OpenShell-managed pieces:supervisor.sidecar.proxyUid| Component | Runs as | Purpose |
|---|---|---|
| Network init container | Root with setup capabilities | Installs pod-level nftables rules and prepares shared sidecar state. |
| Network sidecar | UID 0 by default;when binary-aware policy is disabled | Runs the proxy, enforces network policy, owns gateway authentication and the gateway session, and serves local policy/provider state over the sidecar control socket. |runAsNonRoot: true
| Agent container | Resolved sandbox UID/GID | Runs the process supervisor and launches the user workload. |In this topology, the agent container defaults to
,allowPrivilegeEscalation: false, andcapabilities.drop: ["ALL"]. TheSYS_PTRACE
default binary-aware network sidecar runs as UID 0, drops default Linux
capabilities, and addsplusDAC_READ_SEARCHfor cross-UID workloadsupervisor.sidecar.processBinaryAwareNetworkPolicy=false
process identity resolution. Settingruns the sidecar asproxyUid
the configured non-root, omits both capabilities, and downgradesnetwork-only
network policy to endpoint/L7 enforcement without binary matching. The root
init container keeps the setup capabilities needed to configure pod networking.Sidecar mode preserves gateway session behavior, including SSH connectivity,
because the network sidecar owns the gateway session and bridges relay requests
to a Linux abstract SSH socket owned by the process supervisor. The relay
verifies the socket peer PID against the authenticated control connection, so
the workload cannot replace the relay endpoint. The agent container does not get a
gateway endpoint, gateway TLS material, or the sandbox bootstrap token in the
default sidecar path.<Warning>
Sidecar mode runs the process supervisor inmode. OpenShell stillshareProcessNamespace: true
enforces network endpoint and L7 policy through the sidecar, and the process
supervisor applies Landlock filesystem policy and child seccomp filters where
the kernel/runtime supports them. The process supervisor does not perform
root-to-sandbox privilege dropping because Kubernetes starts the container as
the sandbox UID/GID, and it does not perform supervisor identity mount
isolation because gateway credentials are not mounted into the agent container.
Sidecar pods useso the network sidecar can/proc/<entrypoint-pid>
resolve workload process and binary identity through.combined
</Warning>Credential Exposure
Sidecar topology keeps gateway credentials in the network sidecar. The agent
container does not mount the projected ServiceAccount token used for sandbox
token bootstrap, does not mount the sandbox client TLS secret, and does not get
gateway callback environment variables.The network sidecar serves the policy and workload-facing provider environment
over a Unix control socket in the shared sidecar state volume. Before launching
the workload, the process supervisor establishes the only accepted connection.
The sidecar validates its UID, GID, and PID with peer credentials, unlinks the
listener, derives the SSH target from trusted configuration, and rejects later
clients. The connection receives bootstrap state and provider-environment
updates after settings polls. If it closes, the network sidecar exits so
Kubernetes recreates the one-client bootstrap listener, and the process
supervisor exits so Kubernetes terminates the workload and restarts the agent
container. This symmetric failure behavior prevents a surviving workload from
claiming the new control listener after an isolated sidecar restart. Future
child processes can see refreshed provider env without giving the agent
container gateway authentication material. This does not mutate the environment
of the already-running workload entrypoint. Usetopology when you
need the full single-supervisor enforcement path; use additional runtime
isolation when you need a stronger container boundary around sidecar workloads.RuntimeClass Isolation
Sidecar topology has been validated with Kata Containers. It does not currently
support gVisor because sidecar mode requires pod-local nftables setup, which
gVisor does not provide to the init container. A supported sandboxed runtime
strengthens the container boundary while OpenShell focuses on network policy
enforcement from the sidecar.Runtime classes do not re-enable the OpenShell privilege-drop or supervisor
mount-isolation controls that sidecar mode relaxes. Use them as an additional
workload boundary, not as a replacement for the combined topology's full
supervisor controls.You can set a default runtime class in the Kubernetes driver configuration or
override it per sandbox with driver config:
openshell sandbox create \
--driver-config-json '{"kubernetes":{"pod":{"runtime_class_name":"kata-containers"}}}' \
-- claude
Enable Sidecar Mode
For direct gateway TOML configuration, set the Kubernetes driver fields:
[openshell.drivers.kubernetes]
topology = "sidecar"
[openshell.drivers.kubernetes.sidecar]
proxy_uid = 1337
proxy_uidconfigures only the relaxed endpoint/L7-only sidecar. It must be agateway.toml
non-root UID and must not match the sandbox UID. The default binary-aware mode
runs the sidecar as UID 0 instead. The network init container exempts the
effective sidecar UID from proxy redirection so the sidecar can reach the
gateway.When the Helm chart renders
, set the equivalent chart values:
supervisor:
topology: sidecar
sidecar:
proxyUid: 1337
processBinaryAwareNetworkPolicy: true
Leavetopologyunset, or set it tocombined, to keep the originalsupervisor.topology
single-container supervisor path. For Helm installs, leaveunset or set it tocombined.supervisor.sidecar.processBinaryAwareNetworkPolicy=falseSet
only when youpolicy.binaries
accept downgrading sidecar network policy to endpoint/L7 enforcement without
matching. This changes the sidecar from UID 0 toproxyUidSYS_PTRACE
and removes itsandDAC_READ_SEARCHcapabilities, which are used/proc
for cross-UIDinspection.openshell logsNext Steps
- To install OpenShell on Kubernetes, refer to Setup.
- To configure gateway authentication, refer to Access Control.
- To review the driver fields, refer to Gateway Configuration File.---
Observability/Accessing Logs
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Accessing Logs"
description: "How to view sandbox logs through the CLI, TUI, and directly on the sandbox filesystem."
keywords: "Generative AI, Cybersecurity, Logging, CLI, TUI, Observability"
---OpenShell provides three ways to access sandbox logs: the CLI, the TUI, and direct filesystem access inside the sandbox.
CLI
Use
to stream logs from a running sandbox:
openshell logs smoke-l4 --source sandbox
The CLI receives logs from the gateway over gRPC. Each line includes a timestamp, source, level, and message:[1775014132.118] [sandbox] [OCSF ] [ocsf] NET:OPEN [INFO] ALLOWED /usr/bin/curl(58) -> api.github.com:443 [policy:github_api engine:opa]
[1775014132.190] [sandbox] [OCSF ] [ocsf] HTTP:GET [INFO] ALLOWED GET http://api.github.com/zen [policy:github_api]
[1775014132.690] [sandbox] [OCSF ] [ocsf] NET:OPEN [MED] DENIED /usr/bin/curl(64) -> httpbin.org:443 [policy:- engine:opa]
[1775014113.058] [sandbox] [INFO ] [openshell_sandbox] Starting sandbox
OCSF structured events showOCSFas the level. Standard tracing events showINFO,WARN, orERROR.openshell policy updateGateway-originated policy mutations also appear in this stream. When the gateway merges
operations or approves or removes draft policy chunks, it emitsgatewayOCSFCONFIG:*lines for the affected sandbox so you can see the exact logical change that produced a new policy revision.openshell sandbox connectTUI
The TUI dashboard displays sandbox logs in real time. Logs appear in the log panel with the same format as the CLI.
Gateway Log Storage
The sandbox pushes logs to the gateway over gRPC in real time. The gateway stores a bounded buffer of recent log lines per sandbox. This buffer is not persisted to disk and is lost when the gateway restarts.
For durable log storage, use the log files inside the sandbox or enable OCSF JSON export and ship the JSONL files to an external log aggregator.
Direct Filesystem Access
Use
to open a shell inside the sandbox and read the log files directly:
openshell sandbox connect my-sandbox
sandbox@my-sandbox:~$ cat /var/log/openshell.2026-04-01.log
You can also run a one-off command without an interactive shell:openshell sandbox connect my-sandbox -- cat /var/log/openshell.2026-04-01.log
The log files inside the sandbox contain the complete record, including events that the gRPC push channel can drop under load. The push channel is bounded and drops events rather than blocking.grepFiltering by Event Type
The shorthand format is designed for
. Some useful patterns:
All denied connections
grep "DENIED\|BLOCKED" /var/log/openshell.*.log
All network events
grep "OCSF NET:" /var/log/openshell.*.log
All L7 enforcement decisions
grep "OCSF HTTP:" /var/log/openshell.*.log
Security findings only
grep "OCSF FINDING:" /var/log/openshell.*.log
Policy changes
grep "OCSF CONFIG:" /var/log/openshell.*.log
All OCSF events, excluding standard tracing
grep "^. OCSF " /var/log/openshell..log
Events at medium severity or above
grep "\[MED\]\|\[HIGH\]\|\[CRIT\]\|\[FATAL\]" /var/log/openshell.*.log
tracingNext Steps
- Learn how the log formats work and how to read the shorthand.
- Enable OCSF JSON export for machine-readable structured output.---
Observability/Logging
---
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
title: "Sandbox Logging"
sidebar-title: "Logging"
description: "How OpenShell logs sandbox activity using standard tracing and OCSF structured events."
keywords: "Generative AI, Cybersecurity, Logging, OCSF, Observability"
---Every OpenShell sandbox produces a log that records network connections, process lifecycle events, filesystem policy decisions, and configuration changes. The log uses two formats depending on the type of event.
Log Formats
Standard tracing
Internal operational events use Rust's
framework with a conventional format:
2026-04-01T03:28:39.160Z INFO openshell_sandbox: Fetching sandbox policy via gRPC
2026-04-01T03:28:39.175Z INFO openshell_sandbox: Creating OPA engine from proto policy data
These events cover startup plumbing, gRPC communication, and internal state transitions that are useful for debugging but do not represent security-relevant decisions.OCSFOCSF structured events
Network, process, filesystem, and configuration events use the Open Cybersecurity Schema Framework (OCSF) format. OCSF is an open standard for normalizing security telemetry across tools and platforms. OpenShell maps sandbox events to OCSF v1.7.0 event classes.
In the log file, OCSF events appear in a shorthand format with an
level label, designed for quick human and agent scanning:
2026-04-01T04:04:13.058Z INFO openshell_sandbox: Starting sandbox
2026-04-01T04:04:13.065Z OCSF CONFIG:DISCOVERY [INFO] Server returned no policy; attempting local discovery
2026-04-01T04:04:13.074Z INFO openshell_sandbox: Creating OPA engine from proto policy data
2026-04-01T04:04:13.078Z OCSF CONFIG:VALIDATED [INFO] Validated 'sandbox' user exists in image
2026-04-01T04:04:32.118Z OCSF NET:OPEN [INFO] ALLOWED /usr/bin/curl(58) -> api.github.com:443 [policy:github_api engine:opa]
2026-04-01T04:04:32.190Z OCSF HTTP:GET [INFO] ALLOWED GET http://api.github.com/zen [policy:github_api engine:opa]
2026-04-01T04:04:32.690Z OCSF NET:OPEN [MED] DENIED /usr/bin/curl(64) -> httpbin.org:443 [policy:- engine:opa] [reason:no matching policy]
TheOCSFlabel at column 25 distinguishes structured events from standardINFOtracing at the same position. Both formats appear in the same file.When viewed through the CLI or TUI, which receive logs through gRPC, the same distinction applies:
[1775014132.118] [sandbox] [OCSF ] [ocsf] NET:OPEN [INFO] ALLOWED /usr/bin/curl(58) -> api.github.com:443 [policy:github_api engine:opa]
[1775014132.690] [sandbox] [OCSF ] [ocsf] NET:OPEN [MED] DENIED /usr/bin/curl(64) -> httpbin.org:443 [policy:- engine:opa] [reason:no matching policy]
[1775014113.058] [sandbox] [INFO ] [openshell_sandbox] Starting sandbox
NET:OCSF Event Classes
OpenShell maps sandbox events to these OCSF classes:
| Shorthand prefix | OCSF class | Class UID | What it covers |
|---|---|---|---|
|| Network Activity | 4001 | TCP proxy CONNECT tunnels, bypass detection, DNS failures |HTTP:
|| HTTP Activity | 4002 | HTTP FORWARD requests, L7 enforcement decisions |SSH:
|| SSH Activity | 4007 | SSH handshakes, authentication, channel operations |PROC:
|| Process Activity | 1007 | Process start, exit, timeout, signal failures |FINDING:
|| Detection Finding | 2004 | Security findings (nonce replay, proxy bypass, unsafe policy) |CONFIG:
|| Device Config State Change | 5019 | Policy load/reload, Landlock, TLS setup, inference routes |LIFECYCLE:
|| Application Lifecycle | 6002 | Sandbox supervisor start, SSH server ready |Reading the Shorthand Format
The shorthand format follows this pattern:
CLASS:ACTIVITY [SEVERITY] ACTION DETAILS [CONTEXT]
NET:OPENComponents
Class and activity (
,HTTP:GET,PROC:LAUNCH) identify the OCSF event class and what happened. The class name always starts at the same column position for vertical scanning.[INFO]Severity indicates the OCSF severity of the event:
| Tag | Meaning | When used |
|---|---|---|
|| Informational | Allowed connections, successful operations |[LOW]
|| Low | DNS failures, operational warnings |[MED]
|| Medium | Denied connections, policy violations |[HIGH]
|| High | Security findings (nonce replay, bypass detection) |[CRIT]
|| Critical | Process timeout kills |[FATAL]
|| Fatal | Unrecoverable failures |ALLOWEDAction (
,DENIED,BLOCKED) is the security control disposition. Not all events have an action; informational config events, for example, do not.process(pid) -> host:portDetails vary by event class:
- Network:
with the process identity and destinationMETHOD url
- HTTP:with the HTTP method and targetname(pid)
- SSH: peer address and authentication type
- Process:with exit code or command line
- Config: description of what changed
- Finding: quoted title with the stable finding type, optional confidence, and source-specific context attributes when availableContext in brackets provides structured fields such as policy provenance, source-specific attributes, and denial reasons.
Examples
An allowed HTTPS connection:
OCSF NET:OPEN [INFO] ALLOWED /usr/bin/curl(58) -> api.github.com:443 [policy:github_api engine:opa]
An L7 read-only policy denying a POST:OCSF HTTP:POST [MED] DENIED POST http://api.github.com/user/repos [policy:github_api engine:opa]
A connection denied because no policy matched:OCSF NET:OPEN [MED] DENIED /usr/bin/curl(64) -> httpbin.org:443 [policy:- engine:opa] [reason:no matching policy]
A connection denied because the destination resolves to an always-blocked address:OCSF NET:OPEN [MED] DENIED /usr/bin/curl(1618) -> 169.254.169.254:80 [policy:- engine:ssrf] [reason:resolves to always-blocked address]
An HTTP request to a non-default port. HTTP log URLs include the port whenever it differs from the scheme default (80 forhttp, 443 forhttps):
OCSF HTTP:GET [INFO] ALLOWED GET http://api.internal.corp:8080/v1/status [policy:internal_api engine:opa]
A supervisor middleware HTTP event records whether it transformed the request. If the middleware also emits a finding, that remains a separate event:OCSF HTTP:POST [INFO] ALLOWED POST http://httpbin.org:443/anything [policy:httpbin engine:middleware] [failed:false transformed:true]
OCSF FINDING:CREATE [MED] "configured content matched" [type:content_guard.match count:1 middleware:prototype-content-guard]
WebSocket middleware emits one safe event per preflight, session-start, or client text-message decision. The event includes the policy-local config, registered implementation, sequence, byte counts, transformation flag, and validated reason code. It never includes the message payload or service-provided free-form reason:OCSF NET:OTHER [INFO] WEBSOCKET_MIDDLEWARE allow config=api-redactor implementation=openshell/regex sequence=3 input_bytes=128 replacement_bytes=96 transformed=true reason_code=-
Coverage events are separate from invocation decisions.binding_not_selectedmeans a host-matched attachment did not advertise the WebSocket operation.unsupported_message_typemeans an active WebSocket stage encountered a binary message, which V1 passes through without inspection. Both are informational, do not applyon_error, and never claim the traffic was inspected:
OCSF NET:OTHER [INFO] WEBSOCKET_MIDDLEWARE_COVERAGE state=binding_not_selected config=http-dlp implementation=example/http-dlp sequence=- message_type=- input_bytes=0
OCSF NET:OTHER [INFO] WEBSOCKET_MIDDLEWARE_COVERAGE state=unsupported_message_type config=api-redactor implementation=openshell/regex sequence=4 message_type=binary input_bytes=512
A fail-open stream error emits both a middleware failure andopenshell.middleware.websocket_stage_disabled. The latter records that OpenShell will bypass that stage for later messages on the same connection. Waiting for saturated admission capacity also emits a detection finding without payload content. When both active capacity and the bounded wait queue are full, HTTP work is rejected before its payload is buffered, returns503 Service Unavailable, and emitsopenshell.middleware.admission_exhausted.Proxy and SSH servers ready:
OCSF NET:LISTEN [INFO] 10.200.0.1:3128
OCSF SSH:LISTEN [INFO]
An SSH connection accepted (one event per invocation, arriving over the supervisor's Unix socket, so there is no network peer address to log):OCSF SSH:OPEN [INFO] ALLOWED
A process launched inside the sandbox:OCSF PROC:LAUNCH [INFO] sleep(49)
A policy reload after a settings change:OCSF CONFIG:DETECTED [INFO] Settings poll: config change detected [old_revision:2915564174587774909 new_revision:11008534403127604466 policy_changed:true]
OCSF CONFIG:LOADED [INFO] Policy reloaded successfully [policy_hash:0cc0c2b525573c07]
NET:Denial Reasons
Denied
andHTTP:events carry a[reason:...]suffix that surfaces the decision detail from the event'sstatus_detailfield. The reason helps distinguish between policy misses, SSRF hardening, and L7 enforcement without inspecting the full OCSF JSONL record.status_detailFor supervisor middleware denials,
contains a platform-owned reason derived from the policy-local middleware config name and optional validated reason code. Middleware failure details also use platform-owned error codes. OpenShell does not copy per-request service text or WebSocket message content into logs.no matching policyCommon reason phrases emitted by the sandbox include:
| Reason | Meaning |
|---|---|
|| OPA evaluated the request and no allow rule matched. |resolves to always-blocked address
|| The destination resolved to loopback, link-local, or unspecified. These ranges are always blocked, even when listed inallowed_ips. |resolves to <ip> which is not in allowed_ips, connection rejected
|| The destination resolved to an IP outside the policy'sallowed_ipsallowlist. |DNS resolution failed for <host>:<port>
|| The proxy could not resolve the destination. |port <n> is a blocked control-plane port, connection rejected
|| The destination port matches a control-plane port (etcd, Kubernetes API, kubelet) and is always blocked. |request-target contains an encoded '/' (%2F)
|| The L7 HTTP parser rejected an encoded slash. Configureallow_encoded_slash: trueon a REST endpoint when the upstream requires encoded slashes. |l7 deny
|| An L7 policy rule denied the request. |allowed_ipsInvalid
entries and entries that overlap always-blocked ranges are rejected at policy-load time, so they never reach the runtime denial path. The phrases above come from the proxy's per-CONNECTallowed_ipsand SSRF checks, not from policy validation.403 ForbiddenProxy Error Responses
When the HTTP CONNECT proxy denies a request or cannot reach the upstream, it returns an HTTP error response with a JSON body. Clients can parse the body to surface actionable failure details instead of treating the status code alone.
A denied CONNECT returns
:
{
"error": "policy_denied",
"detail": "CONNECT api.example.com:443 not permitted by policy",
"reason": "binary '/usr/bin/node' not allowed in policy 'allow_api' (ancestors: [/usr/local/bin/claude])"
}
Thereasonfield is included when the policy engine provides a specific denial reason (for example, which binary or rule caused the rejection). It is omitted when no additional detail is available.502 Bad GatewayAn upstream that the proxy cannot reach returns
:
{
"error": "upstream_unreachable",
"detail": "connection to api.example.com:443 failed"
}
Theerrorfield is a short machine-readable code (policy_denied,middleware_denied,middleware_failed,ssrf_denied,upstream_unreachable). Thedetailfield is a human-readable explanation suitable for display in an agent transcript. The optionalreasonfield, when present, provides the specific denial cause from the policy engine (for example, which binary was not allowed or which rule was missing).methodFor L7 REST policy denials, the body also includes structured policy fields such as
,path,rule_missing, andnext_steps. When policy advisor is enabled, it also includesagent_guidance, a short plain-language instruction telling the agent to read/etc/openshell/skills/policy_advisor.md, propose the narrowest rule throughhttp://policy.local/v1/proposals, wait forpolicy_reloaded: true, and retry. A middleware denial instead identifies the policy-local config inmiddlewareand can include a validatedreason_code. A fail-closed runtime failure usesmiddleware_failedwith platform-owned text. Both middleware responses omitrule_missing,next_steps, andagent_guidancebecause no policy rule is missing.CONFIG:Filesystem Sandbox Logs
Landlock filesystem restrictions emit
events at startup and whenever the sandbox has to skip a requested path.On startup, the probe reports the kernel's supported Landlock ABI version alongside the requested path counts:
OCSF CONFIG:ENABLED [INFO] Landlock filesystem sandbox available [abi:v2 compat:BestEffort ro:4 rw:2]
OCSF CONFIG:ENABLED [INFO] Applying Landlock filesystem sandbox [abi:V2 compat:BestEffort ro:4 rw:2]
OCSF CONFIG:ENABLED [INFO] Landlock ruleset built [rules_applied:5 skipped:1]
Whenlandlock.compatibilityisbest_effortand a requested path fails to open for reasons other thanNotFound(for example, permission denied or a symlink loop), the sandbox continues without that path and emits a[MED]event so the degradation is not silent:
OCSF CONFIG:OTHER [MED] Skipping inaccessible Landlock path (best-effort) [path:/opt/data error:Permission denied (os error 13)]
`
Set
landlock.compatibility to hard_requirement in the policy to make these failures fatal instead of degraded.Log File Location
Inside the sandbox, logs are written to
/var/log/:| File | Format | Rotation |
|---|---|---|
|
openshell.YYYY-MM-DD.log | Shorthand + standard tracing | Daily, 3 files max |
| openshell-ocsf.YYYY-MM-DD.log` | OCSF JSONL when enabled | Daily, 3 files max |Both files rotate daily and retain the 3 most recent files to bound disk usage.
Next Steps
- Access logs through the CLI, TUI, or sandbox filesystem.
- Enable OCSF JSON export for SIEM integration and compliance.
- Learn about network policies that generate these events.
---