Skip to content

NVIDIA OpenShell sandboxes

NVIDIA OpenShell runs AI agents in sandboxes. A policy decides which hosts, HTTP methods and paths each binary in a sandbox may reach, and the sandbox’s supervisor logs every decision as an OCSF event. Shield360 works with it in two ways:

  • Discovery. The endpoint collector reports the OpenShell CLI and gateway, with their versions and how many sandboxes are running, in the AI inventory.
  • Decisions. On a machine where OpenShell runs, the collector reads the sandboxes’ logs and sends each allow and deny decision, policy load and process launch to Shield360. They appear under Detections → Sandbox decisions, in activity provenance as audit evidence, as detection-rule signals, and as alerts.

Shield360 does not change OpenShell’s policies here; it records what they decided.

Tested with OpenShell v0.1.5 (October 2026), which writes OCSF 1.8.0. Releases up to v0.0.108 wrote OCSF 1.7.0; Shield360 reads both, and the 1.1 and 1.3 schemas OpenShell can downgrade to with its ocsf_schema_version setting.

Each sandbox has two containers: the workload, where the agent runs, and the supervisor, which enforces the policy. The supervisor writes to its standard error:

2026-10-23T19:29:51.293Z OCSF NET:OPEN [INFO] ALLOWED /usr/bin/curl(0) -> api.github.com:443 [policy:github_readonly engine:opa]
2026-10-23T19:29:51.563Z OCSF HTTP:POST [MED] DENIED POST https://api.github.com/repos/x/y/issues [policy:github_readonly engine:l7] [reason:...]
2026-10-23T19:29:51.862Z OCSF NET:OPEN [MED] DENIED /usr/bin/curl(0) -> httpbin.org:443 [reason:transparent_tcp_policy_denied]

With OpenShell’s ocsf_json_enabled setting on, each of those lines is followed by the full event:

2026-10-23T19:29:51.862Z OCSF-JSON {"class_uid":4001,"class_name":"Network Activity","activity_name":"Open","action_id":2,"action":"Denied","dst_endpoint":{"domain":"httpbin.org","port":443},...}
EventOCSF classShield360 span
Connection allowed or denied4001 Network Activityopenshell.network
HTTP request allowed or denied4002 HTTP Activityopenshell.http
SSH session into the sandbox4007 SSH Activityopenshell.ssh
Process launched (workload container)1007 Process Activityopenshell.process
Policy loaded or replaced5019 Device Config State Changeopenshell.config
Detection finding2004 Detection Findingopenshell.finding

OpenShell v0.1.5 writes no file-system events: Landlock refuses a file access in the kernel and nothing is logged. Policy-DNS bookkeeping, settings polls, relay chatter and Landlock setup are OCSF events too; Shield360 does not keep them.

Where the logs live:

Where OpenShell runsSupervisor log
Docker or Podman (macOS, Linux)The supervisor container’s output: docker logs openshell-<workspace>--<sandbox>-<id>-supervisor. Containers carry the labels openshell.ai/managed-by=openshell, openshell.ai/sandbox-id, openshell.ai/sandbox-name and openshell.ai/isolation-role=supervisor.
Kubernetes (Helm chart)Container supervisor in each sandbox’s supervisor pod: kubectl logs <pod> -c supervisor, on the node under /var/log/pods/<namespace>_<pod>_<uid>/supervisor/.
Inside the supervisor/var/log/openshell.YYYY-MM-DD.log (shorthand) and, with JSON on, /var/log/openshell-ocsf.YYYY-MM-DD.jsonl, on a temporary file system.
  • A Shield360 collector enrolled on the machine where OpenShell runs (see Deploy the collector), version with OpenShell support.
  • Network collection on for the people who run OpenShell (Settings → Collection policy, Network at Connections or above). Decisions name the hosts sandboxes reached, so they follow the same switch as network observation.
  • Optional, for the most detail: OpenShell’s full OCSF events.
Terminal window
openshell settings set --global --key ocsf_json_enabled --value true --yes

Without it the collector reads the shorter lines, which carry the decision, destination, binary and rule but not the sandbox’s event ids or OCSF version.

The collector looks for OpenShell cheaply: the openshell and openshell-gateway binaries where the installers put them (/opt/homebrew/bin, /usr/local/bin, /usr/bin, /snap/bin, ~/.local/bin), the configuration directories (~/.config/openshell, ~/.local/state/openshell, /etc/openshell) and a process named openshell-gateway listening on port 17670. Only when one of those is there does it ask the container engine (Docker Desktop, Docker Engine, Colima, OrbStack, Rancher Desktop or Podman, over its local socket) for OpenShell’s running sandboxes, with one list call filtered by OpenShell’s label.

In the AI inventory, OpenShell is one tool asset, NVIDIA OpenShell, with an install row for the CLI (its version and path) and one for the gateway (Agent sandbox gateway: its version, its address and how many sandboxes are running). The count is reported again whenever it changes, within ten minutes.

To check a machine:

Terminal window
shield360 collector openshell sandboxes # installed?, container engines, running sandboxes
shield360 collector ai-software # the inventory as the collector reports it

Every 15 seconds, on a machine where OpenShell is found, the collector reads what each sandbox container logged since it last looked (at most 4 MiB per container per pass; a container seen for the first time is read from an hour back) and sends the decisions to the machine’s own OTLP entrance, so they land in the database of the project the machine is enrolled to. A batch the platform did not accept is read again on the next pass; batches it accepted are not. Each decision is written once even when it is sent again (a request that timed out after the write, for example): Shield360 remembers for a day, per machine, which decisions it already wrote, in Redis when the deployment has REDIS_URL.

Each decision is one span with these attributes:

AttributeExample
openshell.decisionallow, deny
openshell.event.classnetwork, http, ssh, process, config
openshell.destination.host, openshell.destination.portapi.github.com, 443
openshell.http.methodPOST
openshell.http.path/repos/x/y/issues — only at content capture Full
openshell.process.executable/usr/bin/curl
openshell.agentclaude — the binary the sandbox started
openshell.policy.rule, openshell.policy.enginegithub_readonly, l7
openshell.reasontransparent_tcp_policy_denied
openshell.policy.hash, openshell.policy.changedon policy loads
openshell.sandbox.id, openshell.sandbox.name, openshell.sandbox.workspacethe sandbox
openshell.version, openshell.ocsf.version0.1.5, 1.8.0

Nothing sensitive is stored unless your organisation opts in:

  • never a query string, a request or response body, a header or a credential;
  • never a command line, the free-text message (it repeats the path), or the addresses OpenShell’s policy DNS resolved;
  • the HTTP path only where the person’s content capture is Full. The collector leaves it out otherwise, and Shield360 takes it out again on arrival whatever a machine sends;
  • with network collection off for a person, nothing: the collector does not read, and Shield360 drops any openshell.* span that arrives.

To stop reading OpenShell’s logs on one machine whatever the platform says, create ~/.config/shield360/disable-openshell (the collector’s configuration directory).

  • Detections → Sandbox decisions. Counts, the most denied destinations, and each decision with its binary, agent, rule and sandbox. Filter by decision, event, sandbox or destination. Needs the View activity provenance permission (provenance.read); also at GET /api/openshell/decisions with an API key.
  • Activity provenance. With recording on, each decision is a record with source OpenShell sandbox: the sandbox is the session, a denial is result: denied and policy decision: block with the rule as the policy, allowed connections are allow, policy loads are type Sandbox policy. The records join the tamper-evident chain kept as compliance evidence.
  • SIEM export. The Agent sandboxes category (agent_sandbox) sends each decision to your SIEM as the OCSF class OpenShell wrote it as, from what Shield360 kept of it. See SIEM & security tools.
  • Detection rules. The OpenShell sandbox decisions signal: thresholds, first-seen destinations or sequences by sandbox, binary, destination or agent.
  • Alerts.
    • Event alerts openshell.denied (one per sandbox, destination and rule per hour) and openshell.policy_changed (a sandbox replaced its policy while running). Route them with an Event rule.
    • Span rules, checked as each span arrives: start from the OpenShell sandbox denial or OpenShell policy change template, or write conditions on any attribute above. The HTTP path is usable only when SHIELD360_SIGNAL_ATTRIBUTES lists openshell.http.path.

On Kubernetes no endpoint collector runs beside the sandboxes, so run the collector’s OpenShell reader on each node as a DaemonSet that reads the supervisor containers’ log files:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: shield360-openshell
namespace: shield360
spec:
selector:
matchLabels: { app: shield360-openshell }
template:
metadata:
labels: { app: shield360-openshell }
spec:
containers:
- name: reader
# The shield360 CLI image, built from cli/Dockerfile.
image: <your registry>/shield360-cli:<version>
args: ["collector", "openshell", "tail", "--pods-glob", "/var/log/pods/*/supervisor/*.log"]
env:
# The collector keeps its enrollment and read positions in
# $XDG_CONFIG_HOME/shield360.
- name: XDG_CONFIG_HOME
value: /var/lib/shield360
volumeMounts:
- { name: pods, mountPath: /var/log/pods, readOnly: true }
- { name: state, mountPath: /var/lib/shield360 }
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { memory: 128Mi }
volumes:
- name: pods
hostPath: { path: /var/log/pods }
- name: state
hostPath: { path: /var/lib/shield360-openshell, type: DirectoryOrCreate }

Enroll the state directory once (shield360 collector enroll with the organisation’s install key and the same XDG_CONFIG_HOME, for example from a one-off Job using the same volume). The reader runs as root to read the node’s pod logs, so the organisation-wide network collection setting applies. Each pod’s sandbox name comes from the event itself with OCSF JSON on, and from the pod name otherwise. The glob reads every container named supervisor on the node; narrow it to OpenShell’s sandbox namespace, for example /var/log/pods/openshell_*/supervisor/*.log.

To test a reader without sending, run it with --dry-run --once: it prints the spans it would send.

SymptomCheck
No OpenShell assetshield360 collector openshell sandboxes: is it installed where the collector looks, and is the AI software setting on for the person?
Gateway with 0 sandboxes running while sandboxes runThe collector cannot reach the container engine’s socket. A collector running as a system service reads /var/run/docker.sock; Docker Desktop’s per-user socket needs the default-socket option on, or a per-user collector.
No decisionsNetwork collection is off for the person, or the kill-switch file exists. shield360 collector openshell tail --dry-run --once shows what would be sent.
Decisions without event ids or OCSF versionOpenShell’s ocsf_json_enabled is off; the collector is reading the shorter lines.