mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-04 00:23:53 +08:00
* refactor(inference): remove managed inference routes Closes #3172 Remove the inference route control plane, inference.local data path, built-in router crate, and SDK surface. Move inference workloads to explicitly imported provider profiles and native endpoints, with migration cleanup and updated tests and documentation. Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(policy): preserve alternate upstream isolation Restore the provider policy activation guard so legacy OpenAI and Anthropic providers configured for alternate base URLs do not grant egress to the built-in public vendor endpoints. Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> --------- Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
266 lines
14 KiB
Plaintext
266 lines
14 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 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.
|