# Network Limits (/docs/network-limits)

Network limits control outbound internet access from sandboxes. Each sandbox runs behind a firewall that restricts which external IP addresses and domains it can reach, preventing untrusted code from exfiltrating data or contacting arbitrary hosts.

Egress is open by default. You can restrict access per sandbox with a network policy: a default-deny allow list of IPv4 CIDR ranges and domains. Set the policy when you [create a sandbox](#create-sandboxes-with-network-restrictions), then [update](#update-network-policy) or [reset](#reset-network-policy) it while the sandbox exists.

<NetworkVisual />

<Callout type="caution">
Enabling unrestricted network access may pose security risks when executing untrusted code. It is recommended to allow only the network addresses or domains you need, or block all network access. Test network connectivity before starting critical development work.
</Callout>

## Use cases [#use-cases]

Network limits are used when code in a sandbox must not reach arbitrary hosts.

- **Untrusted or generated code.** Code from users or agents runs with no outbound access, so it cannot send data out of the sandbox. With blocking on and empty allow lists, DNS resolves no names either. Network policies apply to Linux and macOS sandboxes, and Windows and GPU sandboxes do not accept them.

- **Agents that call known services.** A domain allow list lets an agent reach the model provider and the APIs it needs, and blocks every other host. The sandbox resolves only the names on the list. A domain allow list turns blocking on by itself.

- **Dependency installs.** The domains of package registries, such as PyPI or npm, are allowed, and the rest of the internet is blocked. A wildcard entry covers the subdomains of a registry host but not the host itself, which needs its own entry. Each entry is a hostname without a protocol, path, port, or query string.

- **Sensitive data.** A sandbox that processes private files or datasets reaches only the hosts on its allow list. DNS is filtered as well, so a name that is not on the list does not resolve. Egress is open by default, so the sandbox gets its policy when it is [created](#create-sandboxes-with-network-restrictions).

- **Restrict after setup.** A sandbox installs its dependencies with open egress, and its policy is updated to block all egress before untrusted code runs. The update applies in the background, and the policy status reads applied once the sandbox enforces it. An update works while the sandbox is running, paused, or stopped.

## Create sandboxes with network restrictions [#create-sandboxes-with-network-restrictions]

Create a sandbox with a network policy. With blocking turned on, the sandbox can reach only the domains and IPv4 ranges on its allow lists. A domain allow list turns blocking on by itself, while an IPv4 allow list restricts egress only when blocking is on. When a policy has both lists, web traffic on ports 80 and 443 needs a listed domain, even to a listed IPv4 range. Network policies apply to [Linux sandboxes](/docs/linux-sandboxes) and [macOS sandboxes](/docs/macos-sandboxes); the examples below create Linux sandboxes, and a macOS create takes the same policy. [Windows](/docs/windows-sandboxes) and [GPU](/docs/gpu-sandboxes) sandboxes do not accept them.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()

# Allow access to specific domains; everything else is denied
sandbox = daytona.v2.create(
    network_policy={"block_all": True, "allow_domains": ["example.com", "*.daytona.io"]},
)
```

| **Parameter**    | **Type**             | **Description**                                                                                                                  |
| ---------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `network_policy` | NetworkPolicyInput | Sets the outbound network policy. It accepts only the `block_all`, `allow_cidrs`, and `allow_domains` keys. Any other key raises `BadRequestError` before the request is sent. If `allow_domains` is set, an omitted `block_all` becomes `True`. Setting `block_all=False` with `allow_domains` is rejected. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// Allow access to specific domains; everything else is denied
const sandbox = await daytona.v2.create({
  networkPolicy: { blockAll: true, allowDomains: ['example.com', '*.daytona.io'] },
})
```

| **Parameter**   | **Type**             | **Description**                                                                                                                    |
| --------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `networkPolicy` | NetworkPolicyInput | Sets the outbound network policy. It accepts only the `blockAll`, `allowCidrs`, and `allowDomains` fields. Any other field throws `BadRequestError` before the request is sent. If `allowDomains` is set, an omitted `blockAll` becomes `true`. Setting `blockAll: false` with `allowDomains` is rejected. |

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Allow access to specific domains; everything else is denied
daytona sandbox create \
  --block-all \
  --allow-domain example.com \
  --allow-domain '*.daytona.io'
```

| **Flag**         | **Type** | **Description**                  |
| ---------------- | -------- | -------------------------------- |
| `--allow-domain` | string | Adds one entry to the domain allow list. It implies `--block-all`. Repeat the flag for more entries. |
| `--allow-cidr`   | string | Adds one entry to the network allow list. Repeat the flag for more entries. It requires `--block-all` or `--allow-domain`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Only `daytona sandbox create` and `daytona sandbox create-macos` accept the network flags.
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Allow access to specific domains; everything else is denied
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
    "network_policy": { "block_all": true, "allow_domains": ["example.com", "*.daytona.io"] }
  }'
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The API rejects `"block_all": false` together with `allow_domains`.
- Without `allow_domains`, `block_all` defaults to `false`.
- The `allow_cidrs` and `allow_domains` lists hold at most 128 entries combined.
</Callout>

</TabItem>
</Tabs>

### Block all egress [#block-all-egress]

Create a sandbox with no outbound access, for code that needs no network. With blocking on and empty allow lists, the sandbox reaches no external address and DNS resolves no names.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()

# Deny all outbound traffic
sandbox = daytona.v2.create(network_policy={"block_all": True})
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// Deny all outbound traffic
const sandbox = await daytona.v2.create({
  networkPolicy: { blockAll: true },
})
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Deny all outbound traffic
daytona sandbox create --block-all
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Deny all outbound traffic
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
    "network_policy": { "block_all": true }
  }'
```

</TabItem>
</Tabs>

### Allow IPv4 ranges [#allow-ipv4-ranges]

Create a sandbox that reaches only specific IPv4 addresses and ranges, in the [network allow list format](#network-allow-list-format). An IPv4 allow list applies only when blocking is on, so turn it on explicitly. Without a domain allow list, DNS resolves a name only to its IPv4 addresses inside the allowed ranges, and a name with no address in them resolves to no address. IPv6 lookups are refused.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()

# Allow one address and one subnet; everything else is denied
sandbox = daytona.v2.create(
    network_policy={"block_all": True, "allow_cidrs": ["1.1.1.1/32", "208.80.152.0/22"]},
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// Allow one address and one subnet; everything else is denied
const sandbox = await daytona.v2.create({
  networkPolicy: { blockAll: true, allowCidrs: ['1.1.1.1/32', '208.80.152.0/22'] },
})
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Allow one address and one subnet; everything else is denied
daytona sandbox create \
  --block-all \
  --allow-cidr 1.1.1.1/32 \
  --allow-cidr 208.80.152.0/22
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Allow one address and one subnet; everything else is denied
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
    "network_policy": { "block_all": true, "allow_cidrs": ["1.1.1.1/32", "208.80.152.0/22"] }
  }'
```

</TabItem>
</Tabs>

### Allow domains [#allow-domains]

Create a sandbox that reaches only specific hosts by name, in the [domain allow list format](#domain-allow-list-format). The sandbox resolves and connects to the listed domains; a wildcard covers every subdomain of a host but not the host itself. The domain list turns blocking on by itself.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()

# Allow a host and all of its subdomains; everything else is denied
sandbox = daytona.v2.create(
    network_policy={"allow_domains": ["pythonhosted.org", "*.pythonhosted.org", "pypi.org"]},
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// Allow a host and all of its subdomains; everything else is denied
const sandbox = await daytona.v2.create({
  networkPolicy: { allowDomains: ['pythonhosted.org', '*.pythonhosted.org', 'pypi.org'] },
})
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Allow a host and all of its subdomains; everything else is denied
daytona sandbox create \
  --allow-domain pythonhosted.org \
  --allow-domain '*.pythonhosted.org' \
  --allow-domain pypi.org
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Allow a host and all of its subdomains; everything else is denied
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{
    "network_policy": { "allow_domains": ["pythonhosted.org", "*.pythonhosted.org", "pypi.org"] }
  }'
```

</TabItem>
</Tabs>

## Network allow list format [#network-allow-list-format]

Allow outbound traffic to specific IPv4 addresses and ranges. Each entry on the network allow list is a single address or a CIDR block, and the sandbox can reach it on any port. With a domain allow list, ports 80 and 443 still need a listed hostname, whatever address the sandbox connects to: a web request that names a host not on the domain allow list is refused with `403`, even when its address is on the network allow list, and UDP port 443 is blocked. The list takes effect only when blocking is on. The SDKs, the API, and the CLI reject a non-empty list unless blocking or a domain allow list is set; the API returns `400`.

| **Rule**                  | **Description**                                                                                                                                               |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **IPv4 only**             | IPv6 addresses and IPv6 CIDR blocks are not supported.                                                                                                        |
| **CIDR format**           | Use standard CIDR notation (`A.B.C.D/N`) with a prefix length up to `32`. The address must be the network address of the range: a CIDR with host bits set, such as `208.80.154.0/22`, returns `400`. A bare address such as `208.80.154.232` is also accepted. |
| **No private ranges**     | Loopback (`127.0.0.0/8`), private (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), link-local (`169.254.0.0/16`), carrier-grade NAT (`100.64.0.0/10`), multicast (`224.0.0.0/4`), benchmarking (`198.18.0.0/15`), `0.0.0.0/8`, `192.0.0.0/24`, `240.0.0.0/4` (which includes the broadcast address), and `168.63.129.16` return `400`, as does any range that overlaps them, such as `0.0.0.0/0`. |
| **No nested entries**     | Do not list an entry that lies inside another entry, such as `208.80.154.232/32` together with `208.80.152.0/22`; the policy fails to apply, and at creation the sandbox does not start. List the larger range only. |
| **Max 128 entries**       | `allow_cidrs` and `allow_domains` combined.                                                                                                                   |
| **Whitespace is ignored** | Entries are trimmed. An empty entry returns `400`.                                                                                                            |

| **Example**           | **Value**                                    |
| --------------------- | -------------------------------------------- |
| **Single IP**         | `208.80.154.232/32` (Wikipedia)              |
| **Subnet**            | `208.80.152.0/22` (Wikimedia)                |
| **Multiple networks** | `["1.1.1.1/32", "208.80.152.0/22"]`          |

## Domain allow list format [#domain-allow-list-format]

Allow outbound traffic to specific hosts by name. Each entry is a hostname, or a wildcard covering a hostname's subdomains. A domain allow list turns blocking on by itself.

DNS is filtered as well: the sandbox resolves only the names on the list and refuses all others. A policy with only IPv4 entries resolves a name only to its addresses inside the allowed ranges, and a policy with empty lists resolves no names at all.

The domain allow list also covers web traffic to the addresses on the network allow list. On ports 80 and 443, the sandbox reaches a host only by a name on the domain allow list, and connecting to a listed IPv4 address does not change which host it reaches.

| **Rule**                | **Description**                                                                                                                                              |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Domains only**        | Use hostnames such as `example.com` or `api.openai.com`. Do not include protocols, paths, ports, or query strings.                                           |
| **Wildcards supported** | Prefix a domain with `*.` to allow its subdomains, for example `*.daytona.io`. A wildcard does not match the bare domain; add `daytona.io` as its own entry. The wildcard needs at least two labels after `*.`, so `*.com` returns `400`. |
| **ASCII only**          | Write internationalized names in punycode, for example `xn--bcher-kva.de` for `bücher.de`. A name with other characters returns `400`.                       |
| **Case-insensitive**    | Entries are lowercased before matching.                                                                                                                      |
| **Whitespace is ignored** | Entries are trimmed. An empty entry returns `400`.                                                                                                         |
| **Empty list**          | An empty `allow_domains` list also turns blocking on.                                                                                                        |
| **Max 128 entries**     | `allow_cidrs` and `allow_domains` combined.                                                                                                                  |

| **Example**          | **Value**                                           |
| -------------------- | --------------------------------------------------- |
| **Single domain**    | `example.com`                                       |
| **Wildcard domain**  | `*.daytona.io`                                      |
| **Multiple domains** | `["example.com", "*.daytona.io", "api.openai.com"]` |

## Operations [#operations]

The SDKs and the API get, update, and reset the network policy of an existing sandbox. The CLI sets a policy only when it creates a sandbox.

### Get network policy [#get-network-policy]

Get the network policy of a sandbox. The response carries two policies: the desired policy, which is the last one you set, and the applied policy, which the sandbox currently enforces. A change is applied in the background, so the two differ until the sandbox catches up. The response also carries an ETag that identifies the current revision; an update or reset must send it back.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

policy = sandbox.get_network_policy()
print(policy.status, policy.desired, policy.applied, policy.etag)
```

The response has the following fields:

| **Field** | **Description** |
| --- | --- |
| `desired` | The last policy set on the sandbox, or `None`. |
| `desired_revision` | The revision of `desired`. |
| `applied` | The policy the sandbox enforces, or `None`. |
| `applied_revision` | The revision of `applied`, or `None`. |
| `applied_start_id` | The sandbox start that `applied` was applied to, or `None`. |
| `etag` | The revision token to send back when you update or reset the policy. |
| `http_status` | The HTTP status code of the response. |
| `status` | The status is `pending`, `applied`, or `failed`. It is `None` for a sandbox that never had a policy. |
| `error` | Explains why the policy is not applied yet, or why it failed when `status` is `failed`. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const policy = await sandbox.getNetworkPolicy()
console.log(policy.status, policy.desired, policy.applied, policy.etag)
```

The response has the following fields:

| **Field** | **Description** |
| --- | --- |
| `desired` | The last policy set on the sandbox, or `undefined`. |
| `desiredRevision` | The revision of `desired`. |
| `applied` | The policy the sandbox enforces, or `undefined`. |
| `appliedRevision` | The revision of `applied`, or `undefined`. |
| `appliedStartId` | The sandbox start that `applied` was applied to, or `undefined`. |
| `etag` | The revision token to send back when you update or reset the policy. |
| `httpStatus` | The HTTP status code of the response. |
| `status` | The status is `pending`, `applied`, or `failed`. It is `undefined` for a sandbox that never had a policy. |
| `error` | Explains why the policy is not applied yet, or why it failed when `status` is `failed`. |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# --include prints the ETag response header
curl 'https://api.daytona.com/sandboxes/{sandboxId}/network-policy' \
  --include \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

```json
{
  "desired": { "version": 2, "block_all": true, "allow_cidrs": [], "allow_domains": ["example.com"] },
  "desired_revision": 1,
  "applied": { "version": 2, "block_all": true, "allow_cidrs": [], "allow_domains": ["example.com"] },
  "applied_revision": 1,
  "applied_start_id": "5b145d87c75d565a5e2b23a91fc1e325/60ad3ddc7c17b94141852ad65fdd10e5",
  "status": "applied",
  "error": null
}
```

The response has the following fields:

| **Field** | **Description** |
| --- | --- |
| `ETag` | Holds the revision as `"np-<revision>"`, for example `"np-1"`. This value is a response header. |
| `status` | The status is `pending`, `applied`, or `failed`. For a sandbox that never had a policy, `status` is `null` and the revision is `0`. |

</TabItem>
</Tabs>

### Update network policy [#update-network-policy]

Replace the whole network policy of an existing sandbox, with the same fields and rules as at [create time](#create-sandboxes-with-network-restrictions). The sandbox must be running, paused, or stopped. The new policy is stored at once and applied in the background; get the policy to see when it takes effect.

Every update sends the ETag of the revision it replaces. If the policy changed since, the update is rejected instead of overwriting it; get the policy and retry with the new ETag.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

current = sandbox.get_network_policy()
updated = sandbox.update_network_policy(
    network_policy={"block_all": True, "allow_domains": ["pypi.org", "files.pythonhosted.org"]},
    if_match=current.etag,
)
print(updated.status)  # pending until the sandbox applies it
```

| **Parameter**    | **Type**             | **Description**                                                                 |
| ---------------- | -------------------- | ------------------------------------------------------------------------------- |
| `network_policy` | NetworkPolicyInput | Sets the full new policy, with the `block_all`, `allow_cidrs`, and `allow_domains` keys. This parameter is required. |
| `if_match`       | str                | Passes the `etag` from the last `get_network_policy()` or update. This parameter is required. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const current = await sandbox.getNetworkPolicy()
const updated = await sandbox.updateNetworkPolicy(
  { blockAll: true, allowDomains: ['pypi.org', 'files.pythonhosted.org'] },
  { ifMatch: current.etag },
)
console.log(updated.status) // pending until the sandbox applies it
```

| **Parameter**     | **Type**             | **Description**                                                                    |
| ----------------- | -------------------- | ---------------------------------------------------------------------------------- |
| `networkPolicy`   | NetworkPolicyInput | Sets the full new policy, with the `blockAll`, `allowCidrs`, and `allowDomains` fields. This parameter is required. |
| `options.ifMatch` | string             | Passes the `etag` from the last `getNetworkPolicy()` or update. This parameter is required. |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://api.daytona.com/sandboxes/{sandboxId}/network-policy' \
  --request PUT \
  --header 'Content-Type: application/json' \
  --header 'If-Match: {etag}' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{ "block_all": true, "allow_domains": ["pypi.org", "files.pythonhosted.org"] }'
```

| **Header**  | **Description**                                                                                                       |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| `If-Match`  | Passes the `ETag` from the last get or update, with its quotes, for example `"np-1"`. This header is required.       |

The response has the following fields:

| **Field**     | **Description**                                                                        |
| ------------- | -------------------------------------------------------------------------------------- |
| Body          | The updated network policy, in the same shape as a get.                                |
| `ETag` header | The revision tag of the updated policy. Send it as `If-Match` on the next update.      |

</TabItem>
</Tabs>

### Reset network policy [#reset-network-policy]

Reset the network policy of a sandbox to open egress. A reset removes the restrictions but keeps the revision history, so it follows the same ETag rules as an update.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

current = sandbox.get_network_policy()
sandbox.reset_network_policy(if_match=current.etag)
```

| **Parameter** | **Type** | **Description**                                                      |
| ------------- | -------- | -------------------------------------------------------------------- |
| `if_match`    | str    | Passes the `etag` from the last `get_network_policy()` or update. This parameter is required. |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const current = await sandbox.getNetworkPolicy()
await sandbox.resetNetworkPolicy({ ifMatch: current.etag })
```

| **Parameter**     | **Type** | **Description**                                                     |
| ----------------- | -------- | ------------------------------------------------------------------- |
| `options.ifMatch` | string | Passes the `etag` from the last `getNetworkPolicy()` or update. This parameter is required. |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# A literal null body resets the policy to open egress
curl 'https://api.daytona.com/sandboxes/{sandboxId}/network-policy' \
  --request PUT \
  --header 'Content-Type: application/json' \
  --header 'If-Match: {etag}' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data 'null'
```

| **Header**  | **Description**                                                                                                       |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| `If-Match`  | Passes the `ETag` from the last get or update, with its quotes, for example `"np-2"`. This header is required.       |

</TabItem>
</Tabs>

### Test network access [#test-network-access]

Run the commands inside the sandbox. They test the allow lists only when blocking is on: a sandbox without a network policy has open egress, so both commands succeed. With blocking on, each command succeeds only when its target is on the sandbox's allow list.

```bash
# Test HTTP connectivity to an allowed address; needs 208.80.152.0/22 on the network allow list
curl -I http://208.80.154.232

# Test HTTP connectivity to an allowed domain; needs example.com on the domain allow list
curl -I https://example.com
```

## Permissions [#permissions]

Network policies use the sandbox permissions of the project, which you can grant to an [organization role](/docs/organizations#roles) or an [API key](/docs/api-keys).

The [audit logs](/docs/audit-logs) record network policy updates.

| **Permission**        | **Grants**                                                                         |
| --------------------- | ---------------------------------------------------------------------------------- |
| **`read:sandboxes`**  | Get the network policy of a sandbox                                                |
| **`write:sandboxes`** | Create sandboxes with network restrictions, and update or reset the network policy |
