---
title:
page: Customize Sandbox Policies
nav: Policies
description: Apply, iterate, and debug sandbox network policies with hot-reload on running OpenShell sandboxes.
topics:
- Generative AI
- Cybersecurity
tags:
- Policy
- Network Policy
- Sandbox
- Security
- Hot Reload
content:
type: how_to
difficulty: technical_intermediate
audience:
- engineer
---
# Customize Sandbox Policies
Use this page to apply and iterate policy changes on running sandboxes. For a full field-by-field YAML definition, use the [Policy Schema Reference](../reference/policy-schema.md).
## Policy Structure
A policy has static sections `filesystem_policy`, `landlock`, and `process` that are locked at sandbox creation, and dynamic sections `network_policies` and `inference` that are hot-reloadable on a running sandbox.
```yaml
version: 1
# Static: locked at sandbox creation. Paths the agent can read vs read/write.
filesystem_policy:
read_only: [/usr, /lib, /etc]
read_write: [/sandbox, /tmp]
# Static: Landlock LSM kernel enforcement. best_effort uses highest ABI the host supports.
landlock:
compatibility: best_effort
# Static: Unprivileged user/group the agent process runs as.
process:
run_as_user: sandbox
run_as_group: sandbox
# Dynamic: hot-reloadable. Named blocks of endpoints + binaries allowed to reach them.
network_policies:
my_api:
name: my-api
endpoints:
- host: api.example.com
port: 443
protocol: rest
tls: terminate
enforcement: enforce
access: full
binaries:
- path: /usr/bin/curl
# Dynamic: hot-reloadable. Routing hints this sandbox can use for inference (e.g. local, nvidia).
inference:
allowed_routes: [local]
```
Static sections are locked at sandbox creation. Changing them requires destroying and recreating the sandbox.
Dynamic sections can be updated on a running sandbox with `openshell policy set` and take effect without restarting.
| Section | Type | Description |
|---|---|---|
| `filesystem_policy` | Static | Controls which directories the agent can access on disk. Paths are split into `read_only` and `read_write` lists. Any path not listed in either list is inaccessible. Set `include_workdir: true` to automatically add the agent's working directory to `read_write`. [Landlock LSM](https://docs.kernel.org/security/landlock.html) enforces these restrictions at the kernel level. |
| `landlock` | Static | Configures Landlock LSM enforcement behavior. Set `compatibility` to `best_effort` (use the highest ABI the host kernel supports) or `hard_requirement` (fail if the required ABI is unavailable). |
| `process` | Static | Sets the OS-level identity for the agent process. `run_as_user` and `run_as_group` default to `sandbox`. Root (`root` or `0`) is rejected. The agent also runs with seccomp filters that block dangerous system calls. |
| `network_policies` | Dynamic | Controls network access for the sandbox. Each block has a name, a list of endpoints (host, port, protocol, and optional rules), and a list of binaries allowed to use those endpoints.
Every outbound connection goes through the proxy, which queries the {doc}`policy engine <../about/architecture>` with the destination and calling binary. A connection is allowed only when both match an entry in the same policy block.
For endpoints with `protocol: rest` and `tls: terminate`, each HTTP request is also checked against that endpoint's `rules` (method and path).
Endpoints without `protocol` or `tls` allow the TCP stream through without inspecting payloads.
If no endpoint matches and inference routes are configured, the request may be rerouted for inference. Otherwise the connection is denied. |
| `inference` | Dynamic | Controls which inference routing backends the sandbox can use. Set `allowed_routes` to a list of route names (for example, `[local]` or `[local, nvidia]`). When an outbound request does not match any `network_policies` entry, the proxy checks whether the destination matches a configured inference route. If it does and the route is in `allowed_routes`, the request is forwarded to that backend. |
## Apply a Custom Policy
Pass a policy YAML file when creating the sandbox:
```console
$ openshell sandbox create --policy ./my-policy.yaml -- claude
```
`openshell sandbox create` keeps the sandbox running after the initial command exits, which is useful when you plan to iterate on the policy. Add `--no-keep` if you want the sandbox deleted automatically instead.
To avoid passing `--policy` every time, set a default policy with an environment variable:
```console
$ export OPENSHELL_SANDBOX_POLICY=./my-policy.yaml
$ openshell sandbox create -- claude
```
The CLI uses the policy from `OPENSHELL_SANDBOX_POLICY` whenever `--policy` is not explicitly provided.
## Iterate on a Running Sandbox
To change what the sandbox can access, pull the current policy, edit the YAML, and push the update. The workflow is iterative: create the sandbox, monitor logs for denied actions, pull the policy, modify it, push, and verify.
```{mermaid}
flowchart TD
A["1. Create sandbox with initial policy"] --> B["2. Monitor logs for denied actions"]
B --> C["3. Pull current policy"]
C --> D["4. Modify the policy YAML"]
D --> E["5. Push updated policy"]
E --> F["6. Verify the new revision loaded"]
F --> B
style A fill:#76b900,stroke:#000000,color:#000000
style B fill:#76b900,stroke:#000000,color:#000000
style C fill:#76b900,stroke:#000000,color:#000000
style D fill:#ffffff,stroke:#000000,color:#000000
style E fill:#76b900,stroke:#000000,color:#000000
style F fill:#76b900,stroke:#000000,color:#000000
linkStyle default stroke:#76b900,stroke-width:2px
```
The following steps outline the hot-reload policy update workflow.
1. Create the sandbox with your initial policy by following [Apply a Custom Policy](#apply-a-custom-policy) above (or set `OPENSHELL_SANDBOX_POLICY`).
2. Monitor denials. Each log entry shows host, port, binary, and reason. Alternatively, use `openshell term` for a live dashboard.
```console
$ openshell logs --tail --source sandbox
```
3. Pull the current policy. Strip the metadata header (Version, Hash, Status) before reusing the file.
```console
$ openshell policy get --full > current-policy.yaml
```
4. Edit the YAML: add or adjust `network_policies` entries, binaries, `access` or `rules`, or `inference.allowed_routes`.
5. Push the updated policy. Exit codes: 0 = loaded, 1 = validation failed, 124 = timeout.
```console
$ openshell policy set --policy current-policy.yaml --wait
```
6. Verify the new revision. If status is `loaded`, repeat from step 2 as needed; if `failed`, fix the policy and repeat from step 4.
```console
$ openshell policy list
```
## Debug Denied Requests
Check `openshell logs --tail --source sandbox` for the denied host, path, and binary.
When triaging denied requests, check:
- Destination host and port to confirm which endpoint is missing.
- Calling binary path to confirm which `binaries` entry needs to be added or adjusted.
- HTTP method and path (for REST endpoints) to confirm which `rules` entry needs to be added or adjusted.
Then push the updated policy as described above.
## Examples
Add these blocks to the `network_policies` section of your sandbox policy. Apply with `openshell policy set --policy --wait`.
Use **Simple endpoint** for host-level allowlists and **Granular rules** for method/path control.
:::::{tab-set}
::::{tab-item} Simple endpoint
Allow `pip install` and `uv pip install` to reach PyPI:
```yaml
pypi:
name: pypi
endpoints:
- host: pypi.org
port: 443
- host: files.pythonhosted.org
port: 443
binaries:
- { path: /usr/bin/pip }
- { path: /usr/local/bin/uv }
```
Endpoints without `protocol` or `tls` use TCP passthrough — the proxy allows the stream without inspecting payloads.
::::
::::{tab-item} Granular rules
Allow Claude and the GitHub CLI to reach `api.github.com` with per-path rules: read-only (GET, HEAD, OPTIONS) and GraphQL (POST) for all paths; full write access for `alpha-repo`; and create/edit issues only for `bravo-repo`. Replace `` with your GitHub org or username.
:::{tip}
For an end-to-end walkthrough that combines this policy with a GitHub credential provider and sandbox creation, refer to {doc}`/tutorials/github-sandbox`.
:::
```yaml
github_repos:
name: github_repos
endpoints:
- host: api.github.com
port: 443
protocol: rest
tls: terminate
enforcement: enforce
rules:
- allow:
method: GET
path: "/**"
- allow:
method: HEAD
path: "/**"
- allow:
method: OPTIONS
path: "/**"
- allow:
method: POST
path: "/graphql"
- allow:
method: "*"
path: "/repos//alpha-repo/**"
- allow:
method: POST
path: "/repos//bravo-repo/issues"
- allow:
method: PATCH
path: "/repos//bravo-repo/issues/*"
binaries:
- { path: /usr/local/bin/claude }
- { path: /usr/bin/gh }
```
Endpoints with `protocol: rest` and `tls: terminate` enable HTTP request inspection — the proxy decrypts TLS and checks each HTTP request against the `rules` list.
::::
:::::
## Next Steps
Explore related topics:
- To learn about network access rules and sandbox isolation layers, refer to {doc}`index`.
- To view the full field-by-field YAML definition, refer to the [Policy Schema Reference](../reference/policy-schema.md).
- To review the default policy breakdown, refer to {doc}`../reference/default-policy`.