How to Sandbox an AI Coding Agent with NVIDIA OpenShell: Install, Network Policy, First Agent
If you let a coding agent install packages, call APIs and hold API keys on your machine, you can now put it in a sandbox where nothing reaches the network unless a policy says so, and the agent never sees the real key. That is what NVIDIA’s open-source OpenShell runtime does, and on October 21, 2026 NVIDIA made it the base of its new OpenShell Reference Architecture for running agents in enterprises. You don’t need any of the data-center hardware from that announcement to use the runtime. This guide installs OpenShell on a laptop or workstation, shows the default-deny policy blocking a request, adds a read-only rule for one API, runs the OpenCode agent with a credential that only works at one host, and walks through approving new access as the agent needs it.
All commands and behaviour below come from NVIDIA’s own material: the OpenShell README, the official docs (Installation, Run Your First Agent, Write Your First Sandbox Network Policy, Manage Policies, Default Policy and the Support Matrix), the v0.1.5 release notes, NVIDIA’s press release and its technical blog post. OpenShell is still on a 0.1.x version line and changes quickly, so check flags against the version you install.
What was announced, and what you can use today
NVIDIA announced the OpenShell Reference Architecture at GTC Berlin on October 21, 2026. The full stack pairs Vera Rubin systems for model inference, Vera CPUs for running agent harnesses, and BlueField-4 DPUs running a security framework called NVIDIA Sentry that, in NVIDIA’s words, “quarantines and stops” an agent “in milliseconds” if it tries to move outside its software boundary. NVIDIA lists Anthropic, AWS, Google Cloud, Microsoft, OpenAI and others among companies working with the architecture, and security vendors including 1Password, CrowdStrike, Okta, Palo Alto Networks and Zscaler as integrating support.
The part a single developer can run right now is the software at its centre: OpenShell, an Apache-2.0 runtime on GitHub. NVIDIA’s technical blog describes four components:
- Gateway: the control plane that manages sandbox lifecycles and policies.
- Supervisor: checks outbound requests against policy and mediates access to credentials, running separately from the sandbox.
- Sandbox: isolates the agent workload and restricts filesystem and process access.
- Policy prover: uses formal verification to check what a policy change would allow before it is applied.
The README sums up the model: “You declare what each agent can touch in a policy, and OpenShell enforces it.” Enforcement happens in the kernel and in a proxy outside the agent’s process, so a prompt-injected agent cannot talk its way past it. Agents “never see real credentials”; OpenShell adds them only to requests bound for approved endpoints.
If you have read our coverage of how Anthropic sandboxes Claude or what OpenAI learned from agents misbehaving in sandboxes, OpenShell is a general-purpose way to apply the same ideas to any agent you run.
Before you start: requirements
From the README and the Support Matrix:
- Operating system: Linux, macOS on Apple Silicon, or Windows with WSL 2. Windows support is marked experimental.
- A runtime the local gateway can use: Docker (Docker Desktop or Docker Engine 28.0 or later), Podman (Linux with Podman 5.x, cgroups v2 and an active Podman user socket), or a MicroVM using host virtualization (Hypervisor.framework on macOS, KVM on Linux).
- Linux kernel features: the sandbox needs the Landlock security module at ABI 3 or newer (introduced in Linux 6.2) and enabled, plus specific seccomp features. The docs warn that “a kernel version alone does not establish support”: if Landlock is disabled or your container runtime’s profile blocks the needed seccomp operations, the sandbox refuses to start rather than running unprotected. On macOS these checks happen inside the Docker Desktop Linux VM, not on the macOS kernel.
- Linux packages need glibc 2.28 or newer.
For the agent part of this guide you’ll also want an OpenRouter API key. The official example uses a free model, so it works without OpenRouter credits.
Step 1: Install OpenShell and the local gateway
The official one-line installer sets up the CLI, the policy prover and a local gateway:
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
As with any curl | sh installer, you can download install.sh and read it first. To pin the current stable release instead of whatever is latest, the v0.1.5 release notes give this form:
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | OPENSHELL_VERSION=v0.1.5 sh
Then confirm the CLI can reach the gateway:
openshell status
What the installer does depends on your platform:
| Platform | How it installs | Where the gateway runs |
|---|---|---|
| macOS | Homebrew package, gateway as a Homebrew service | https://localhost:17670 |
| Debian / Ubuntu | Debian package, gateway as a systemd user service | https://127.0.0.1:17670 |
| Fedora / RHEL | RPM package, gateway as a systemd user service | https://127.0.0.1:17670 |
| Snap (opt-in) | OPENSHELL_INSTALL_METHOD=snap | https://127.0.0.1:17670 |
On Linux, these are the commands to check and restart the gateway:
systemctl --user status openshell-gateway
systemctl --user restart openshell-gateway
journalctl --user -u openshell-gateway -f
The gateway is a user service, so it stops when you log out. To keep it running, enable linger:
sudo loginctl enable-linger $USER
On macOS, use brew services list and brew services restart openshell.
Telemetry: OpenShell collects anonymous operational counts by default. The README says it does not collect sandbox names, hostnames, file paths, prompts, credentials, provider or model names, or user content. To turn it off, set OPENSHELL_TELEMETRY_ENABLED=false on the gateway (or server.telemetryEnabled=false for Helm installs).
Step 2: Understand the default policy
Before adding anything, know what a sandbox gets when no other policy applies. The Default Policy reference spells it out:
- Network: no network rules, so all outbound network access is denied.
- Filesystem: the sandbox working directory is read-write;
/bin,/usr,/lib,/proc,/dev/urandom,/etcand/var/logare read-only;/tmpand/dev/nullare read-write. - Process identity: on Docker and Podman, the workload runs as the image’s non-root
USER, or as UID/GID 1000 if the image declares none. An image whose user is root is rejected unless your policy sets a non-rootrun_as_user.
One detail catches people out: the restrictive default applies only when there is no global policy, no saved policy for the sandbox, and no policy inside the image. So “I didn’t pass --policy” does not prove you’re on the default. Check what a sandbox is really using with:
openshell policy get <sandbox> --base
openshell policy get <sandbox> --full
--base is the policy you set; --full is the effective policy, including rules that attached providers add.
Step 3: Watch default-deny block a request
This step follows NVIDIA’s first network policy tutorial. You need a sandbox image that has curl in it. The docs use a placeholder name; replace it with an image you own:
openshell sandbox create \
--name demo \
--from registry.example.com/team/agent-tools:1.0 \
--no-auto-providers
--no-auto-providers skips the provider setup prompt, since this step uses curl rather than an AI agent. You land in a shell inside the sandbox (sandbox@demo:~$). Try to reach the GitHub API:
curl -s https://api.github.com/zen
It fails:
curl: (56) Received HTTP code 403 from proxy after CONNECT
The sandbox proxy intercepted the HTTPS connection to api.github.com:443 and rejected it because no rule lets curl reach that host.
Keep this sandbox shell open and open a second terminal on your host. The docs warn that exiting the shell stops the sandbox’s main process, and with the default restart policy you can’t reconnect to it afterwards. (If you want throwaway sandboxes that delete themselves on exit, add --no-keep when you create them.)
From the host terminal, read the deny log:
openshell logs demo --since 5m --source sandbox
You’ll see a line naming the binary, destination and reason, like this example from the docs:
[sandbox] [OCSF ] [ocsf] NET:OPEN [MED] DENIED /usr/bin/curl(64) -> api.github.com:443 [policy:- engine:opa] [reason:network connections not allowed by policy]
Every blocked connection is logged this way, so an agent that tries to reach somewhere unexpected leaves a record.
Step 4: Add a read-only rule for one API
Still on the host, give curl read-only access to the GitHub API:
openshell policy update demo \
--rule-name github_api \
--binary /usr/bin/curl \
--add-endpoint api.github.com:443:read-only:rest:enforce \
--wait
The endpoint value is five fields separated by colons:
| Field | Value here | Meaning |
|---|---|---|
| host | api.github.com | Destination hostname |
| port | 443 | Destination port |
| access preset | read-only | Allows GET, HEAD and OPTIONS only |
| protocol | rest | The proxy terminates TLS and inspects each HTTP request |
| enforcement | enforce | Blocks every request the rule doesn’t allow |
policy update only touches the network rules and keeps the rest of the policy. --wait blocks until the sandbox reports it loaded the new revision. No restart is needed. The equivalent YAML in the policy’s network_policies section is:
network_policies:
github_api:
endpoints:
- host: api.github.com
port: 443
protocol: rest
enforcement: enforce
access: read-only
binaries:
- path: /usr/bin/curl
Note the binaries list: the rule is tied to one executable. Another program inside the same sandbox still can’t reach GitHub. The --binary value must be the real path of the executable inside the sandbox.
Back in the sandbox shell, the read now works:
curl -s https://api.github.com/zen
Now try a write:
curl -s -X POST https://api.github.com/repos/octocat/hello-world/issues \
-H "Content-Type: application/json" \
-d '{"title":"oops"}'
The connection is allowed, because the host is allowed, but the proxy inspects the HTTP method and returns a 403 with "error":"policy_denied" and the rule that blocked it. That is the practical value of request-level (L7) rules: an agent can read code from GitHub but cannot open issues, push or delete anything. Running openshell logs demo --since 5m --source sandbox again shows an HTTP:POST ... DENIED line with the exact method and path.
Two tips from the docs for real workloads:
- Policy events are INFO-level records whatever their severity, so don’t filter logs with
--level warnor you’ll hide them. - To build a policy gradually, use
enforcement: auditinstead ofenforce. Violations are logged but not blocked. Run in audit mode, review the logs, then switch toenforce.
If you need one specific write, add an explicit allow rule rather than switching to read-write. This example from Manage Policies allows issue creation only:
openshell policy update demo \
--rule-name github_api \
--binary /usr/bin/curl \
--add-allow 'api.github.com:443:POST:/repos/*/*/issues' \
--wait
Replace --wait with --dry-run to preview a change. To remove the rule entirely: openshell policy update demo --remove-rule github_api --wait.
Clean up when you’re done:
openshell sandbox delete demo
Deleting a sandbox stops its processes and purges any injected credentials.
Step 5: Run a real agent with a scoped credential
Now run an actual coding agent. NVIDIA’s Run Your First Agent guide uses OpenCode talking to OpenRouter. (If you already use OpenCode, our OpenCode 2.0 upgrade notes cover its recent breaking changes.)
5a. Set up the provider. A provider profile declares which credentials exist, which endpoints they may be sent to, and which executables may use them. NVIDIA’s example OpenRouter profile sends OPENROUTER_API_KEY as a bearer token to openrouter.ai on port 443 “and nowhere else”, with read-write L7-enforced access, and only for the binary /usr/local/bin/opencode. The profile’s own comments say to copy and edit it rather than import it unchanged, because the binaries list must match the paths in your image. For the stock OpenCode image it already does.
Export your key, import the profile and create the provider:
export OPENROUTER_API_KEY=... # your key
openshell profile import \
--url https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/openrouter.yaml
openshell provider create \
--name openrouter \
--type openrouter \
--from-existing
The key must be set before you create the provider. OpenShell stores it in its credential store and, per the docs, “sends it only to openrouter.ai”. The agent process never holds the real value.
5b. Create the sandbox with the agent as its main process. Pass the agent command after --:
openshell sandbox create \
--name my-agent \
--from ghcr.io/anomalyco/opencode:latest \
--provider openrouter \
-- opencode -m openrouter/nvidia/nemotron-3.5-lightning:free
OpenCode finds the OpenRouter credential and starts with the model passed to -m. You can pick any model listed by opencode models openrouter.
To give the agent code to work on, upload a directory from the host:
openshell sandbox upload my-agent ./src
5c. Approve access as the agent asks for it. The agent will soon try something the policy doesn’t allow: a package registry, a docs site, a Git host. OpenShell denies the request and, according to the docs, drafts a narrow rule for it automatically (one binary, one host and port). The gateway runs each draft through the policy prover before you see it. List what’s waiting:
openshell rule get my-agent --status pending
Approve a proposal whose rule matches what you meant to grant, or reject it with a reason the agent will receive:
openshell rule approve my-agent --chunk-id <chunk-id>
openshell rule reject my-agent \
--chunk-id <chunk-id> \
--reason "Not needed for this task."
There is also openshell rule approve-all my-agent. It skips anything the security checks flagged unless you add --include-security-flagged, and tells you how many it skipped. Approved rules hot-reload into the running sandbox, and the agent retries the request.
By default the agent itself can’t file requests for access; OpenShell only drafts them from blocked connections. The optional policy advisor, off by default, gives the agent a local API to propose its own narrower rules (for HTTP APIs, down to method and path). A proposal never changes the policy on its own; it waits for you unless you opt into automatic approval when the risk checks find nothing.
Step 6: Move from one-off rules to a policy file
Once you know what an agent needs, put it in a file so every sandbox starts the same way:
openshell sandbox create --name my-sandbox --policy ./policy.yaml
The easiest starting point is the policy you built interactively. Export it with openshell policy get my-sandbox --base and edit it. To replace a running sandbox’s whole policy:
openshell policy set my-sandbox --policy policy.yaml --wait
What changes live and what needs a new sandbox is the main thing to remember:
| Change | Running sandbox | What to do |
|---|---|---|
| Network rules | Loads the new rules; connections opened under the old rules are closed | Apply, then retry the request |
| Added filesystem paths | Saved, but the running workload keeps its old filesystem permissions | Recreate the sandbox |
Removed filesystem paths, include_workdir, Landlock or process settings | Rejected once the workload has started | Recreate the sandbox |
Recreating means openshell sandbox delete my-sandbox then openshell sandbox create --name my-sandbox --policy ./policy.yaml. Copy out anything you need first.
Every change is checked twice, once by the gateway and once by the sandbox. If the sandbox can’t load a revision, the default fail_closed mode blocks network traffic until you submit a valid policy; retain_last_valid (set in the gateway configuration) keeps the last good one instead. To roll back, list revisions with openshell policy list my-sandbox, print an old one with openshell policy get my-sandbox --rev 2 --base, save it and policy set it.
Upgrading to 0.1.5: things that can break
If you already run OpenShell, the v0.1.5 release notes (October 20, 2026) include several upgrade notes:
- Log collectors: supervisor OCSF log files are now named
openshell-ocsf.YYYY-MM-DD.jsonlinstead of.log. Update collector patterns, and collect any history you need first, because old.logfiles aren’t migrated and may be cleaned up. - Kubernetes: sandbox templates that set pod annotations or labels are now rejected unless an operator allowlists the keys with
allowed_pod_annotationsandallowed_pod_labels. TheopenshellCLI and its--labelflag aren’t affected. - Go SDK:
SandboxInterface.Create()andCreateFromTemplate()now take functional options such asWithLabels()instead of a positional labels argument. - Snap: the snap no longer enables the user gateway for every user. If an older revision enabled it globally, run
sudo systemctl disable --global snap.openshell.user-gateway.serviceonce, then enable it from the one account that should run it.
Useful additions in the same release: openshell sandbox create --env-from KEY[=ENVVAR] sets sandbox environment variables from your shell without putting values on the command line, and a fix means stopping a MicroVM sandbox no longer discards recent writes.
Troubleshooting
openshell status can’t reach the gateway. On Linux, check systemctl --user status openshell-gateway and follow journalctl --user -u openshell-gateway -f. If it stopped after you logged out, enable linger (Step 1). On macOS, brew services restart openshell. Only one user gateway can bind 127.0.0.1:17670 at a time, so two users running one each will conflict.
The sandbox fails to start on Linux. Check the kernel requirements: Landlock ABI 3+ and enabled, plus the seccomp features in the Support Matrix. OpenShell is designed to fail closed rather than start without the boundary. Older distribution kernels and locked-down container runtime profiles are the usual causes.
An image is rejected because it runs as root. Docker and Podman refuse root images unless the policy you pass at creation sets a non-root run_as_user. Either set that, or build the image with a non-root USER.
A request is still blocked after you added a rule. Check the --binary path is the real executable path inside the image, not a symlink you assumed. Run openshell policy get <sandbox> --full to see the effective policy, and read openshell logs <sandbox> --source sandbox (without --level warn) for the exact deny reason.
You added a filesystem path and nothing changed. Filesystem changes only apply when a sandbox starts. Recreate it.
A sandbox is stuck in Error after a failed start. Since 0.1.5 you can stop it and start it again without deleting it or its persistent workspace.
FAQ
Do I need NVIDIA hardware? Not for the runtime. The requirements are an OS, a container runtime or MicroVM, and the kernel features above. The Vera, Rubin and BlueField-4 parts belong to the enterprise reference architecture. GPU sandboxes are optional (openshell sandbox create --gpu).
Does the agent ever see my API key? Per the README, no: OpenShell adds credentials only to requests bound for approved endpoints, and a provider profile limits which binaries can use them.
Can I run Claude Code, Codex or another agent instead of OpenCode? The docs say OpenShell “can run any agent that is available in a sandbox image”. Build an image with the agent in it, create a provider profile for its API with the right binary path, and scope network rules to that binary (the docs give /usr/local/bin/claude as an example). For agent setup itself, see our Claude Code daily-driver guide.
Is this a replacement for securing my model servers? No. OpenShell limits what an agent can do from inside a sandbox. It doesn’t protect an inference server you’ve exposed to the internet. For that, see how to lock down Ollama, vLLM and llama.cpp endpoints.
Can my coding agent drive OpenShell for me? The repo ships Agent Skills that teach an agent to use the CLI, write sandbox policies and debug gateways: npx skills add NVIDIA/OpenShell.
Where do I run it in a team? The gateway can be deployed to Kubernetes with the official Helm chart. The docs note that your CNI must enforce NetworkPolicy. See Kubernetes setup.
Bottom line
The OpenShell Reference Architecture announcement is about data-center hardware, but the useful part for most builders is the free runtime underneath it. The steps above get you an agent that starts with zero network access, holds no real secrets, and can only gain access you approve, with every denied request logged. Start with audit mode on a project you know, turn the logged requests into a policy file, then switch to enforce.
More security guides on 7minai: continuous scanning with Claude Security and hardening self-hosted LLM endpoints.