> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/openshell/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/openshell/_mcp/server.

# Sandbox Logging

> How OpenShell logs sandbox activity using standard tracing and OCSF structured events.

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 `tracing` framework with a conventional format:

```text
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.

### OCSF structured events

Network, process, filesystem, configuration, and API activity events use the [Open Cybersecurity Schema Framework (OCSF)](https://ocsf.io) format. OCSF is an open standard for normalizing security telemetry across tools and platforms. OpenShell maps sandbox events to OCSF v1.8.0 event classes.

In the log file, OCSF events appear in a shorthand format with an `OCSF` level label, designed for quick human and agent scanning:

```text
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]
```

The `OCSF` label at column 25 distinguishes structured events from standard `INFO` tracing 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:

```text
[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
```

## OCSF Event Classes

OpenShell maps sandbox events to these OCSF classes:

| Shorthand prefix | OCSF class                 | Class UID | What it covers                                                         |
| ---------------- | -------------------------- | --------- | ---------------------------------------------------------------------- |
| `NET:`           | 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, provider and settings changes |
| `LIFECYCLE:`     | Application Lifecycle      | 6002      | Sandbox supervisor start, SSH server ready                             |

## Reading the Shorthand Format

The shorthand format follows this pattern:

```text
CLASS:ACTIVITY [SEVERITY] ACTION DETAILS [CONTEXT]
```

### Components

**Class and activity** (`NET:OPEN`, `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.

**Severity** indicates the OCSF severity of the event:

| Tag       | Meaning       | When used                                          |
| --------- | ------------- | -------------------------------------------------- |
| `[INFO]`  | 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                             |

**Action** (`ALLOWED`, `DENIED`, `BLOCKED`) is the security control disposition. Not all events have an action; informational config events, for example, do not.

**Details** vary by event class:

* Network: `process(pid) -> host:port` with the process identity and destination
* HTTP: `METHOD url` with the HTTP method and target
* SSH: peer address and authentication type
* Process: `name(pid)` 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 available

**Context** in brackets provides structured fields such as policy provenance, source-specific attributes, and denial reasons.

### Examples

An allowed HTTPS connection:

```text
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:

```text
OCSF HTTP:POST [MED] DENIED POST http://api.github.com/user/repos [policy:github_api engine:opa]
```

A connection denied because no policy matched:

```text
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:

```text
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 for `http`, 443 for `https`):

```text
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:

```text
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:

```text
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_selected` means a host-matched attachment did not advertise the WebSocket operation. `unsupported_message_type` means an active WebSocket stage encountered a binary message, which V1 passes through without inspection. Both are informational, do not apply `on_error`, and never claim the traffic was inspected:

```text
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 and `openshell.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, returns `503 Service Unavailable`, and emits `openshell.middleware.admission_exhausted`.

Proxy and SSH servers ready:

```text
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):

```text
OCSF SSH:OPEN [INFO] ALLOWED
```

A process launched inside the sandbox:

```text
OCSF PROC:LAUNCH [INFO] sleep(49)
```

A policy reload after a settings change:

```text
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]
```

## Denial Reasons

Denied `NET:` and `HTTP:` events carry a `[reason:...]` suffix that surfaces the decision detail from the event's `status_detail` field. The reason helps distinguish between policy misses, SSRF hardening, and L7 enforcement without inspecting the full OCSF JSONL record.

For supervisor middleware denials, `status_detail` 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.

Common reason phrases emitted by the sandbox include:

| Reason                                                              | Meaning                                                                                                                                            |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `no matching policy`                                                | 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 in `allowed_ips`.              |
| `resolves to <ip> which is not in allowed_ips, connection rejected` | The destination resolved to an IP outside the policy's `allowed_ips` allowlist.                                                                    |
| `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. Configure `allow_encoded_slash: true` on a REST endpoint when the upstream requires encoded slashes. |
| `l7 deny`                                                           | An L7 policy rule denied the request.                                                                                                              |

Invalid `allowed_ips` 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-CONNECT `allowed_ips` and SSRF checks, not from policy validation.

## Proxy 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 `403 Forbidden`:

```json
{
  "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])"
}
```

The `reason` field 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.

An upstream that the proxy cannot reach returns `502 Bad Gateway`:

```json
{
  "error": "upstream_unreachable",
  "detail": "connection to api.example.com:443 failed"
}
```

The `error` field is a short machine-readable code (`policy_denied`, `middleware_denied`, `middleware_failed`, `ssrf_denied`, `upstream_unreachable`). The `detail` field is a human-readable explanation suitable for display in an agent transcript. The optional `reason` field, when present, provides the specific denial cause from the policy engine (for example, which binary was not allowed or which rule was missing).

For L7 REST policy denials, the body also includes structured policy fields such as `method`, `path`, `rule_missing`, and `next_steps`. When policy advisor is enabled, it also includes `agent_guidance`, a short plain-language instruction telling the agent to read `/etc/openshell/skills/policy_advisor.md`, propose the narrowest rule through `http://policy.local/v1/proposals`, wait for `policy_reloaded: true`, and retry. A middleware denial instead identifies the policy-local config in `middleware` and can include a validated `reason_code`. A fail-closed runtime failure uses `middleware_failed` with platform-owned text. Both middleware responses omit `rule_missing`, `next_steps`, and `agent_guidance` because no policy rule is missing.

## Filesystem Sandbox Logs

Landlock filesystem restrictions emit `CONFIG:` 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:

```text
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]
```

When `landlock.compatibility` is `best_effort` and a requested path fails to open for reasons other than `NotFound` (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:

```text
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](/observability/accessing-logs) through the CLI, TUI, or sandbox filesystem.
* [Enable OCSF JSON export](/observability/ocsf-json-export) for SIEM integration and compliance.
* Learn about [network policies](/sandboxes/policies) that generate these events.