> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Network Policies

> Deep dive into NetworkPolicy: domain allowlists, IP CIDR rules, metadata service blocking, and the Layer-7 TCP proxy internals.

`NetworkPolicy` provides fine-grained control over what network destinations a sandbox can reach. It operates at two layers: kernel-level IP filtering via `iptables` and application-level domain filtering via the per-namespace TCP proxy.

## NetworkPolicy model

```python theme={null}
from declaw import NetworkPolicy, SecurityPolicy, ALL_TRAFFIC

policy = SecurityPolicy(
    network=NetworkPolicy(
        allow_out=["*.openai.com", "pypi.org", "1.1.1.1"],
        deny_out=[ALL_TRAFFIC],
        allow_public_traffic=False,
        mask_request_host="internal-proxy.corp.com",
    )
)
```

| Field                  | Type           | Default | Description                                                                                                                                                              |
| ---------------------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `allow_out`            | `list[str]`    | `[]`    | Domains, IPs, or CIDRs to allow outbound                                                                                                                                 |
| `deny_out`             | `list[str]`    | `[]`    | Domains, IPs, or CIDRs to deny outbound                                                                                                                                  |
| `allow_public_traffic` | `bool \| None` | `True`  | Whether external HTTP clients can reach ports inside the sandbox via the [port proxy](/features/port-proxy). Set to `false` to return 403 on all inbound proxy requests. |
| `mask_request_host`    | `str \| None`  | `None`  | Override the `Host` header on outbound requests                                                                                                                          |

## Domain-based rules

Domain entries in `allow_out` and `deny_out` are matched against the TLS SNI field (HTTPS) or HTTP `Host` header (HTTP).

### Exact match

```python theme={null}
NetworkPolicy(allow_out=["api.openai.com"])  # matches only api.openai.com
```

### Wildcard subdomains

```python theme={null}
NetworkPolicy(allow_out=["*.openai.com"])
# Matches: api.openai.com, platform.openai.com, files.openai.com
# Does not match: openai.com (no subdomain)
```

### Regex patterns

Prefix the entry with `~` to use a regex:

```python theme={null}
NetworkPolicy(allow_out=["~.*\\.anthropic\\.com"])
# Matches any subdomain or path at anthropic.com
```

## IP and CIDR rules

IP and CIDR entries bypass the domain proxy and are applied directly as `iptables` rules in the kernel — zero userspace overhead.

```python theme={null}
NetworkPolicy(
    allow_out=["1.1.1.1", "8.8.8.0/24", "10.0.0.0/8"],
    deny_out=[ALL_TRAFFIC],
)
```

`ALL_TRAFFIC` is equivalent to `"0.0.0.0/0"`.

<Note>
  **Any `allow_out` entry turns egress into a whitelist.** As soon as `allow_out` is
  non-empty — whether it lists domains, IPs, or CIDRs — the sandbox may reach **only**
  the listed destinations; everything else is denied. The explicit
  `deny_out=[ALL_TRAFFIC]` in the example above is therefore optional (it is implied by
  the allowlist), though harmless to include. A sandbox created with **no** network
  policy is unrestricted — the allowlist applies only when you opt in with `allow_out`.
</Note>

<Note>
  **Egress enforcement is IPv4.** Declaw sandboxes route IPv4 only — IPv6 egress is
  disabled (dropped at the kernel), so a sandbox cannot send IPv6 traffic regardless
  of policy. Virtually every public endpoint is reachable over IPv4, so this is
  transparent in normal use, and it guarantees an `allow_out` allowlist cannot be
  bypassed over IPv6. IPv6-only destinations are not currently supported.
</Note>

## Priority rules

Allow rules always take precedence over deny rules, regardless of the order they are listed.

```python theme={null}
# This allows 1.1.1.1 even though 0.0.0.0/0 is denied
NetworkPolicy(
    allow_out=["1.1.1.1"],
    deny_out=[ALL_TRAFFIC],
)
```

## Metadata service blocking

Cloud instance-metadata and credential endpoints are always blocked in **every** sandbox, regardless of the network policy (or whether one is set). This prevents SSRF attacks where agent code could steal the worker's cloud credentials. The blocked endpoints are:

* `169.254.169.254` — AWS/Azure IMDS and GCP metadata (`metadata.google.internal` resolves here)
* `169.254.170.2` — AWS ECS/Fargate task metadata + credentials
* `169.254.170.23` — AWS EKS Pod Identity
* all IPv6 metadata endpoints (IPv6 egress is disabled entirely — see above)

```python theme={null}
result = sbx.commands.run("curl -s http://169.254.169.254/latest/meta-data/")
# Always fails — blocked at the kernel iptables level before any proxy
```

<Warning>
  These endpoints cannot be reached even if you add them to `allow_out` — they are dropped by kernel `DROP` rules that the sandbox network policy cannot override.
</Warning>

## DNS resolution under an allowlist

DNS keeps working whenever you set an egress allowlist — you do not need to add your
resolver to `allow_out` manually:

* **Domain allowlists:** Declaw runs a per-sandbox resolver that resolves your allowed
  domains and permits the resulting IPs automatically (this also handles CDN IP
  rotation, so a domain whose IPs change stays reachable).
* **IP/CIDR allowlists:** outbound DNS (UDP port 53) to Declaw's resolvers is
  permitted so `gethostbyname()` continues to resolve. DNS is **scoped to those
  resolvers** — a sandbox cannot send UDP/53 to an arbitrary host. To use a custom
  resolver, add its IP to `allow_out`.

<Note>
  Under an **IP/CIDR** allowlist, resolving a name does not grant access to its IP — you
  can look up any hostname, but you can only *connect* to the IPs you listed. To allow a
  service **by name**, add it as a **domain** entry in `allow_out`: domain entries
  resolve the name *and* permit the resolved IPs, whereas a raw IP allowlist does not.
</Note>

## How the TCP proxy works

```mermaid theme={null}
flowchart TD
    VM["Sandbox VM\nOutbound TCP"] --> IPT["iptables REDIRECT\n:80 -> :proxy_http_port\n:443 -> :proxy_tls_port"]
    IPT --> Proxy["Per-Namespace TCP Proxy\n(runs in host ns)"]
    Proxy --> Peek["Peek at first bytes"]
    Peek -->|"HTTPS\n(TLS ClientHello)"| SNI["Extract SNI\nfrom ClientHello"]
    Peek -->|"HTTP"| Host["Read Host header"]
    SNI --> Match["Domain policy\nmatch/no-match"]
    Host --> Match
    Match -->|"allowed"| FwdTLS["Forward (or edge proxy\nif PII enabled)"]
    Match -->|"denied"| Reject["TCP RST\n+ audit log"]
    FwdTLS --> Internet
```

The proxy runs in the host network namespace for the sandbox's network namespace. All TCP traffic from the VM is redirected to the proxy via `iptables REDIRECT` rules applied to the per-sandbox veth interface.

For HTTPS connections, the proxy peeks at the TLS ClientHello (without decrypting) to extract the SNI hostname. For HTTP connections, it reads the first line of the request to find the `Host` header.

If the hostname is in the allowlist and no body scanning is required, the proxy connects to the real destination and forwards the TCP stream directly without any TLS interception. This means there is no TLS overhead for pure network policies.

## Combine with SecurityPolicy for full control

`NetworkPolicy` as a field inside `SecurityPolicy` integrates with PII scanning and audit logging:

```python theme={null}
from declaw import Sandbox, SecurityPolicy, NetworkPolicy, PIIConfig, ALL_TRAFFIC

sbx = Sandbox.create(
    security=SecurityPolicy(
        network=NetworkPolicy(
            allow_out=["*.openai.com", "*.anthropic.com"],
            deny_out=[ALL_TRAFFIC],
        ),
        pii=PIIConfig(enabled=True, types=["ssn", "email"], action="redact"),
        audit=True,
    )
)
```

In this configuration:

1. iptables drops all non-DNS outbound traffic by default
2. TCP proxy intercepts connections to `*.openai.com` and `*.anthropic.com`
3. PII scanner activates TLS interception on those allowed domains
4. All events are logged to the audit trail

## GCP and AWS firewall hardening

At the infrastructure level, the deploy scripts configure cloud firewalls to restrict SSH access to your deployer IP only. The sandbox-level network policies provide defense-in-depth on top of the cloud firewall.

| Layer                     | Enforced by                  | Controls                                     |
| ------------------------- | ---------------------------- | -------------------------------------------- |
| Cloud firewall            | GCP/AWS security groups      | SSH access, inbound ports to the Declaw node |
| Sandbox network namespace | iptables rules per-veth      | IP/CIDR outbound policies                    |
| TCP proxy                 | Per-namespace Go process     | Domain-level outbound policies               |
| TLS interception          | Security proxy edge proxy CA | Body-level PII and injection scanning        |
