You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: refresh user-facing docs for recent sandbox and inference changes (#868)
* 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>
Copy file name to clipboardExpand all lines: docs/about/architecture.mdx
+22-11Lines changed: 22 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,25 +3,24 @@
3
3
# SPDX-License-Identifier: Apache-2.0
4
4
title: "How OpenShell Works"
5
5
sidebar-title: "How It Works"
6
-
description: "OpenShell architecture overview covering the gateway, sandbox, policy engine, and privacy router."
6
+
description: "OpenShell architecture overview covering the gateway, sandbox, and policy engine."
7
7
keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Security, Architecture"
8
8
position: 2
9
9
---
10
10
11
-
OpenShell runs inside a Docker container. Each sandbox is an isolated environment managed through the gateway. Four components work together to keep agents secure.
11
+
OpenShell runs inside a Docker container. Each sandbox is an isolated environment managed through the gateway. Three components work together to keep agents secure.
The following table describes each component and its role in the system:
17
+
The following table describes each component and its role in the system:
18
18
19
19
| Component | Role |
20
20
|---|---|
21
-
|**Gateway**| Control-plane API that coordinates sandbox lifecycle and state, acts as the auth boundary, and brokers requests across the platform. |
21
+
|**Gateway**| Control-plane API that coordinates sandbox lifecycle and state, and acts as the auth boundary between clients and sandboxes. |
22
22
|**Sandbox**| Isolated runtime that includes container supervision and policy-enforced egress routing. |
23
-
|**Policy Engine**| Policy definition and enforcement layer for filesystem, network, and process constraints. Defense in depth enforces policies from the application layer down to infrastructure and kernel layers. |
24
-
|**Privacy Router**| Privacy-aware LLM routing layer that keeps sensitive context on sandbox compute and routes based on cost and privacy policy. |
23
+
|**Policy Engine**| Policy definition and enforcement layer for filesystem, network, process, and inference constraints. Defense in depth enforces policies from the application layer down to infrastructure and kernel layers. |
25
24
26
25
## How a Request Flows
27
26
@@ -31,15 +30,27 @@ Every outbound connection from agent code passes through the same decision path:
31
30
2. The proxy inside the sandbox intercepts the connection and identifies which binary opened it.
32
31
3. If the target is `https://inference.local`, the proxy handles it as managed inference before policy evaluation. OpenShell strips sandbox-supplied credentials, injects the configured backend credentials, and forwards the request to the managed model endpoint.
33
32
4. For every other destination, the proxy queries the policy engine with the destination, port, and calling binary.
34
-
5. The policy engine returns one of two decisions:
35
-
-**Allow** - the destination and binary match a policy block. Traffic flows directly to the external service.
36
-
-**Deny** - no policy block matched. The connection is blocked and logged.
33
+
5. The policy engine returns one of the following decisions. Explicit deny takes precedence over allow, and allow takes precedence over implicit deny.
37
34
38
-
For REST endpoints with TLS termination enabled, the proxy also decrypts TLS and checks each HTTP request against per-method, per-path rules before allowing it through.
35
+
| Decision | When it applies | Outcome |
36
+
|---|---|---|
37
+
|**Explicit Deny**| A deny rule, or a hardening rule such as server-side request forgery (SSRF) protection, a blocked control-plane port, or a Layer 7 (L7) deny, rejects the request even when an allow rule would otherwise permit it. | The connection is blocked and logged. |
38
+
|**Allow**| The destination and binary match a policy block, and no deny rule applies. | Traffic flows directly to the external service. |
39
+
|**Implicit Deny**| No policy block matched. | The connection is blocked and logged. |
40
+
41
+
For REST endpoints, the proxy auto-detects Transport Layer Security (TLS) by peeking the first bytes of each tunnel. When TLS is detected, the proxy terminates it transparently using a per-sandbox ephemeral certificate authority (CA) so each HTTP request can be checked against per-method, per-path rules before forwarding. To trust a corporate or internal CA for upstream traffic, add the PEM-encoded certificate to the sandbox container's trust store at image build time.
42
+
43
+
## Gateway Lifecycle
44
+
45
+
OpenShell preserves sandbox state across gateway restarts. After `openshell gateway stop` and `openshell gateway start`, running sandboxes resume from their saved state. Existing SSH sessions reconnect without re-authenticating.
46
+
47
+
## Observability
48
+
49
+
The sandbox emits operational decisions as Open Cybersecurity Schema Framework (OCSF) v1.7.0 structured logs. The events cover network connections, HTTP requests, SSH sessions, process lifecycle, detection findings, and configuration changes. For event classes and the JSON Lines (JSONL) export format, refer to [OCSF JSON Export](/observability/ocsf-json-export).
39
50
40
51
## Deployment Modes
41
52
42
-
OpenShell can run locally, on a remote host, or behind a cloud proxy. The architecture is identical in all cases — only the Docker container location and authentication mode change.
53
+
OpenShell can run locally, on a remote host, or behind a cloud proxy. The architecture is identical in all cases. Only the Docker container location and authentication mode change.
Copy file name to clipboardExpand all lines: docs/inference/about.mdx
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,10 +24,11 @@ If code calls an external inference host directly, that traffic is evaluated onl
24
24
25
25
| Property | Detail |
26
26
|---|---|
27
-
| Credentials | No sandbox API keys needed. Credentials come from the configured provider record. |
28
-
| Header forwarding |`inference.local` forwards only approved inference headers. It strips caller auth and other unexpected headers before sending the request upstream. |
27
+
| Credentials | No sandbox API keys needed. Credentials come from the configured provider record. The router strips caller-supplied `Authorization` before forwarding the request. |
28
+
| Header forwarding |`inference.local` forwards only a per-provider header allowlist. OpenAI routes allow `openai-organization` and `x-model-id`. Anthropic routes allow `anthropic-version` and `anthropic-beta`. NVIDIA routes allow `x-model-id`. All other caller headers are stripped. |
29
29
| Configuration | One provider and one model define sandbox inference for the active gateway. Every sandbox on that gateway sees the same `inference.local` backend. |
30
30
| Provider support | NVIDIA, any OpenAI-compatible provider, and Anthropic all work through the same endpoint. |
31
+
| Streaming reliability | The router tolerates idle gaps of up to 120 seconds between streamed chunks so long reasoning responses are not cut off mid-stream. |
31
32
| Hot-refresh | OpenShell picks up provider credential changes and inference updates without recreating sandboxes. Changes propagate within about 5 seconds by default. |
Copy file name to clipboardExpand all lines: docs/inference/configure.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -103,7 +103,7 @@ openshell inference set \
103
103
--timeout 300
104
104
```
105
105
106
-
The value is in seconds. When `--timeout` is omitted (or set to `0`), the default of 60 seconds applies.
106
+
The value is in seconds. When `--timeout` is omitted (or set to `0`), the default of 60 seconds applies. Increase `--timeout` when you expect extended thinking phases so the full response completes before the request deadline.
The client-supplied `model` and `api_key` values are not sent upstream. The privacy router injects the real credentials from the configured provider and rewrites the model before forwarding.
157
+
The client-supplied `model` and `api_key` values are not sent upstream. The privacy router injects the real credentials from the configured provider and rewrites the model before forwarding. Caller-supplied `Authorization` and other non-allowlisted headers are stripped; only a per-provider header allowlist reaches the upstream endpoint. For the exact allowlist, refer to [About Inference Routing](/inference/about).
158
158
159
159
Some SDKs require a non-empty API key even though `inference.local` does not use the sandbox-provided value. In those cases, pass any placeholder such as `test` or `unused`.
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.
162
+
163
+
Common reason phrases emitted by the sandbox include:
164
+
165
+
| Reason | Meaning |
166
+
|---|---|
167
+
|`no matching policy`| OPA evaluated the request and no allow rule matched. |
168
+
|`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`. |
169
+
|`resolves to <ip> which is not in allowed_ips, connection rejected`| The destination resolved to an IP outside the policy's `allowed_ips` allowlist. |
170
+
|`DNS resolution failed for <host>:<port>`| The proxy could not resolve the destination. |
171
+
|`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. |
172
+
|`l7 deny`| An L7 policy rule denied the request. |
173
+
174
+
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.
175
+
176
+
## Proxy Error Responses
177
+
178
+
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.
179
+
180
+
A denied CONNECT returns `403 Forbidden`:
181
+
182
+
```json
183
+
{
184
+
"error": "policy_denied",
185
+
"detail": "CONNECT api.example.com:443 not permitted by policy"
186
+
}
187
+
```
188
+
189
+
An upstream that the proxy cannot reach returns `502 Bad Gateway`:
190
+
191
+
```json
192
+
{
193
+
"error": "upstream_unreachable",
194
+
"detail": "connection to api.example.com:443 failed"
195
+
}
196
+
```
197
+
198
+
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.
199
+
200
+
## Filesystem Sandbox Logs
201
+
202
+
Landlock filesystem restrictions emit `CONFIG:` events at startup and whenever the sandbox has to skip a requested path.
203
+
204
+
On startup, the probe reports the kernel's supported Landlock ABI version alongside the requested path counts:
OCSF CONFIG:ENABLED [INFO] Landlock ruleset built [rules_applied:5 skipped:1]
210
+
```
211
+
212
+
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:
Landlock setup runs in two phases. The parent supervisor probes the kernel ABI and opens the configured path file descriptors before forking. The child then applies the ruleset with `restrict_self()` after privilege drop. At startup, OpenShell emits the selected ABI version and the applied read-only and read-write rule counts so you can confirm what the kernel accepted.
149
+
148
150
| Aspect | Detail |
149
151
|---|---|
150
-
| Default |`compatibility: best_effort`. Uses the highest kernel ABI available. The system skips missing paths with a warning. If the kernel does not support Landlock, the sandbox continues without filesystem restrictions. |
152
+
| Default |`compatibility: best_effort`. Uses the highest kernel ABI available. Missing paths are skipped. If the kernel does not support Landlock or any configured path cannot be opened, the sandbox continues without those restrictions and emits a High-severity OCSF `DetectionFinding`. |
151
153
| What you can change | Set `compatibility: hard_requirement` to abort sandbox startup if Landlock is unavailable or any configured path cannot be opened. |
152
154
| Risk if relaxed | On kernels without Landlock (pre-5.13), or when all paths fail to open, the sandbox runs without kernel-level filesystem restrictions. The agent can access any file the process user can access. |
153
-
| Recommendation | Use `best_effort` for development. Use `hard_requirement` in environments where any gap in filesystem isolation is unacceptable. Run on Ubuntu 22.04+ or any kernel 5.13+ for Landlock support. |
155
+
| Recommendation | Use `best_effort` for development. Use `hard_requirement` in environments where any gap in filesystem isolation is unacceptable. Treat High-severity Landlock findings as a signal to investigate the host kernel or the image. Run on Ubuntu 22.04+ or any kernel 5.13+ for Landlock support. |
154
156
155
157
### Read-Only vs Read-Write Paths
156
158
@@ -192,13 +194,15 @@ The sandbox process runs as a non-root user after explicit privilege dropping.
192
194
193
195
### Seccomp Filters
194
196
195
-
A BPF seccomp filter restricts which socket domains the sandbox process can use.
197
+
A BPF seccomp filter restricts which socket domains the sandbox process can use and blocks a denylist of syscalls that enable container escape, privilege escalation, or host observation. The sandbox sets `PR_SET_NO_NEW_PRIVS` before applying the filter.
196
198
197
199
| Aspect | Detail |
198
200
|---|---|
199
-
| Default | The filter allows `AF_INET` and `AF_INET6` (for proxy communication) and blocks `AF_NETLINK`, `AF_PACKET`, `AF_BLUETOOTH`, and `AF_VSOCK` with `EPERM`. The sandbox sets `PR_SET_NO_NEW_PRIVS` before applying the filter. |
201
+
| Socket domains | The filter allows `AF_INET` and `AF_INET6` (for proxy communication) and blocks `AF_NETLINK`, `AF_PACKET`, `AF_BLUETOOTH`, and `AF_VSOCK` with `EPERM`. |
| Conditional syscall blocks |`execveat` with `AT_EMPTY_PATH`, `unshare` and `clone` with `CLONE_NEWUSER`, and `seccomp(SECCOMP_SET_MODE_FILTER)` are denied with `EPERM`. |
200
204
| What you can change | This is not a user-facing knob. OpenShell enforces it automatically. |
201
-
| Risk if relaxed |`AF_NETLINK` allows manipulation of routing tables and firewall rules. `AF_PACKET` enables raw packet capture. `AF_VSOCK` enables VM socket communication. |
205
+
| Risk if relaxed |The blocked syscalls support container escape (`mount`, `pivot_root`, `move_mount`, namespace creation), cross-process observation (`ptrace`, `process_vm_readv`, `pidfd_*`), raw kernel bypass (`bpf`, `io_uring_setup`, `perf_event_open`), and filter evasion (`seccomp`, `userfaultfd`). |
202
206
| Recommendation | No action needed. OpenShell enforces this automatically. |
203
207
204
208
### Enforcement Application Order
@@ -224,6 +228,7 @@ The agent never receives the provider API key.
224
228
| Aspect | Detail |
225
229
|---|---|
226
230
| Default | Always active. The proxy handles `inference.local` before OPA policy evaluation. The gateway injects credentials on the host side. |
231
+
| Keep-alive isolation | If a sandbox reuses a keep-alive connection that previously carried a routed inference request for a subsequent non-inference request, the proxy denies the non-inference request with `connection not allowed by policy` and closes the connection. This prevents agents from reusing an inference-authorized connection for other destinations. |
227
232
| What you can change | Configure inference routes with `openshell inference set`. |
228
233
| Risk if bypassed | If an inference provider's host is added directly to `network_policies`, the agent could reach it with a stolen or hardcoded key, bypassing credential isolation. |
229
234
| Recommendation | Do not add inference provider hosts to `network_policies`. Use OpenShell inference routing instead. |
0 commit comments