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