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