Write Your First Sandbox Network Policy#

This tutorial shows how OpenShell’s network policy system works in under five minutes. You create a sandbox, watch a request get blocked by the default-deny policy, apply a fine-grained L7 rule, and verify that reads are allowed while writes are blocked, all without restarting anything.

After completing this tutorial, you understand:

  • How default-deny networking blocks all outbound traffic from a sandbox.

  • How to apply a network policy that grants read-only access to a specific API.

  • How L7 enforcement distinguishes between HTTP methods such as GET and POST on the same endpoint.

  • How to inspect deny logs for a complete audit trail.

Prerequisites#

  • A working OpenShell installation. Complete the Quickstart before proceeding.

  • Docker Desktop running on your machine.

Tip

To run every step of this tutorial, you can also use the automated demo script at the examples/sandbox-policy-quickstart directory in the NVIDIA OpenShell repository. It runs the full walkthrough in under a minute but without any user interaction.

$ bash examples/sandbox-policy-quickstart/demo.sh

Create a Sandbox#

Start by creating a sandbox with no network policies. This gives you a clean environment to observe default-deny behavior.

$ openshell sandbox create --name demo --keep --no-auto-providers

--keep keeps the sandbox running after you exit so you can reconnect later. --no-auto-providers skips the provider setup prompt since this tutorial uses curl instead of an AI agent.

You land in an interactive shell inside the sandbox:

sandbox@demo:~$

Try to Reach the GitHub API#

With no network policy in place, every outbound connection is blocked. Test this by making a simple API call from inside the sandbox:

$ curl -s https://api.github.com/zen

https://api.github.com/zen is a lightweight, unauthenticated GitHub REST endpoint that returns a random aphorism on each call. It requires no tokens or parameters, which makes it a convenient smoke-test target for verifying outbound HTTPS connectivity.

The request fails. By default, all outbound network traffic is denied. The sandbox proxy intercepted the HTTPS CONNECT request to api.github.com:443 and rejected it because no network policy authorizes curl to reach that host.

curl: (56) Received HTTP code 403 from proxy after CONNECT

Exit the sandbox. The --keep flag keeps it running:

$ exit

Check the Deny Log#

Every denied connection produces a structured log entry. Query the sandbox logs from your host to confirm the denial and inspect the reason.

$ openshell logs demo --since 5m

You see a line like:

action=deny dst_host=api.github.com dst_port=443 binary=/usr/bin/curl deny_reason="no matching network policy"

Every denied connection is logged with the destination, the binary that attempted it, and the reason. Nothing gets out silently.

Apply a Read-Only GitHub API Policy#

To allow the sandbox to reach the GitHub API, define a network policy that grants read-only access. The policy specifies which host, port, binary, and HTTP methods are permitted. Create a file called github-readonly.yaml with the following content:

version: 1

filesystem_policy:
  include_workdir: true
  read_only: [/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log]
  read_write: [/sandbox, /tmp, /dev/null]
landlock:
  compatibility: best_effort
process:
  run_as_user: sandbox
  run_as_group: sandbox

network_policies:
  github_api:
    name: github-api-readonly
    endpoints:
      - host: api.github.com
        port: 443
        protocol: rest
        tls: terminate
        enforcement: enforce
        access: read-only
    binaries:
      - { path: /usr/bin/curl }

The filesystem_policy, landlock, and process sections preserve the default sandbox settings. This is required because policy set replaces the entire policy. The network_policies section is the key part: curl may make GET, HEAD, and OPTIONS requests to api.github.com over HTTPS. Everything else is denied. The proxy terminates TLS using tls: terminate to inspect each HTTP request and enforce the read-only access preset at the method level.

Apply it:

$ openshell policy set demo --policy github-readonly.yaml --wait

--wait blocks until the sandbox confirms the new policy is loaded. No restart required. Policies are hot-reloaded.

Tip

This tutorial uses curl and read-only access to keep things simple. When building policies for real workloads:

  • To scope the policy to an agent, replace the binaries section with your agent’s binary, such as /usr/local/bin/claude, instead of curl.

  • To grant write access, change access: read-only to read-write or add explicit rules for specific paths. Refer to the Policy Schema Reference.

  • To allow additional endpoints, stack multiple policies in the same file for PyPI, npm, or your internal APIs. Refer to Customize Sandbox Policies for examples.

Verify If GET Requests Are Allowed#

The policy is now active. Reconnect to the sandbox and retry the same request to confirm that read access works.

$ openshell sandbox connect demo

Retry the same request:

$ curl -s https://api.github.com/zen
Anything added dilutes everything else.

It works. The read-only preset allows GET requests through.

Try a Write#

The read-only preset allows GET but blocks mutating methods like POST, PUT, and DELETE. Test this by sending a POST request to the GitHub API while still inside the sandbox:

$ curl -s -X POST https://api.github.com/repos/octocat/hello-world/issues \
    -H "Content-Type: application/json" \
    -d '{"title":"oops"}'
{"error":"policy_denied","policy":"github-api-readonly","detail":"POST /repos/octocat/hello-world/issues not permitted by policy"}

The CONNECT request succeeded because api.github.com is allowed, but the L7 proxy inspected the HTTP method and returned 403. POST is not in the read-only preset. An agent with this policy can read code from GitHub but cannot create issues, push commits, or modify anything.

Exit the sandbox:

$ exit

Check the L7 Deny Log#

L7 denials are logged separately from connection-level denials. The log entry includes the exact HTTP method and path that the proxy rejected.

$ openshell logs demo --level warn --since 5m
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"

The log captures the exact HTTP method, path, and deny reason. In production, pipe these logs to your SIEM for a complete audit trail of every request your agent makes.

Tip

To log violations without blocking requests, set enforcement: audit instead of enforcement: enforce in the policy. This is useful for building a policy iteratively: deploy in audit mode, review the logs, and switch to enforce when the rules are correct.

Clean Up#

Delete the sandbox to free resources. This stops all processes and purges any injected credentials.

$ openshell sandbox delete demo

Tip

To run this entire walkthrough non-interactively, use the automated demo script:

$ bash examples/sandbox-policy-quickstart/demo.sh

Next Steps#