> 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 full documentation content, see https://docs.nvidia.com/openshell/llms-full.txt.

# 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 don't represent security-relevant decisions.

### OCSF structured events

Network, process, filesystem, and configuration 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.7.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 via 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, inference routes     |
| `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 confidence level

**Context** in brackets at the end provides the policy rule and enforcement engine that produced the decision.

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

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.

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"
}
```

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`, `ssrf_denied`, `upstream_unreachable`). The `detail` field is a human-readable explanation suitable for display in an agent transcript.

## 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.