Skip to content

Commit a359f1d

Browse files
authored
docs(examples): add sandbox policy quickstart walkthrough (#266)
* docs(examples): add sandbox policy quickstart walkthrough Add an interactive getting-started example that demonstrates OpenShell's network policy system end-to-end: default-deny, L7 read-only access, and audit logging — all with a single YAML policy file. - examples/sandbox-policy-quickstart/policy.yaml: policy with default static fields (filesystem, landlock, process) so it works out of the box with `openshell policy set` - examples/sandbox-policy-quickstart/demo.sh: automated demo script using printf (portable) and openshell ssh-proxy for sandbox exec - examples/sandbox-policy-quickstart/README.md: step-by-step manual walkthrough - README.md: add "See network policy in action" section linking to the quickstart Signed-off-by: Alexander Watson <zredlined@gmail.com> Made-with: Cursor Signed-off-by: Alexander Watson <zredlined@gmail.com> Made-with: Cursor * docs: soften default-deny wording to minimal outbound access Sandbox defaults vary by type and community configs, so "all outbound traffic is blocked" is too absolute. Made-with: Cursor * docs: use curl -sS so L4 deny errors are visible curl -s suppresses stderr, hiding the 403 from the CONNECT proxy. Adding -S ensures the error message is always shown. Made-with: Cursor --------- Signed-off-by: Alexander Watson <zredlined@gmail.com>
1 parent 623a86e commit a359f1d

4 files changed

Lines changed: 445 additions & 0 deletions

File tree

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,37 @@ The sandbox container includes the following tools by default:
4646
| Developer | `gh`, `git`, `vim`, `nano` |
4747
| Networking | `ping`, `dig`, `nslookup`, `nc`, `traceroute`, `netstat` |
4848

49+
### See network policy in action
50+
51+
Every sandbox starts with **minimal outbound access**. You open additional access with a short YAML policy that the proxy enforces at the HTTP method and path level, without restarting anything.
52+
53+
```bash
54+
# 1. Create a sandbox (starts with minimal outbound access)
55+
openshell sandbox create --name demo --keep --no-auto-providers
56+
57+
# 2. Inside the sandbox — blocked
58+
sandbox$ curl -sS https://api.github.com/zen
59+
curl: (56) Received HTTP code 403 from proxy after CONNECT
60+
61+
# 3. Back on the host — apply a read-only GitHub API policy
62+
sandbox$ exit
63+
openshell policy set demo --policy examples/sandbox-policy-quickstart/policy.yaml --wait
64+
65+
# 4. Reconnect — GET allowed, POST blocked by L7
66+
openshell sandbox connect demo
67+
sandbox$ curl -sS https://api.github.com/zen
68+
Anything added dilutes everything else.
69+
70+
sandbox$ curl -sS -X POST https://api.github.com/repos/octocat/hello-world/issues -d '{"title":"oops"}'
71+
{"error":"policy_denied","detail":"POST /repos/octocat/hello-world/issues not permitted by policy"}
72+
```
73+
74+
See the [full walkthrough](examples/sandbox-policy-quickstart/) or run the automated demo:
75+
76+
```bash
77+
bash examples/sandbox-policy-quickstart/demo.sh
78+
```
79+
4980
## Protection Layers
5081

5182
OpenShell applies defense in depth across four policy domains:
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# Sandbox Policy Quickstart
2+
3+
See how OpenShell's network policy system works in under five minutes.
4+
You'll create a sandbox, watch a request get blocked by the default-deny
5+
policy, apply a fine-grained L7 rule, and verify that reads are allowed
6+
while writes are blocked — all without restarting anything.
7+
8+
## Prerequisites
9+
10+
- A running OpenShell gateway (`openshell gateway start`)
11+
- Docker daemon running
12+
13+
## What's in this example
14+
15+
| File | Description |
16+
| ------------- | -------------------------------------------------------------------- |
17+
| `policy.yaml` | L7 read-only policy for the GitHub REST API, scoped to `curl` |
18+
| `demo.sh` | Automated script that runs the full walkthrough non-interactively |
19+
20+
## Walkthrough
21+
22+
### 1. Create a sandbox
23+
24+
```bash
25+
openshell sandbox create --name demo --keep --no-auto-providers
26+
```
27+
28+
`--keep` keeps the sandbox running after you exit so you can reconnect
29+
later. `--no-auto-providers` skips the provider setup prompt since this
30+
demo doesn't use an AI agent.
31+
32+
You'll land in an interactive shell inside the sandbox:
33+
34+
```
35+
sandbox@demo:~$
36+
```
37+
38+
### 2. Try to reach the GitHub API — blocked
39+
40+
```bash
41+
curl -s https://api.github.com/zen
42+
```
43+
44+
The request fails. By default, **all outbound network traffic is denied**.
45+
The sandbox proxy intercepted the HTTPS CONNECT request to
46+
`api.github.com:443` and rejected it because no network policy authorizes
47+
`curl` to reach that host.
48+
49+
```
50+
curl: (56) Received HTTP code 403 from proxy after CONNECT
51+
```
52+
53+
Exit the sandbox (the sandbox stays alive thanks to `--keep`):
54+
55+
```bash
56+
exit
57+
```
58+
59+
### 3. Check the deny log
60+
61+
```bash
62+
openshell logs demo --since 5m
63+
```
64+
65+
You'll see a line like:
66+
67+
```
68+
action=deny dst_host=api.github.com dst_port=443 binary=/usr/bin/curl deny_reason="no matching network policy"
69+
```
70+
71+
Every denied connection is logged with the destination, the binary that
72+
attempted it, and the reason. Nothing gets out silently.
73+
74+
### 4. Apply the read-only GitHub API policy
75+
76+
Review the policy:
77+
78+
```bash
79+
cat examples/sandbox-policy-quickstart/policy.yaml
80+
```
81+
82+
```yaml
83+
version: 1
84+
85+
# Default sandbox filesystem and process settings.
86+
# These static fields are required when using `openshell policy set`
87+
# because it replaces the entire policy.
88+
filesystem_policy:
89+
include_workdir: true
90+
read_only: [/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log]
91+
read_write: [/sandbox, /tmp, /dev/null]
92+
landlock:
93+
compatibility: best_effort
94+
process:
95+
run_as_user: sandbox
96+
run_as_group: sandbox
97+
98+
network_policies:
99+
github_api:
100+
name: github-api-readonly
101+
endpoints:
102+
- host: api.github.com
103+
port: 443
104+
protocol: rest
105+
tls: terminate
106+
enforcement: enforce
107+
access: read-only
108+
binaries:
109+
- { path: /usr/bin/curl }
110+
```
111+
112+
The top section preserves the default sandbox filesystem and process
113+
settings (required because `policy set` replaces the entire policy).
114+
The `network_policies` section is the interesting part: **curl may make
115+
GET, HEAD, and OPTIONS requests to `api.github.com` over HTTPS.
116+
Everything else is denied.** The proxy terminates TLS (`tls: terminate`)
117+
to inspect each HTTP request and enforce the `read-only` access preset
118+
at the method level.
119+
120+
Apply it:
121+
122+
```bash
123+
openshell policy set demo \
124+
--policy examples/sandbox-policy-quickstart/policy.yaml \
125+
--wait
126+
```
127+
128+
`--wait` blocks until the sandbox confirms the new policy is loaded.
129+
No restart required — policies are hot-reloaded.
130+
131+
### 5. Connect and verify: GET works
132+
133+
```bash
134+
openshell sandbox connect demo
135+
```
136+
137+
```bash
138+
curl -s https://api.github.com/zen
139+
```
140+
141+
```
142+
Anything added dilutes everything else.
143+
```
144+
145+
It works. Try a more visual endpoint:
146+
147+
```bash
148+
curl -s https://api.github.com/octocat
149+
```
150+
151+
```
152+
MMM. .MMM
153+
MMMMMMMMMMMMMMMMMMM
154+
MMMMMMMMMMMMMMMMMMM ____________________________
155+
MMMMMMMMMMMMMMMMMMMMM | |
156+
MMMMMMMMMMMMMMMMMMMMMMM | Speak like a human. |
157+
MMMMMMMMMMMMMMMMMMMMMMMM |_ ________________________|
158+
MMMM::- -:::::::- -::MMMM |/
159+
MM~:~ 00~:::::~ 00~:~MM
160+
.. MMMMM::.00:::+:::.00teleMMM ..
161+
.MM::::: ._. :::::MM.
162+
MMMM;:::::;MMMM
163+
-MM MMMMMMM
164+
^ M+ MMMMMMMMM
165+
MMMMMMM MM MM MM
166+
MM MM MM MM
167+
MM MM MM MM
168+
.~~MM~MM~MM~MM~~.
169+
~~~~MM:~MM~~~MM~:MM~~~~
170+
~~~~~~==googler======~~~~~~
171+
~~~~~~==googler======
172+
:MMMMMMMMMMM:
173+
'=googler=='
174+
```
175+
176+
### 6. Try a write — blocked by L7
177+
178+
```bash
179+
curl -s -X POST https://api.github.com/repos/octocat/hello-world/issues \
180+
-H "Content-Type: application/json" \
181+
-d '{"title":"oops"}'
182+
```
183+
184+
```json
185+
{"error":"policy_denied","policy":"github-api-readonly","detail":"POST /repos/octocat/hello-world/issues not permitted by policy"}
186+
```
187+
188+
The CONNECT request succeeded (api.github.com is allowed), but the L7
189+
proxy inspected the HTTP method and returned **403**. `POST` is not in
190+
the `read-only` preset. Your agent can read code from GitHub but cannot
191+
create issues, push commits, or modify anything.
192+
193+
Exit the sandbox:
194+
195+
```bash
196+
exit
197+
```
198+
199+
### 7. Check the L7 deny log
200+
201+
```bash
202+
openshell logs demo --level warn --since 5m
203+
```
204+
205+
```
206+
l7_decision=deny dst_host=api.github.com l7_action=POST l7_target=/repos/octocat/hello-world/issues l7_deny_reason="POST /repos/octocat/hello-world/issues not permitted by policy"
207+
```
208+
209+
The log captures the exact HTTP method, path, and deny reason. In
210+
production, pipe these logs to your SIEM for a complete audit trail of
211+
every request your agent makes.
212+
213+
### 8. Clean up
214+
215+
```bash
216+
openshell sandbox delete demo
217+
```
218+
219+
## What you just saw
220+
221+
| State | What happens |
222+
| ------------------ | ----------------------------------------------- |
223+
| **Default deny** | All outbound traffic blocked — nothing gets out |
224+
| **L7 read-only** | GET to `api.github.com` allowed, POST blocked |
225+
| **Audit trail** | Every request logged with method, path, decision |
226+
227+
The policy hot-reloads in seconds and gives
228+
you verifiable, fine-grained control over what your agent can access —
229+
without `--dangerously-skip-permissions`.
230+
231+
## Next steps
232+
233+
- **Customize the policy**: Change `access: read-only` to `read-write`
234+
or add explicit `rules` for specific paths. See the
235+
[security policy reference](../../architecture/security-policy.md).
236+
- **Scope to an agent**: Replace the `binaries` section with your
237+
agent's binary (e.g., `/usr/local/bin/claude`) instead of `curl`.
238+
- **Add more endpoints**: Stack multiple policies in the same file
239+
to allow PyPI, npm, or your internal APIs.
240+
- **Try audit mode**: Set `enforcement: audit` to log violations
241+
without blocking, useful for building a policy iteratively.

0 commit comments

Comments
 (0)