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.
What OpenShell writes
Section titled “What OpenShell writes”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},...}| Event | OCSF class | Shield360 span |
|---|---|---|
| Connection allowed or denied | 4001 Network Activity | openshell.network |
| HTTP request allowed or denied | 4002 HTTP Activity | openshell.http |
| SSH session into the sandbox | 4007 SSH Activity | openshell.ssh |
| Process launched (workload container) | 1007 Process Activity | openshell.process |
| Policy loaded or replaced | 5019 Device Config State Change | openshell.config |
| Detection finding | 2004 Detection Finding | openshell.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 runs | Supervisor 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. |
Before you start
Section titled “Before you start”- 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.
openshell settings set --global --key ocsf_json_enabled --value true --yesWithout 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.
Discovery
Section titled “Discovery”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:
shield360 collector openshell sandboxes # installed?, container engines, running sandboxesshield360 collector ai-software # the inventory as the collector reports itDecisions
Section titled “Decisions”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:
| Attribute | Example |
|---|---|
openshell.decision | allow, deny |
openshell.event.class | network, http, ssh, process, config |
openshell.destination.host, openshell.destination.port | api.github.com, 443 |
openshell.http.method | POST |
openshell.http.path | /repos/x/y/issues — only at content capture Full |
openshell.process.executable | /usr/bin/curl |
openshell.agent | claude — the binary the sandbox started |
openshell.policy.rule, openshell.policy.engine | github_readonly, l7 |
openshell.reason | transparent_tcp_policy_denied |
openshell.policy.hash, openshell.policy.changed | on policy loads |
openshell.sandbox.id, openshell.sandbox.name, openshell.sandbox.workspace | the sandbox |
openshell.version, openshell.ocsf.version | 0.1.5, 1.8.0 |
What is never sent
Section titled “What is never sent”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).
Where decisions appear
Section titled “Where decisions appear”- 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 atGET /api/openshell/decisionswith 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: deniedandpolicy decision: blockwith the rule as the policy, allowed connections areallow, 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) andopenshell.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_ATTRIBUTESlistsopenshell.http.path.
- Event alerts
Kubernetes
Section titled “Kubernetes”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/v1kind: DaemonSetmetadata: name: shield360-openshell namespace: shield360spec: 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.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Check |
|---|---|
| No OpenShell asset | shield360 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 run | The 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 decisions | Network 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 version | OpenShell’s ocsf_json_enabled is off; the collector is reading the shorter lines. |