# Network

`SandboxNetworkPolicy` controls outbound traffic for a sandbox.

Use `mode` as the default fallback, and use `egress.trafficRules` for explicit ordered allow and deny behavior.

Cloud metadata and host credential endpoints are protected by the platform. An
`allow-all` policy or explicit allow rule cannot grant access to those destinations.

An active-policy update is acknowledged only after the node applies the new
policy and reconciles existing connections. Concurrent updates are serialized
against the current policy and runtime version; a delayed retry cannot restore
an older policy. Reopening access requires a new TCP connection after revocation.

## Policy Shape

| Field | Description |
|-------|-------------|
| `mode` | Default fallback for unmatched traffic: `allow-all` or `block-all` |
| `egress.trafficRules` | Ordered allow/deny rules. First matching rule wins |
| `egress.protocolRules` | Protocol-aware operation controls for allowed traffic. See [Protocol Controls](/docs/sandbox/protocol-controls) |
| `egress.credentialRules` | Destination-scoped outbound auth rules, optionally constrained by HTTP request matchers. See [Credential / Egress Auth](/docs/sandbox/credential/egress-auth) |
| `egress.proxy` | Customer-managed SOCKS5 route for allowed TCP traffic. See [Egress Proxy](/docs/sandbox/proxy) |
| `network.credentialBindings` | Local bindings used by `credentialRules`. See [Credential / Egress Auth](/docs/sandbox/credential/egress-auth) |

The same public `SandboxNetworkPolicy` shape is used in:

- template `spec.network`
- sandbox claim `config.network`
- runtime `GET` and `PUT` `/api/v1/sandboxes/{id}/network`

## Mode And Rule Evaluation

1. `trafficRules` are evaluated in order.
2. The first matching rule wins.
3. If no rule matches, traffic falls back to `mode`.

<Callout variant="info">
Use `trafficRules` for new policies. Legacy `allowed*` and `denied*` fields still work, but they are deprecated and should not be mixed with `trafficRules`.
</Callout>

<Callout variant="info">
If a credential rule uses `tlsMode: terminate-reoriginate`, Sandbox0 exposes the ctld network runtime's MITM CA as `SANDBOX0_NETWORK_MITM_CA_FILE` and configures procd-managed processes with a combined CA bundle through common TLS environment variables.
</Callout>

## Traffic Rule Fields

| Field | Required | Description |
|-------|----------|-------------|
| `action` | Yes | `allow` or `deny` |
| `name` | No | Stable identifier for replacement and merge |
| `domains` | No | Domain match list |
| `cidrs` | No | CIDR match list |
| `ports` | No | Port/protocol constraints |
| `appProtocols` | No | Classified application protocols such as `http`, `tls`, `ssh`, `dns`, `redis` |

## HTTP Request Matchers

`credentialRules[*].httpMatch` narrows credential injection for HTTP-family rules after the HTTP request is visible to the ctld network runtime. It can match methods, exact paths, path prefixes, query parameters, and headers.

For HTTPS and gRPC rules, request matching requires `tlsMode: terminate-reoriginate` so the ctld network runtime can read the request before injecting credentials.

## Protocol Rules

`protocolRules` run after traffic is allowed. HTTP rules can limit request methods and paths; MCP rules can limit remote MCP tool calls by tool name.

For HTTPS HTTP or MCP rules, set `tlsMode: terminate-reoriginate` on the protocol rule so the ctld network runtime can inspect request contents.

## Get Network Policy

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/network
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `policy, err := sandbox.GetNetworkPolicy(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("mode=%s\\n", policy.Mode)`
    },
    {
      label: "Python",
      language: "python",
      code: `policy = sandbox.get_network_policy()
print(policy.mode)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const policy = await sandbox.getNetworkPolicy();
console.log(policy.mode);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox network get -s sb_abc123`
    }
  ]}
/>

## Update Network Policy

<Endpoint method="PUT">
/api/v1/sandboxes/{'{id}'}/network
</Endpoint>

For a running Nomad sandbox, manager records the desired policy against the exact active allocation and asks the node-local ctld runtime to apply it. The new policy becomes visible from `GET` only after PostgreSQL commits ctld's matching applied-policy token. If manager or the node channel disconnects after the request is recorded, `PUT` can return `503` while a background controller continues the same idempotent operation; retrying the same `PUT` is safe. Pause, deletion, hard expiry, or allocation replacement cancels an unacknowledged update instead of publishing policy for the wrong runtime.

Stock gVisor transfers the carrier's IP address into its userspace network stack
at startup. Active-policy validation retains the originally registered source
address while checking the exact namespace identity and live carrier link. A
missing link or conflicting host address prevents acknowledgement; an IP still
owned by a live runtime cannot be silently reassigned to another policy.

Before acknowledging a changed or removed policy, ctld closes TCP proxy connections and UDP sessions authorized by the previous runtime or policy version. Clients must reconnect after a policy update, including updates that still permit the same destination. Closing both upstream and downstream sockets prevents idle relays from surviving sandbox termination; an IP reused by another runtime cannot inherit the previous runtime's proxy session.

Credential-binding changes use the same Nomad desired/apply/ack path. Manager keeps effective bindings outside the sanitized sandbox config, includes their source-version-independent digest in the node policy, and stages referenced sources without changing the current runtime projection. Only a matching ctld acknowledgement atomically publishes the request policy, current bindings, claim digest, and runtime-slot policy digest. A source rotation that races the update wins either before publication or advances the newly published bindings afterward. Cancellation removes the staged source pins, while deletion removes the current bindings before the durable claim is marked cleaned.

### Allow Only GitHub HTTPS

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err = sandbox.UpdateNetworkPolicy(ctx, apispec.SandboxNetworkPolicy{
    Mode: apispec.SandboxNetworkPolicyModeBlockAll,
    Egress: apispec.NewOptNetworkEgressPolicy(apispec.NetworkEgressPolicy{
        TrafficRules: []apispec.TrafficRule{
            {
                Name: apispec.NewOptString("allow-github"),
                Action: apispec.TrafficRuleActionAllow,
                Domains: []string{"github.com", "api.github.com"},
                Ports: []apispec.PortSpec{
                    {
                        Port: 443,
                        Protocol: apispec.NewOptString("tcp"),
                    },
                },
            },
        },
    }),
})
if err != nil {
    log.Fatal(err)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.sandbox_network_policy import SandboxNetworkPolicy

sandbox.update_network_policy(
    SandboxNetworkPolicy.from_dict({
        "mode": "block-all",
        "egress": {
            "trafficRules": [
                {
                    "name": "allow-github",
                    "action": "allow",
                    "domains": ["github.com", "api.github.com"],
                    "ports": [{"port": 443, "protocol": "tcp"}],
                },
            ],
        },
    })
)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await sandbox.updateNetworkPolicy({
    mode: 'block-all',
    egress: {
        trafficRules: [
            {
                name: 'allow-github',
                action: 'allow',
                domains: ['github.com', 'api.github.com'],
                ports: [{ port: 443, protocol: 'tcp' }],
            },
        ],
    },
});`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox network update --mode block-all \\
  --traffic-rule '{"name":"allow-github","action":"allow","domains":["github.com","api.github.com"],"ports":[{"port":443,"protocol":"tcp"}]}' \\
  -s sb_abc123`
    }
  ]}
/>

## Legacy Compatibility

`egress.allowedDomains`, `allowedCidrs`, `allowedPorts`, `deniedDomains`, `deniedCidrs`, and `deniedPorts` are still accepted for compatibility:

- In `block-all`, only `allowed*` fields are enforced.
- In `allow-all`, only `denied*` fields are enforced.

Prefer `trafficRules` for all new policies.

## Next Steps

<CardGroup>
  <Card title="Protocol Controls" href="/docs/sandbox/protocol-controls" cta="Continue">
    Restrict MCP tools and future protocol-level operations.
  </Card>

  <Card title="Egress Proxy" href="/docs/sandbox/proxy" cta="Continue">
    Route allowed TCP egress through a customer-managed SOCKS5 proxy.
  </Card>

  <Card title="Sandbox Services" href="/docs/sandbox/services" cta="Continue">
    Expose named sandbox ports through public HTTP service routes.
  </Card>
</CardGroup>
