Sandbox Logging

View as Markdown

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:

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

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:

[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 prefixOCSF classClass UIDWhat it covers
NET:Network Activity4001TCP proxy CONNECT tunnels, bypass detection, DNS failures
HTTP:HTTP Activity4002HTTP FORWARD requests, L7 enforcement decisions
SSH:SSH Activity4007SSH handshakes, authentication, channel operations
PROC:Process Activity1007Process start, exit, timeout, signal failures
FINDING:Detection Finding2004Security findings (nonce replay, proxy bypass, unsafe policy)
CONFIG:Device Config State Change5019Policy load/reload, Landlock, TLS setup, inference routes
LIFECYCLE:Application Lifecycle6002Sandbox supervisor start, SSH server ready

Reading the Shorthand Format

The shorthand format follows this pattern:

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:

TagMeaningWhen used
[INFO]InformationalAllowed connections, successful operations
[LOW]LowDNS failures, operational warnings
[MED]MediumDenied connections, policy violations
[HIGH]HighSecurity findings (nonce replay, bypass detection)
[CRIT]CriticalProcess timeout kills
[FATAL]FatalUnrecoverable 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:

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:

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

A connection denied because no policy matched:

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:

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

OCSF HTTP:GET [INFO] ALLOWED GET http://api.internal.corp:8080/v1/status [policy:internal_api engine:opa]

Proxy and SSH servers ready:

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

OCSF SSH:OPEN [INFO] ALLOWED

A process launched inside the sandbox:

OCSF PROC:LAUNCH [INFO] sleep(49)

A policy reload after a settings change:

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:

ReasonMeaning
no matching policyOPA evaluated the request and no allow rule matched.
resolves to always-blocked addressThe 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 rejectedThe 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 rejectedThe destination port matches a control-plane port (etcd, Kubernetes API, kubelet) and is always blocked.
l7 denyAn 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:

1{
2 "error": "policy_denied",
3 "detail": "CONNECT api.example.com:443 not permitted by policy"
4}

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

1{
2 "error": "upstream_unreachable",
3 "detail": "connection to api.example.com:443 failed"
4}

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:

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:

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/:

FileFormatRotation
openshell.YYYY-MM-DD.logShorthand + standard tracingDaily, 3 files max
openshell-ocsf.YYYY-MM-DD.logOCSF JSONL when enabledDaily, 3 files max

Both files rotate daily and retain the 3 most recent files to bound disk usage.

Next Steps