mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-27 07:03:17 +08:00
* docs: refresh user-facing docs for recent sandbox and inference changes - architecture: document system CA loading for upstream TLS, `tls: skip` as the opt-out, gateway state persistence across restarts, and OCSF structured logging surface. - inference: document per-provider header allowlist, Authorization stripping, 120s streaming idle tolerance, and extended-thinking timeout guidance. - manage-sandboxes: add "Execute a Command in a Sandbox" section for `openshell sandbox exec` with flag reference. - security best practices: expand seccomp denylist (unconditional and conditional blocks), document two-phase Landlock probe, High-severity `landlock-unavailable` finding, and inference keep-alive closure. - observability logging: document port in HTTP log URLs, `[reason:...]` denial suffixes, proxy 403/502 JSON error bodies, and Landlock CONFIG:ENABLED/CONFIG:OTHER events. Signed-off-by: Miyoung Choi <miyoungc@nvidia.com> * docs(architecture): tighten high-level architecture page Trim implementation detail (CA bundle paths, deprecated TLS keys, SSH handshake secret) from the high-level architecture page, fix accuracy issues surfaced during deep audit, and expand uncommon acronyms on first mention. - Drop unsupported "cost-based routing" claim from Privacy Router row. - Replace "brokers requests across the platform" with auth-boundary description. - Add "inference" to Policy Engine constraint list per AGENTS.md. - Expand Deny rule to include SSRF, blocked control-plane port, and L7 deny paths in addition to deny-by-default. - Switch Allow/Deny labels from hyphen to colon; remove em dashes and a double space. - Expand LLM, SSRF, L7, TLS, CA, PEM, SSH, OCSF, and JSONL on first use. Signed-off-by: Miyoung Choi <miyoungc@nvidia.com> Made-with: Cursor * docs: address audit feedback on refresh PR - observability/logging: rewrite the allowed_ips paragraph after the Denial Reasons table; the previous wording said authors "can use" invalid entries while also stating they were rejected, which was contradictory and conflated load-time validation with the runtime per-CONNECT denial phrases the section documents. - about/architecture: split compound sentences in the new Gateway Lifecycle and Observability sections so each clause stands alone. - inference/about: drop the streaming-tolerance sentence from the prose paragraph since the dedicated Streaming reliability table row already covers it. Signed-off-by: Miyoung Choi <miyoungc@nvidia.com> Made-with: Cursor * docs(architecture): address review feedback on architecture page - Drop the Privacy Router row from the Components table; it is not yet a separately exposed customer-facing component. - Update the page description and intro count to match the remaining three components (gateway, sandbox, policy engine). - Split the policy decision into the three modes that the engine actually implements: Explicit Deny (deny rules and hardening rules, takes precedence), Allow, and Implicit Deny (no rule matched). Signed-off-by: Miyoung Choi <miyoungc@nvidia.com> Made-with: Cursor * docs(architecture): convert policy decisions to a table Promote the three policy decisions (Explicit Deny, Allow, Implicit Deny) to a top-level table with Decision, When it applies, and Outcome columns instead of a nested bulleted list under list item 5. Top-level tables render reliably across markdown renderers, where nested-in-list tables do not. Signed-off-by: Miyoung Choi <miyoungc@nvidia.com> Made-with: Cursor * docs: repharse a bit --------- Signed-off-by: Miyoung Choi <miyoungc@nvidia.com>
236 lines
10 KiB
Plaintext
236 lines
10 KiB
Plaintext
---
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
title: "Sandbox Logging"
|
|
sidebar-title: "Logging"
|
|
description: "How OpenShell logs sandbox activity using standard tracing and OCSF structured events."
|
|
keywords: "Generative AI, Cybersecurity, Logging, OCSF, Observability"
|
|
---
|
|
|
|
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] 0.0.0.0:2222
|
|
```
|
|
|
|
An SSH handshake accepted, one event per connection:
|
|
|
|
```text
|
|
OCSF SSH:OPEN [INFO] ALLOWED 10.42.0.52:42706 [auth:NSSH1]
|
|
```
|
|
|
|
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. |
|
|
| `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.
|