***

title: Write Your First Sandbox Network Policy
sidebar-title: First Network Policy
slug: tutorials/first-network-policy
description: See how OpenShell network policies work by creating a sandbox, observing default-deny in action, and applying a fine-grained L7 read-only rule.
keywords: Generative AI, Cybersecurity, Tutorial, Policy, Network Policy, Sandbox, Security
---------------------

For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.nvidia.com/openshell/latest/tutorials/llms.txt. For full documentation content, see https://docs.nvidia.com/openshell/latest/tutorials/llms-full.txt.

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](/get-started/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](https://github.com/NVIDIA/OpenShell/blob/main/examples/sandbox-policy-quickstart) directory in the NVIDIA OpenShell repository. It runs the full walkthrough in under a minute but without any user interaction.

  ```shell
  bash examples/sandbox-policy-quickstart/demo.sh
  ```
</Tip>

<Steps toc={true}>
  ## Create a Sandbox

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

  ```shell
  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:

  ```text
  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:

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

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

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

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

  ```shell
  openshell logs demo --since 5m
  ```

  You see a line like:

  ```text
  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:

  ```yaml
  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
          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 auto-detects TLS on HTTPS endpoints and terminates it to inspect each HTTP request and enforce the `read-only` access preset at the method level.

  Apply it:

  ```shell
  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/policy-schema).
    * To allow additional endpoints, stack multiple policies in the same file for PyPI, npm, or your internal APIs. Refer to [Policies](/sandboxes/policies) for examples.
  </Tip>

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

  ```shell
  openshell sandbox connect demo
  ```

  Retry the same request:

  ```shell
  curl -s https://api.github.com/zen
  ```

  ```text
  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:

  ```shell
  curl -s -X POST https://api.github.com/repos/octocat/hello-world/issues \
      -H "Content-Type: application/json" \
      -d '{"title":"oops"}'
  ```

  ```json
  {"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:

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

  ```shell
  openshell logs demo --level warn --since 5m
  ```

  ```text
  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.
  </Tip>

  ## Clean Up

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

  ```shell
  openshell sandbox delete demo
  ```

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

    ```shell
    bash examples/sandbox-policy-quickstart/demo.sh
    ```
  </Tip>
</Steps>

## Next Steps

* To walk through a full policy iteration with Claude Code, including diagnosing denials and applying fixes from outside the sandbox, refer to [Github Sandbox](/tutorials/github-sandbox).